我正在写一个简单的Sieve of Eratosthenes,其中一个产生一个列表,其中包含所有素数,直到某个数N,而不执行任何除法或模运算。抽象地说,我的实现使用了一个N个布尔值的数组,它们都以False开头,并且最终在算法过程中翻转为True。
如果我将list
0
和1
list
True
False
实施,我想知道这会更快和/或使用更少的内存{1}}和bytearray
,或0
1
和$ 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
。
使用python 2.7,我在使用N = 10k和N = 30M时发现了以下内容:
list
令我惊讶的是,对于小的N值,bytearray
的整数是最好的,对于大的N值,list
是最好的。真假的$ 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
总是较慢。
我也在python 3.3中重复测试:
list
此处有相同的顺序,bytearray
对于小N更好,list
对于大N,但True
对False
和{{1与list
1
和0
的{{1}}没有显着差异。
在python 2.7和3.3中,内存使用完全相同。我在sys.getsizeof
或list
上使用了bytearray
,它在算法的开头和结尾处的大小相同。
>>> 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
我有点惊讶〜大 bytearray
使用的内存比大list
更大,因为大bytearray
更快。
任何人都可以解释为什么这个算法使用list
对小N运行得更快,而对于大N使用bytearray
更快?
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
答案 0 :(得分:2)
任何人都可以解释为什么这个算法使用列表运行得更快 小N,使用bytearray表示大N?
这些都是特定于实现的,但你会发现这种现象在实践中经常发生,使用较小的数据类型会在小输入上表现更差,而在更大的情况下表现更好。
例如,如果您使用按位逻辑来提取第n位(其中n
是变量)而不是使用布尔数组,那么通常会发生这种情况。当n
是运行时变量而不是仅设置整个字节时,从字节中提取第n位需要更多指令,但是bitset使用的空间更少。
从广义上讲,这通常是因为用于访问这些较小类型的指令更昂贵,但硬件缓存比DRAM访问速度快得多,并且使用较小类型所获得的空间局部性得到改善而不是弥补它您的输入大小越来越大。
换句话说,当您点击那些较大的输入时,空间位置起着越来越重要的作用,而较小的数据类型会为您提供更多的输入(允许您将更多相邻元素放入缓存行)。您可能还会获得改进的时间局部性(更频繁地访问缓存行中的相同相邻元素)。因此,即使元素在加载到寄存器中时需要更多指令或更昂贵的指令,现在您从缓存中访问更多内存这一事实也可以弥补这一开销。
现在为什么bytearrays
可能需要比整数列表更多的指令或更昂贵的指令,我不确定。这是一个非常具体的,特定于实现的细节。但也许在你的情况下,它试图将字节数组的第n个元素加载到双字对齐边界和双字大小的寄存器中,并且必须使用附加指令和操作数提取特定字节以在寄存器内修改。除非我们知道Python编译器/解释器为您的特定机器发出的确切机器指令,否则这是所有推测。但无论情况如何,您的测试都表明bytearrays
需要更昂贵的指令才能访问,但更适合缓存(当您点击那些较大的输入时,这会更多地补偿)。
在任何情况下,当涉及较小的数据类型而不是较大的数据类型时,您会发现这种事情发生了很多。这包括压缩,处理压缩数据,仔细创建,注意对齐等硬件细节,有时可以胜过未压缩数据,因为解压缩数据所需的额外处理可以通过改进的压缩数据空间局部性来补偿,但仅适用于足够大的输入内存访问开始发挥更重要的作用。