Python 布尔值的大小列表和字节数组的相对性能

Python 布尔值的大小列表和字节数组的相对性能,python,performance,memory,Python,Performance,Memory,我在写一个简单的例子,在这个例子中,一个人在不执行任何除法或模运算的情况下,生成一个包含所有素数的列表,最大数为N。抽象地说,我的实现使用了一个由N个布尔值组成的数组,这些值都以False开头,并最终在算法过程中变为True >>> import sieve >>> sieve.verbose = True >>> x = sieve.soe_list(10000) soe_list, 10000 numbers, size = 83120

我在写一个简单的例子,在这个例子中,一个人在不执行任何除法或模运算的情况下,生成一个包含所有素数的列表,最大数为N。抽象地说,我的实现使用了一个由N个布尔值组成的数组,这些值都以False开头,并最终在算法过程中变为True

>>> import sieve
>>> sieve.verbose = True
>>> x = sieve.soe_list(10000)
soe_list, 10000 numbers, size = 83120
>>> x = sieve.soe_byte(10000)
soe_byte, 10000 numbers, size = 10993
>>> x = sieve.soe_list2(10000)
soe_list2, 10000 numbers, size = 83120
>>> x = sieve.soe_list(3000000)
soe_list, 3000000 numbers, size = 26791776
>>> x = sieve.soe_byte(3000000)
soe_byte, 3000000 numbers, size = 3138289
>>> x = sieve.soe_list2(3000000)
soe_list2, 3000000 numbers, size = 26791776

            10k       3M
byte (01)   ~11k      ~3.1M
list (01)   ~83k      ~27M
list (TF)   ~83k      ~27M
我想知道如果我将它实现为
0
1
列表,是
True
False
列表,还是
0
1
bytearray
,这是否会更快和/或使用更少的内存

计时(python 2.7) 使用Python2.7,当使用N=10k和N=30M时,我发现了以下几点:

$ python -m timeit -s 'import sieve' -n 10 'sieve.soe_list(3000000)'
10 loops, best of 3: 1.42 sec per loop
$ python -m timeit -s 'import sieve' -n 10 'sieve.soe_byte(3000000)'
10 loops, best of 3: 1.23 sec per loop
$ python -m timeit -s 'import sieve' -n 10 'sieve.soe_list2(3000000)'
10 loops, best of 3: 1.65 sec per loop

$ python -m timeit -s 'import sieve' -n 500 'sieve.soe_list(10000)'
500 loops, best of 3: 3.59 msec per loop
$ python -m timeit -s 'import sieve' -n 500 'sieve.soe_byte(10000)'
500 loops, best of 3: 4.12 msec per loop
$ python -m timeit -s 'import sieve' -n 500 'sieve.soe_list2(10000)'
500 loops, best of 3: 4.25 msec per loop

            10k       3M
byte (01)   4.12 ms   1.23 s
list (01)   3.59 ms   1.42 s
list (TF)   4.25 ms   1.65 s
令我惊讶的是,对于较小的N值,整数的
列表
是最好的,而对于较大的N值,
bytearray
是最好的。True和False的
列表总是比较慢

计时(python 3.3) 我还在python 3.3中重复了测试:

$ python -m timeit -s 'import sieve' -n 10 'sieve.soe_list(3000000)'
10 loops, best of 3: 2.05 sec per loop
$ python -m timeit -s 'import sieve' -n 10 'sieve.soe_byte(3000000)' 
10 loops, best of 3: 1.76 sec per loop
$ python -m timeit -s 'import sieve' -n 10 'sieve.soe_list2(3000000)'
10 loops, best of 3: 2.02 sec per loop

$ python -m timeit -s 'import sieve' -n 500 'sieve.soe_list(10000)'
500 loops, best of 3: 5.19 msec per loop
$ python -m timeit -s 'import sieve' -n 500 'sieve.soe_byte(10000)'
500 loops, best of 3: 5.34 msec per loop
$ python -m timeit -s 'import sieve' -n 500 'sieve.soe_list2(10000)'
500 loops, best of 3: 5.16 msec per loop

            10k       3M
byte (01)   5.34 ms   1.76 s
list (01)   5.19 ms   2.05 s
list (TF)   5.16 ms   2.02 s
在这里,同样的顺序是
列表
对于小N更好,而
bytearray
对于大N更好,但是
列表
带有
True
False
列表
带有
1
0
没有显著差异

内存使用 在Python2.7和3.3中,内存使用完全相同。我在
列表
bytearray
上使用了
sys.getsizeof
,在算法的开始和结束时大小相同

>>> import sieve
>>> sieve.verbose = True
>>> x = sieve.soe_list(10000)
soe_list, 10000 numbers, size = 83120
>>> x = sieve.soe_byte(10000)
soe_byte, 10000 numbers, size = 10993
>>> x = sieve.soe_list2(10000)
soe_list2, 10000 numbers, size = 83120
>>> x = sieve.soe_list(3000000)
soe_list, 3000000 numbers, size = 26791776
>>> x = sieve.soe_byte(3000000)
soe_byte, 3000000 numbers, size = 3138289
>>> x = sieve.soe_list2(3000000)
soe_list2, 3000000 numbers, size = 26791776

            10k       3M
