You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何pd.DataFrame内存占用低于pd.Series?大数据处理内存疑问

大数据处理中Series与DataFrame内存占用异常分析

问题场景

开发大数据处理程序时,为降低DataFrame内存占用,测试读取大型sas7bdat文件单列数据的两种方式(构建pd.Series与模拟DataFrame方式加载),发现内存指标出现矛盾。

测试结果

方式1:直接构建Series

以Series加载
0           b'Richard Wilkins'
1           b'Richard Wilkins'
2           b'Richard Wilkins'
3           b'Richard Wilkins'
4           b'Richard Wilkins'
                   ...
16539995      b'Rebecca Davis'
16539996      b'Rebecca Davis'
16539997      b'Rebecca Davis'
16539998      b'Rebecca Davis'
16539999      b'Rebecca Davis'
长度: 16540000, 数据类型: object
Series数据内存占用: 264640000 Bytes    -> 调用pd.Series.memory_usage()
Series对象内存占用: 1024200016 Bytes  -> 调用sys.getsizeof(Series)
进程物理内存占用: 2740.828125 MB,耗时: 45.28001594543457秒  -> 调用psutil.Process().memory_info().rss

方式2:通过列表收集后concat(模拟DataFrame方式)

以pd.concat构建
0           b'Richard Wilkins'
1           b'Richard Wilkins'
2           b'Richard Wilkins'
3           b'Richard Wilkins'
4           b'Richard Wilkins'
                   ...
16539995      b'Rebecca Davis'
16539996      b'Rebecca Davis'
16539997      b'Rebecca Davis'
16539998      b'Rebecca Davis'
16539999      b'Rebecca Davis'
名称: name, 长度: 16540000, 数据类型: object
结果数据内存占用: 132320128 Bytes
结果对象内存占用: 891880144 Bytes
进程物理内存占用: 3775.93359375 MB,耗时: 41.85226058959961秒

矛盾点与疑问

测试结果显示:

  • Series的数据内存为252MB,而第二种方式的数据内存为126MB(仅前者一半);
  • Series对象内存为976MB,第二种方式的对象内存为850MB(后者更低);
  • 但进程物理内存显示第二种方式占用更高。

请问这是什么原因?开发中应信任哪种内存指标?

测试代码

sas_read_column_series.py

# sas_read_column_series.py 
import os
import time
from sys import getsizeof

import pandas as pd
import psutil

file_path = os.path.dirname(os.path.realpath(__file__)) + os.sep
df_iter = pd.read_sas(
    file_path + "test.sas7bdat", chunksize=1000000)
# 列名:idx, name, age, gender, country, birthdate, email

ps = psutil.Process()
start_time = time.time()
result_series = pd.Series()
while True:
    df_block = df_iter.read()
    print(
        f"当前内存占用: { ps.memory_info().rss / 1024 / 1024 } MB,耗时: { time.time() - start_time } 秒")
    if len(df_block) == 0:
        break
    result_series = pd.concat([result_series, df_block["name"]])

end_time = time.time()
print(f"结果Series: { result_series }")
print(
    f"Series数据内存占用: { result_series.memory_usage() } Bytes")
print(
    f"Series对象内存占用: { getsizeof(result_series) } Bytes")
print(
    f"总内存占用: { ps.memory_info().rss / 1024 / 1024 } MB,总耗时: { end_time - start_time } 秒")

sas_read_column_dataframe.py

# sas_read_column_dataframe.py 
import os
import time
from sys import getsizeof

import pandas as pd
import psutil

file_path = os.path.dirname(os.path.realpath(__file__)) + os.sep
df_iter = pd.read_sas(
    file_path + "test.sas7bdat", chunksize=1000000)
# 列名:idx, name, age, gender, country, birthdate, email

ps = psutil.Process()
start_time = time.time()
result_df = []
while True:
    df_block = df_iter.read()
    print(
        f"当前内存占用: { ps.memory_info().rss / 1024 / 1024 } MB,耗时: { time.time() - start_time } 秒")
    if len(df_block) == 0:
        break
    result_df.append(df_block["name"])
result_df = pd.concat(result_df, axis=0)

end_time = time.time()
print(f"结果DF: { result_df }")
print(
    f"DF数据内存占用: { result_df.memory_usage() } Bytes")
print(
    f"DF对象内存占用: { getsizeof(result_df) } Bytes")
print(
    f"总内存占用: { ps.memory_info().rss / 1024 / 1024 } MB,总耗时: { end_time - start_time } 秒")

问题解析与建议

内存指标差异原因

  1. memory_usage()
    该方法仅统计pandas对象存储数据的内存。对于Series,默认index=True,会包含索引的内存开销;第二种方式通过列表收集块后一次性concat,索引被统一生成,减少了重复开销,因此数据内存占比更低。另外,对于object类型,默认memory_usage()仅统计指针大小,若要统计实际字符串内存,需添加deep=True参数。

  2. sys.getsizeof()
    该方法统计Python对象的总内存,包括内部结构、元数据等额外开销。pandas的Series是复杂对象,两种方式的差异源于对象内部结构的优化程度:一次性concat生成的对象结构更紧凑,因此整体内存开销更低。

  3. psutil的rss
    该指标是进程的常驻物理内存,包含临时变量、缓存、未回收的中间对象、Python解释器内存等所有进程占用的资源。第二种方式循环中所有块的Series都被存储在列表中,直到最后concat才可能被释放,因此进程内存占用更高;而第一种方式每次concat后旧Series会被覆盖,引用计数降低后可被垃圾回收,内存开销更小。

开发中指标选择

  • 优化数据存储:优先使用memory_usage(deep=True),它能准确统计数据本身(包括object类型的实际内容)的内存占用,是优化pandas内存的核心指标。
  • 了解对象整体开销:sys.getsizeof()可用于查看Python对象的总内存,但仅作为辅助参考,不能精准反映数据存储的实际开销。
  • 监控程序整体内存:psutil的rss适合监控整个程序的内存消耗,用于排查内存泄漏或评估程序的整体资源占用,但无法单独反映单个pandas对象的内存。

另外需注意:第二个脚本中最终result_df实际是Series而非DataFrame,变量命名可能造成混淆,若要测试真正的单列DataFrame,应将df_block["name"]改为df_block[["name"]](双层方括号)。

内容的提问来源于stack exchange,提问作者Eveheeero

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 05:10:21