我已经使用大熊猫进行研究了大约两个月,效果很好 . 有了大量的中型跟踪事件数据集,pandas PyTables(HDF5接口)在允许我使用我所熟悉和喜爱的所有Python工具处理异构数据方面做了大量工作 .
一般来说,我在PyTables中使用Fixed(以前称为“Storer”)格式,因为我的工作流程是一次写入,多次读取,并且我的许多数据集的大小都是这样的,我可以将50-100个数据集加载到内存中 . 时间没有严重的缺点 . (注意:我的大部分工作都是在具有128GB系统内存的Opteron服务器级机器上完成的 . )
但是,对于大型数据集(500MB或更高),我希望能够使用PyTables“Tables”格式的更具可伸缩性的随机访问和查询功能,这样我就可以在内存之外执行查询,然后将更小的结果集加载到内存中进行处理 . 然而,这里的一大障碍是写性能 . 是的,正如我所说,我的工作流程是一次写入,多次读取,但相对时间仍然是不可接受的 .
作为一个例子,我最近在我的48核心机器上运行了一个大型的Cholesky分解,花了3分8秒(188秒) . 这会生成~2.2 GB的跟踪文件 - 跟踪与程序并行生成,因此没有额外的“跟踪创建时间” .
我的二进制跟踪文件初始转换为pandas / PyTables格式花费了相当多的时间,但很大程度上是因为二进制格式是故意无序的,以减少跟踪生成器本身的性能影响 . 这与从Storer格式转换为Table格式时的性能损失无关 .
我的测试最初是用pandas 0.12,numpy 1.7.1,PyTables 2.4.0和numexpr 0.20.1运行的 . 我的48核心机器每个核心运行2.8GHz,我正在写一个ext3文件系统,它可能(但不一定)在SSD上 .
我可以在7.1秒内将整个数据集写入Storer格式的HDF5文件(生成文件大小:3.3GB) . 写入表格式的相同数据集(结果文件大小也是3.3GB),写入需要178.7秒 .
代码如下:
with Timer() as t:
store = pd.HDFStore('test_storer.h5', 'w')
store.put('events', events_dataset, table=False, append=False)
print('Fixed format write took ' + str(t.interval))
with Timer() as t:
store = pd.HDFStore('test_table.h5', 'w')
store.put('events', events_dataset, table=True, append=False)
print('Table format write took ' + str(t.interval))
输出很简单
Fixed format write took 7.1
Table format write took 178.7
我的数据集有28,880,943行,列是基本数据类型:
node_id int64
thread_id int64
handle_id int64
type int64
begin int64
end int64
duration int64
flags int64
unique_id int64
id int64
DSTL_LS_FULL float64
L2_DMISS float64
L3_MISS float64
kernel_type float64
dtype: object
...所以我认为写入速度不应该存在任何特定于数据的问题 .
我还尝试添加BLOSC压缩,以排除可能影响一种情况或另一种情况的任何奇怪的I / O问题,但压缩似乎同样会降低两者的性能 .
现在,我意识到pandas文档说Storer格式提供了更快的写入速度和更快的读取速度 . (我确实体验到更快的读取,因为读取Storer格式似乎需要大约2.5秒,而读取Table格式大约需要10秒 . )但是,表格格式写入应该花费25倍,这似乎太过分了 . 只要Storer格式写 .
参与PyTables或pandas的任何人都可以解释为什么写入可查询格式(显然只需要很少的额外数据)的架构(或其他)原因应该延长一个数量级?是否有希望在未来改善这一点?我喜欢参与一个项目或另一个项目的贡献,因为我的领域是高性能计算,我看到这个领域的两个项目都有一个重要的用例......但是对它做一些澄清是有帮助的 . 首先涉及的问题,和/或关于如何加快那些了解系统构建方式的人的建议 .
编辑:
在IPython中使用%prun运行以前的测试会为Storer / Fixed格式提供以下(可读性有所降低)配置文件输出:
%prun -l 20 profile.events.to_hdf('test.h5', 'events', table=False, append=False)
3223 function calls (3222 primitive calls) in 7.385 seconds
Ordered by: internal time
List reduced from 208 to 20 due to restriction <20>
ncalls tottime percall cumtime percall filename:lineno(function)
6 7.127 1.188 7.128 1.188 {method '_createArray' of 'tables.hdf5Extension.Array' objects}
1 0.242 0.242 0.242 0.242 {method '_closeFile' of 'tables.hdf5Extension.File' objects}
1 0.003 0.003 0.003 0.003 {method '_g_new' of 'tables.hdf5Extension.File' objects}
46 0.001 0.000 0.001 0.000 {method 'reduce' of 'numpy.ufunc' objects}
表格格式如下:
%prun -l 40 profile.events.to_hdf('test.h5', 'events', table=True, append=False, chunksize=1000000)
499082 function calls (499040 primitive calls) in 188.981 seconds
Ordered by: internal time
List reduced from 526 to 40 due to restriction <40>
ncalls tottime percall cumtime percall filename:lineno(function)
29 92.018 3.173 92.018 3.173 {pandas.lib.create_hdf_rows_2d}
640 20.987 0.033 20.987 0.033 {method '_append' of 'tables.hdf5Extension.Array' objects}
29 19.256 0.664 19.256 0.664 {method '_append_records' of 'tables.tableExtension.Table' objects}
406 19.182 0.047 19.182 0.047 {method '_g_writeSlice' of 'tables.hdf5Extension.Array' objects}
14244 10.646 0.001 10.646 0.001 {method '_g_readSlice' of 'tables.hdf5Extension.Array' objects}
472 10.359 0.022 10.359 0.022 {method 'copy' of 'numpy.ndarray' objects}
80 3.409 0.043 3.409 0.043 {tables.indexesExtension.keysort}
2 3.023 1.512 3.023 1.512 common.py:134(_isnull_ndarraylike)
41 2.489 0.061 2.533 0.062 {method '_fillCol' of 'tables.tableExtension.Row' objects}
87 2.401 0.028 2.401 0.028 {method 'astype' of 'numpy.ndarray' objects}
30 1.880 0.063 1.880 0.063 {method '_g_flush' of 'tables.hdf5Extension.Leaf' objects}
282 0.824 0.003 0.824 0.003 {method 'reduce' of 'numpy.ufunc' objects}
41 0.537 0.013 0.668 0.016 index.py:607(final_idx32)
14490 0.385 0.000 0.712 0.000 array.py:342(_interpret_indexing)
39 0.279 0.007 19.635 0.503 index.py:1219(reorder_slice)
2 0.256 0.128 10.063 5.031 index.py:1099(get_neworder)
1 0.090 0.090 119.392 119.392 pytables.py:3016(write_data)
57842 0.087 0.000 0.087 0.000 {numpy.core.multiarray.empty}
28570 0.062 0.000 0.107 0.000 utils.py:42(is_idx)
14164 0.062 0.000 7.181 0.001 array.py:711(_readSlice)
编辑2:
使用pandas 0.13的预发布副本再次运行(2013年11月20日美国东部时间11点左右),表格格式的写入时间显着改善,但仍然没有“合理地”与Storer / Fixed的写入速度进行比较格式 .
%prun -l 40 profile.events.to_hdf('test.h5', 'events', table=True, append=False, chunksize=1000000)
499748 function calls (499720 primitive calls) in 117.187 seconds
Ordered by: internal time
List reduced from 539 to 20 due to restriction <20>
ncalls tottime percall cumtime percall filename:lineno(function)
640 22.010 0.034 22.010 0.034 {method '_append' of 'tables.hdf5Extension.Array' objects}
29 20.782 0.717 20.782 0.717 {method '_append_records' of 'tables.tableExtension.Table' objects}
406 19.248 0.047 19.248 0.047 {method '_g_writeSlice' of 'tables.hdf5Extension.Array' objects}
14244 10.685 0.001 10.685 0.001 {method '_g_readSlice' of 'tables.hdf5Extension.Array' objects}
472 10.439 0.022 10.439 0.022 {method 'copy' of 'numpy.ndarray' objects}
30 7.356 0.245 7.356 0.245 {method '_g_flush' of 'tables.hdf5Extension.Leaf' objects}
29 7.161 0.247 37.609 1.297 pytables.py:3498(write_data_chunk)
2 3.888 1.944 3.888 1.944 common.py:197(_isnull_ndarraylike)
80 3.581 0.045 3.581 0.045 {tables.indexesExtension.keysort}
41 3.248 0.079 3.294 0.080 {method '_fillCol' of 'tables.tableExtension.Row' objects}
34 2.744 0.081 2.744 0.081 {method 'ravel' of 'numpy.ndarray' objects}
115 2.591 0.023 2.591 0.023 {method 'astype' of 'numpy.ndarray' objects}
270 0.875 0.003 0.875 0.003 {method 'reduce' of 'numpy.ufunc' objects}
41 0.560 0.014 0.732 0.018 index.py:607(final_idx32)
14490 0.387 0.000 0.712 0.000 array.py:342(_interpret_indexing)
39 0.303 0.008 19.617 0.503 index.py:1219(reorder_slice)
2 0.288 0.144 10.299 5.149 index.py:1099(get_neworder)
57871 0.087 0.000 0.087 0.000 {numpy.core.multiarray.empty}
1 0.084 0.084 45.266 45.266 pytables.py:3424(write_data)
1 0.080 0.080 55.542 55.542 pytables.py:3385(write)
我注意到在运行这些测试时,有很长一段时间写入似乎“暂停”(磁盘上的文件没有活跃增长),但在某些时段内CPU使用率也很低 .
我开始怀疑一些已知的ext3限制可能与pandas或PyTables严重交互 . Ext3和其他非基于范围的文件系统有时很难解除大文件的链接,例如,即使在1GB文件的简单'rm'中,类似的系统性能(CPU使用率低,但等待时间长)也很明显 .
为了澄清,在每个测试用例中,我确保在开始测试之前删除现有文件(如果有的话),以免导致任何ext3文件删除/覆盖惩罚 .
但是,当使用index = None重新运行此测试时,性能会大幅提升(约为50秒,而索引时为~120) . 所以看起来这个过程要么继续受CPU限制(我的系统有相对较旧的AMD Opteron Istanbul CPU运行@ 2.8GHz,尽管它也有8个插槽,每个插槽有6个核心CPU,除了其中一个,所有当然,在写入过程中处于空闲状态),或者PyTables或pandas尝试操作/读取/分析文件的方式之间存在一些冲突,当文件系统已部分或完全导致文件系统导致病态错误的I / O行为时,索引是发生 .
编辑3:
@ Jeff建议对较小的数据集(磁盘上为1.3 GB)进行测试,将PyTables从2.4升级到3.0.0之后,让我来到这里:
In [7]: %timeit f(df)
1 loops, best of 3: 3.7 s per loop
In [8]: %timeit f2(df) # where chunksize= 2 000 000
1 loops, best of 3: 13.8 s per loop
In [9]: %timeit f3(df) # where chunksize= 2 000 000
1 loops, best of 3: 43.4 s per loop
实际上,除了打开索引时(默认情况下),我的表现似乎在所有场景中都超过了他 . 但是,索引仍然似乎是一个杀手,如果我在运行这些测试时解释 top
和 ls
的输出的方式是正确的,那么仍有一段时间没有重要的处理或任何文件写入的发生(即,Python进程的CPU使用率接近0,文件大小保持不变) . 我只能假设这些是文件读取 . 为什么文件读取会导致速度减慢我很难理解,因为我可以在3秒内将这个磁盘中的整个3 GB文件可靠地加载到内存中 . 如果他们're not file reads, then what is the system '等待'? (没有其他人登录到该计算机,并且没有其他文件系统活动 . )
此时,对于相关python模块的升级版本,我的原始数据集的性能下降到下面的数字 . 特别感兴趣的是系统时间,我假设它至少是执行IO所花时间的上限,以及Wall时间,这似乎可能解释了这些没有写入/没有CPU活动的神秘时期 .
In [28]: %time f(profile.events)
CPU times: user 0 ns, sys: 7.16 s, total: 7.16 s
Wall time: 7.51 s
In [29]: %time f2(profile.events)
CPU times: user 18.7 s, sys: 14 s, total: 32.7 s
Wall time: 47.2 s
In [31]: %time f3(profile.events)
CPU times: user 1min 18s, sys: 14.4 s, total: 1min 32s
Wall time: 2min 5s
尽管如此,索引似乎会导致我的用例显着减慢 . 也许我应该尝试限制索引的字段而不是简单地执行默认情况(这可能很好地索引DataFrame中的所有字段)?我不确定这可能会如何影响查询时间,尤其是在查询基于非索引字段选择的情况下 .
根据Jeff的请求,生成文件的ptdump .
ptdump -av test.h5
/ (RootGroup) ''
/._v_attrs (AttributeSet), 4 attributes:
[CLASS := 'GROUP',
PYTABLES_FORMAT_VERSION := '2.1',
TITLE := '',
VERSION := '1.0']
/df (Group) ''
/df._v_attrs (AttributeSet), 14 attributes:
[CLASS := 'GROUP',
TITLE := '',
VERSION := '1.0',
data_columns := [],
encoding := None,
index_cols := [(0, 'index')],
info := {1: {'type': 'Index', 'names': [None]}, 'index': {}},
levels := 1,
nan_rep := 'nan',
non_index_axes :=
[(1, ['node_id', 'thread_id', 'handle_id', 'type', 'begin', 'end', 'duration', 'flags', 'unique_id', 'id', 'DSTL_LS_FULL', 'L2_DMISS', 'L3_MISS', 'kernel_type'])],
pandas_type := 'frame_table',
pandas_version := '0.10.1',
table_type := 'appendable_frame',
values_cols := ['values_block_0', 'values_block_1']]
/df/table (Table(28880943,)) ''
description := {
"index": Int64Col(shape=(), dflt=0, pos=0),
"values_block_0": Int64Col(shape=(10,), dflt=0, pos=1),
"values_block_1": Float64Col(shape=(4,), dflt=0.0, pos=2)}
byteorder := 'little'
chunkshape := (4369,)
autoindex := True
colindexes := {
"index": Index(6, medium, shuffle, zlib(1)).is_csi=False}
/df/table._v_attrs (AttributeSet), 15 attributes:
[CLASS := 'TABLE',
FIELD_0_FILL := 0,
FIELD_0_NAME := 'index',
FIELD_1_FILL := 0,
FIELD_1_NAME := 'values_block_0',
FIELD_2_FILL := 0.0,
FIELD_2_NAME := 'values_block_1',
NROWS := 28880943,
TITLE := '',
VERSION := '2.7',
index_kind := 'integer',
values_block_0_dtype := 'int64',
values_block_0_kind := ['node_id', 'thread_id', 'handle_id', 'type', 'begin', 'end', 'duration', 'flags', 'unique_id', 'id'],
values_block_1_dtype := 'float64',
values_block_1_kind := ['DSTL_LS_FULL', 'L2_DMISS', 'L3_MISS', 'kernel_type']]
以及更新的模块和完整数据集的另一个%prun:
%prun -l 25 %time f3(profile.events)
CPU times: user 1min 14s, sys: 16.2 s, total: 1min 30s
Wall time: 1min 48s
542678 function calls (542650 primitive calls) in 108.678 seconds
Ordered by: internal time
List reduced from 629 to 25 due to restriction <25>
ncalls tottime percall cumtime percall filename:lineno(function)
640 23.633 0.037 23.633 0.037 {method '_append' of 'tables.hdf5extension.Array' objects}
15 20.852 1.390 20.852 1.390 {method '_append_records' of 'tables.tableextension.Table' objects}
406 19.584 0.048 19.584 0.048 {method '_g_write_slice' of 'tables.hdf5extension.Array' objects}
14244 10.591 0.001 10.591 0.001 {method '_g_read_slice' of 'tables.hdf5extension.Array' objects}
458 9.693 0.021 9.693 0.021 {method 'copy' of 'numpy.ndarray' objects}
15 6.350 0.423 30.989 2.066 pytables.py:3498(write_data_chunk)
80 3.496 0.044 3.496 0.044 {tables.indexesextension.keysort}
41 3.335 0.081 3.376 0.082 {method '_fill_col' of 'tables.tableextension.Row' objects}
20 2.551 0.128 2.551 0.128 {method 'ravel' of 'numpy.ndarray' objects}
101 2.449 0.024 2.449 0.024 {method 'astype' of 'numpy.ndarray' objects}
16 1.789 0.112 1.789 0.112 {method '_g_flush' of 'tables.hdf5extension.Leaf' objects}
2 1.728 0.864 1.728 0.864 common.py:197(_isnull_ndarraylike)
41 0.586 0.014 0.842 0.021 index.py:637(final_idx32)
14490 0.292 0.000 0.616 0.000 array.py:368(_interpret_indexing)
2 0.283 0.142 10.267 5.134 index.py:1158(get_neworder)
274 0.251 0.001 0.251 0.001 {method 'reduce' of 'numpy.ufunc' objects}
39 0.174 0.004 19.373 0.497 index.py:1280(reorder_slice)
57857 0.085 0.000 0.085 0.000 {numpy.core.multiarray.empty}
1 0.083 0.083 35.657 35.657 pytables.py:3424(write_data)
1 0.065 0.065 45.338 45.338 pytables.py:3385(write)
14164 0.065 0.000 7.831 0.001 array.py:615(__getitem__)
28570 0.062 0.000 0.108 0.000 utils.py:47(is_idx)
47 0.055 0.001 0.055 0.001 {numpy.core.multiarray.arange}
28570 0.050 0.000 0.090 0.000 leaf.py:397(_process_range)
87797 0.048 0.000 0.048 0.000 {isinstance}
2 回答
这是一个有趣的讨论 . 我认为Peter在固定格式上的性能非常出色,因为格式一次写入,而且他有一个非常好的SSD(它的写入速度超过450 MB / s) .
附加到表是一个更复杂的操作(必须放大数据集,并且必须检查新记录,以便我们可以确保它们遵循表的模式) . 这就是为什么在表中追加行通常较慢(但仍然,Jeff正在达到~70 MB / s,这是非常好的) . 杰夫的速度比彼得快,可能是因为他拥有更好的处理器 .
最后,PyTables中的索引使用单个处理器,是的,这通常是一个昂贵的操作,所以如果你不打算在磁盘上查询数据,你应该真的禁用它 .
这是我刚才做的类似比较 . 它约有1/3的数据10M行 . 最终尺寸为1.3GB
我定义了3个定时功能:
测试固定格式(在0.12中称为Storer) . 这以PyTables数组格式写入
使用PyTables表格式以表格格式写入 . 不要创建索引 .
与f2相同,但创建索引(通常已完成)
创建框架
时间运行与OP提供的文件相同(链接如下)
像f1一样,固定格式
像f2,表格式,没有索引
像f3一样,表格格式带索引
像f3一样,带有索引的表格式,用blosc压缩
显示原始文件(test.h5,压缩,test.hdf)
有几点需要注意 .
你没有包括你的索引是什么,也没有包括它的排序(虽然我只认为这会产生一些小的差异) .
我的例子中的写入惩罚大约是2倍(尽管我已经看到它在包含索引时间时有点大) . 因此你的7s(我的时间的1/2),是我写作数字的3倍是非常可疑的 . 我正在使用一个相当快的磁盘阵列 . 如果你使用基于闪存的磁盘,那么这是可能的 .
master / v0.13.0(很快发布),大大改善了表的写入时间 .
您可以尝试在写入数据时将
chunksize
参数设置为更大的数字(默认值为100000) . 'relatively'低数字的目的是具有恒定的内存使用率 . (例如,如果更大,你将使用更多的内存,理论上它应该写得更快) .Tables比固定格式提供2个优点:1)查询检索,以及2)可附加性 . 读取整个表都没有利用,所以如果你只想读取整个表,那么建议使用Fixed格式 . (根据我的经验,Table的灵活性大大超过了写入惩罚,但是YMMV)
底线是重复计时(使用ipython,因为它将运行多个测试) . 如果你可以重现你的结果,那么请发一个%prun,我会看一看 .
更新:
所以这个大小的表的推荐方法是用blosc压缩并使用pandas master / 0.13.0和PyTables 3.0.0