Python列表序列化 - 最快的方法

11
我需要在Python脚本中从文件中加载(反序列化)预先计算好的整数列表(进入Python列表)。该列表很大(高达数百万个项目),只要加载最快,我就可以选择存储它的格式。
哪种方法是最快的,为什么?
  1. 在仅包含分配给变量的列表的.py文件上使用import
  2. 使用cPickleload
  3. 其他某种方法(也许是numpy?)
此外,如何可靠地对这些事情进行基准测试?
补充说明:由于导入被缓存,因此无法在测试中多次执行。使用pickle加载的速度在第一次运行后也会更快,可能是由于操作系统的页面预取。 使用cPickle加载100万个数字需要1.1秒第一次运行,并且在脚本的后续执行中需要0.2秒。
直觉上,我觉得cPickle应该更快,但我希望有数字(我认为这很具挑战性)。
是的,对我来说,这很重要。
谢谢

这真的是你代码中最慢的部分吗?你会多频繁地加载文件? - Douglas Leeder
你尝试过这些吗?你现在有什么指标? - S.Lott
说句实话,你可以通过使用“execfile()”来避免导入问题... - gahooa
6个回答

7
我猜如果你真的需要一个列表中的这个元素,cPickle将是最快的选择。
如果你可以使用内置序列类型array,我测试了100万个整数只用了四分之一秒钟:
from array import array
from datetime import datetime

def WriteInts(theArray,filename):
    f = file(filename,"wb")
    theArray.tofile(f)
    f.close()

def ReadInts(filename):
    d = datetime.utcnow()
    theArray = array('i')
    f = file(filename,"rb")
    try:
        theArray.fromfile(f,1000000000)
    except EOFError:
        pass
    print "Read %d ints in %s" % (len(theArray),datetime.utcnow() - d)
    return theArray

if __name__ == "__main__":
    a = array('i')
    a.extend(range(0,1000000))
    filename = "a_million_ints.dat"
    WriteInts(a,filename)
    r = ReadInts(filename)
    print "The 5th element is %d" % (r[4])

读取 1000000 个整数在 0:00:03.500000 内完成,你用了1/4秒钟吗? - Eli Bendersky
然而,你是对的,array.fromfile 比 cpickle 快得多!! - Eli Bendersky
@eliben - 你可能想选择这个作为最佳答案。使用timeit模块的课程很受欢迎,但它们并没有直接回答你的问题! - Greg Ball

3

关于基准测试,可以参考Python标准库中的timeit模块。为了找到最快的方法,可以尝试实现所有你能想到的方式,并使用timeit进行测量。

随机想法:根据具体情况,你可能会发现以类似于.newsrc文件中使用的方式来存储“整数集合”是最快的。

1, 3-1024, 11000-1200000

如果您需要检查某个东西是否在该集合中,则加载并与这种表示匹配应该是最快的方法之一。这假设您的整数集合相当密集,具有长的连续序列和相邻值。

2
为了帮助你计时,Python库提供了 timeit 模块:
该模块提供了一个简单的方法来计算 Python 代码的执行时间。它同时提供了命令行和可调用接口。它避免了许多常见的测量执行时间的陷阱。
以下是一个示例(摘自手册),比较使用 hasattr()try/except 来测试缺失和存在的对象属性的成本:
% timeit.py 'try:' '  str.__nonzero__' 'except AttributeError:' '  pass'
100000 loops, best of 3: 15.7 usec per loop
% timeit.py 'if hasattr(str, "__nonzero__"): pass'
100000 loops, best of 3: 4.26 usec per loop
% timeit.py 'try:' '  int.__nonzero__' 'except AttributeError:' '  pass'
1000000 loops, best of 3: 1.43 usec per loop
% timeit.py 'if hasattr(int, "__nonzero__"): pass'
100000 loops, best of 3: 2.23 usec per loop

2

你是否需要始终加载整个文件?如果不需要,unpack_from() 可能是最佳解决方案。假设您有1000000个整数,但只想加载从50000到50099的整数,则可以执行以下操作:

import struct
intSize = struct.calcsize('i') #this value would be constant for a given arch
intFile = open('/your/file.of.integers')
intTuple5K100 = struct.unpack_from('i'*100,intFile,50000*intSize)

2
"如何可靠地对这些事情进行基准测试?"
我不理解这个问题。
您可以编写一堆小函数来创建和保存各种形式的列表。
您可以编写一堆小函数来加载各种形式的列表。
您可以编写一个小计时器函数来获取开始时间,执行加载过程几十次(以获得足够长的平均值,使操作系统调度噪声不会主导测量)。
您可以在一个小报告中总结数据。
这有一些无关的问题,展示了如何衡量和比较性能。 将整数列表转换为一个数字? Python中的字符串连接与字符串替换"

如果import已经被缓存,我如何在循环中多次执行"import <filename>"? - Eli Bendersky
1
如果您的数据集足够大,一个测量可能就足够了。否则,您可以在shell循环中从命令行运行并计时。此外,请查看imp.load_module。 - S.Lott

1
cPickle将是最快的,因为它以二进制形式保存,不需要解析真正的Python代码。
其他优点是它更安全(因为它不执行命令),并且您不会遇到正确设置`$PYTHONPATH`的问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接