byte (01)   ~11k      ~3.1M
list (01)   ~83k      ~27M
list (TF)   ~83k      ~27M
我有点惊讶~large
bytearray
比large
list
占用了更多的内存,因为large
bytearray
更快

编辑:哎呀,正如评论中指出的,我把自己的值看错了,把27M解释为270万。这个名单真的要大得多

问题 有人能解释一下为什么对于小N,使用
列表运行得更快,而对于大N,使用
字节数组运行得更快吗

测试代码供参考 sieve.py:

import sys

if sys.version_info.major == 3:
    xrange = range

verbose = False

def soe_byte(upper):
    numbers = bytearray(0 for _ in xrange(0,upper+1))
    if verbose:
        print("soe_byte, {} numbers, size = {}".format(upper, sys.getsizeof(numbers)))
    primes = []
    cur = 2
    while cur <= upper:
        if numbers[cur] == 1:
            cur += 1
            continue
        primes.append(cur)
        for i in xrange(cur,upper+1,cur):
             numbers[i] = 1
    return primes

def soe_list(upper):
    numbers = list(0 for _ in xrange(0,upper+1))
    if verbose:
        print("soe_list, {} numbers, size = {}".format(upper, sys.getsizeof(numbers)))
    primes = []
    cur = 2
    while cur <= upper:
        if numbers[cur] == 1:
            cur += 1
            continue
        primes.append(cur)
        for i in xrange(cur,upper+1,cur):
             numbers[i] = 1
    return primes

def soe_list2(upper):
    numbers = list(False for _ in xrange(0,upper+1))
    if verbose:
        print("soe_list2, {} numbers, size = {}".format(upper, sys.getsizeof(numbers)))
    primes = []
    cur = 2
    while cur <= upper:
        if numbers[cur] == True:
            cur += 1
            continue
        primes.append(cur)
        for i in xrange(cur,upper+1,cur):
             numbers[i] = True
    return primes 
导入系统 如果sys.version_info.major==3: xrange=范围 冗长=错误 def soe_字节(上限): 数字=字节数组(0表示x范围内的uu(0,上限+1)) 如果冗长: 打印(“soe_字节,{}数字,大小={}”。格式(大写,sys.getsizeof(数字))) 素数=[] cur=2 趁现在 有人能解释一下为什么这个算法运行得更快吗 小N,对大N使用bytearray更快

这都是特定于实现的,但您会看到这种现象在实践中经常发生,在小输入上使用较小的数据类型会执行得更差,在大输入上会执行得更好

例如,如果您使用位逻辑提取第n位(其中
n
是一个变量),而不是仅使用布尔数组,则通常会出现这种情况。当
n
是一个运行时变量时,从字节中提取第n位需要更多的指令,而不仅仅是设置整个字节,但是位集使用的空间更少

从广义上讲,这通常是因为用于访问较小类型的指令更昂贵,但硬件缓存比DRAM访问快得多,而且随着输入大小越来越大,使用较小类型所获得的改进的空间局部性弥补了这一点

换言之,当您点击那些较大的输入时,空间位置扮演着越来越重要的角色,而较小的数据类型会提供更多的空间位置(允许您将更多的相邻元素放入缓存线)。您还可能得到改进的时间局部性(更频繁地访问缓存线中相同的相邻元素)。因此,即使这些元素在加载到寄存器时需要更多的指令或更昂贵的指令,但现在从缓存访问更多的内存,这一开销得到了更大的补偿

至于为什么
bytearray
可能需要比整数列表更多的指令或更昂贵的指令,我不确定。这是非常具体的具体实施细节。但在您的例子中,它可能试图将bytearray的第n个元素加载到dword对齐的边界和dword大小的寄存器中,并且必须使用额外的指令和操作数提取要在寄存器中修改的特定字节。这完全是猜测,除非我们知道Python编译器/解释器为特定机器发出的确切机器指令。但不管是什么情况,您的测试表明,
ByteArray
需要更昂贵的指令来访问,但缓存友好度要高得多(当您点击那些较大的输入时,这会得到更多的补偿)


在任何情况下,当涉及到较小的数据类型而不是较大的数据类型时,您都会看到这种情况经常发生。这包括压缩,其中处理压缩数据(仔细创建,注意硬件细节,如对齐)有时会优于未压缩数据,因为解压缩数据所需的额外处理由压缩数据的改进空间位置补偿,但只有在内存访问开始发挥更关键作用的足够大的输入时。

您的大小计算似乎有错误。列表的内存使用量(以字节为单位)应该是~27MB,这比bytearray使用的3.1MB大得多。你是对的——这是人为错误——我在创建表时读取的值是错误的。我想这也可以解释。总的来说,
list
可能比
bytearray
快,但我只是在内存足够大时得到缓存未命中?python 3次的摘要似乎是从python 2.7部分复制的。这与你的实际计时结果不符。是的,我真的把汇总表弄乱了,我把它放在这里是为了让它变得更简单