TL,DR;
作爲一個C程序,這將是一個內存綁定算法。然而,即使是jit編譯的pypy代碼也有相當多的開銷,並且操作不再是「免費」的。令人驚訝的是(或者不是),兩個sieve
版本有不同的jit-code,第二個版本導致代碼變慢可能只是運氣不好。
如果是C,@Armin的答案是正確的。衆所周知,對於現代計算機/緩存和內存綁定代碼,如果我們跳過一個整數,並不重要 - 但所有的值都必須從內存中獲取,這是一個瓶頸。請參閱this article以獲得很好的解釋。
然而,我的實驗表明,非優化版本(sieve
)比優化版本(sieve_var
)稍快。時機還顯示,sieve
的最後一行,即[i for i in range(2, n) if nums[i] == 0]
的執行速度比sieve_var
-return [2] + [i for i in range(3, n, 2) if nums[i] == 0]
的行更快。
在我的機器上,它是0.45
秒,而對於10^8
元素,則是0.65
秒。這些數字可能因機器不同而不同,因此CPU更快和內存更慢的人很可能根本看不到任何差異。如果可以用「內存占主導地位」的方式來解釋,那麼我們應該能夠看到,速度較慢的版本有更多的緩存未命中。
但是,通過運行valgrind --tool=cachegrind pypy sieveXXX.py
我們可以看到,緩存未命中數量幾乎沒有差異,至少沒有任何數據可以解釋可觀察到的差異。
讓我們考慮一個稍微簡單的版本,它具有完全相同的行爲 - 我們不節約的素數,但只算來:
def sieve(n):
...
res=0
for i in range(2, n):
if nums[i] == 0:
res+=1
return res
def sieve_var(n):
...
res=1
for i in range(3, n,2):
if nums[i] == 0:
res+=1
return res
第一個版本仍然較快:0.35
秒。與0.45
秒(爲了確保時間差不是僥倖,而不是由於某些jit-熱身,我把代碼的最後一部分放到for循環中,並始終保持相同的時間)。
在進一步深入之前,讓我們來看看在C-實施及其裝配
long long int sum(long long int *a, int n){
long long int res=0;
for(int i=2;i<n;i++)
if(a[i]==0)
res++;
return res;
}
與gcc and -Os
we get編譯:
movl $2, %edx
xorl %eax, %eax
.L4:
cmpl %edx, %esi
jle .L1
cmpq $0, (%rdi,%rdx,8)
jne .L3
incq %rax
.L3:
incq %rdx
jmp .L4
.L1:
ret
非常小,直接的和只需要0.08
秒我機。我的內存可以達到10 GB/s的速度,並且有8*10^8
字節 - 因此基本上需要整個時間來獲取數據。
但是由此我們也看到,與C代碼相比,pypy版本的開銷大約爲0.25
秒。它從何而來?通過利用vmprof-module我們可以看到JIT碼和:
- 用於循環的一次迭代中,還有更多的操作比C-版本
- 的版本
sieve
和sieve_par
有很大的不同。我們可以使用use debugger to count the number of instruction迭代:24
sieve
和76
-sieve_var
只能處理每個第二個元素,所以關係實際上是24:38
。
很難說,爲什麼在沒有調試pypy的情況下,兩個版本的jit-code差別很大。可能是運氣不好,sieve_par
比較慢。
@ user2357112如果錯了,爲什麼'sieve(10 ** 8)== sieve_var(10 ** 8)'? – qwr
你是怎麼做到的? – user2357112
@ user2357112基本上't = time.clock();打印(LEN(篩(10 ** 8)));打印(time.clock() - t)' – qwr