標籤:素數篩法 linux64 nasm 彙編
在博文(1)和(2)裡分別用了4中方式寫一個素數篩選的演算法,分別是javascript in browser、node.js、ruby和c;最終的結果是c最快,node.js其次,js in b雖然也不慢,但極不穩定,所以排在第三,ruby最慢。
現在我們在linux64中用組合語言重寫sieve演算法,看看動用最終的武器:組合語言,我們能不能進一步最佳化素數篩選演算法。
如果忘了演算法邏輯,不要緊,下面分別再次貼出node.js、ruby以及c的sieve代碼:
首先是node.js:
function sieve(n){ var a = new Int8Array(n+1); var max = Math.floor(Math.sqrt(n)); var p = 2; while(p <= max){ for(var i=2*p;i<=n;i+=p) a[i] = 1; while(a[++p]); /* empty */ } while(a[n]) n--; return n;}
然後是ruby:
def sieve(n) a = Array.new(n+1); max = Math.sqrt(n).to_i; p = 2; while p<=max do i = 2*p while i<=n do a[i] = 1 i+=p end while a[p+=1] == 1 do end end while a[n] do n-=1 end n end
最後是c的代碼:
ULL sieve(ULL n){ char *a = malloc(n+1); if(!a) return 0; memset(a,0,n+1); ULL max = sqrtl(n); ULL p = 2; while(p <= max){ for(ULL i=2*p;i<=n;i+=p) a[i] = 1; while(a[++p]); /* empty */ } while(a[n]) n--; return n;}
下面嘗試用彙編重寫sieve函數,需要注意的幾點是:
- 可以不調用C庫中的sqrtx標準函數,直接使用浮點fsqrt指令;
- 可以將絕大部分記憶體變數放到寄存器中以加速存取;
- 只關心sieve函數的演算法,而用c代碼調用彙編的sieve,這樣可以發揮各自的長處;否則我還得寫個讀取輸入參數的前置代碼,不值當的;
- 注意彙編和c的調用介面:在linux64中,參數並不壓棧傳遞;因為sieve只有一個參數,所以放在rdi中傳遞,傳回值還是放在rax中。
- 需要調用mmap申請足夠的記憶體以便做篩表。注意這裡沒有寫足夠詳細的錯誤處理,更詳細的操作請參考本貓的【linux下64位彙編的系統調用】系列博文。
- 最後要注意的是,代碼最佳化和代碼編寫一定不要同時進行!這在所有程式設計語言中都適用,彙編中尤為重要!否則必成一鍋粥鳥!因為誰都不可能上來就寫最佳化後的代碼,一定是先功能邏輯正常後在著手考慮最佳化的問題。本貓第一遍寫的是最保守代碼,全部變數放在記憶體中,隨用隨取,用完儲存。在代碼邏輯正確後(這時計算sieve 100000000所花時間為4xxx ms),在逐步將記憶體變數轉放到寄存器中。
要說明的是該段代碼肯定還可以進一步最佳化,但本貓就到這裡為止了,希望能夠拋磚引玉。先把結果說一下吧:用彙編寫的sieve版本是最快的,超過了c代碼,在本貓 Intel(R) Core(TM)2 Duo CPU T7100 @ 1.80GHz上跑出了最快的37xx毫秒,比c版的平均要快100-200毫秒,而且非常穩定。
最後貼出C的main.c和彙編的sieve.s代碼:
main.c:
#include <stdio.h>#include <stdlib.h>#include <stdbool.h>#include <string.h>#include <time.h>#include <unistd.h>typedef unsigned long long ULL;ULL sieve(ULL n);int main(int argc,char **argv){ ULL n = 0; if(argc < 2){ printf("usage %s n\n",argv[0]); return 1; } sscanf(argv[1],"%llu",&n); if(n == 0){ puts("wrong number format"); return 2; } else if(n < 0){ puts("must + number"); return 3; } int start = clock(); ULL result = sieve(n); if(result == -1){ puts("sieve calc failed!"); return 4; } double end = ((1.0 * (clock() - start)) / CLOCKS_PER_SEC) * 1000.0; printf("max p is %llu (take %f ms)\n",result,end); return 0;}
彙編的sieve.s:
section .data n:dq 0 len:dq 0 addr: dq 0 p:dq 2 max:dq 0 i:dq 2 section .text global sievesieve: push rbp push rbx push rcx mov rbp,rsp mov [n],rdi ;save 1st arg to n inc rdi mov [len],rdi ;mmap len = n + 1 mov eax,9 ;call syscall mmap mov rdi,0 mov rsi,[len] mov rdx,3 mov r10,33 mov r8,-1 mov r9,0 syscall cmp rax,0xfffffffffffff001 ;mmap error jb next mov rax,-1 ;return -1 jmp quitnext: ;save mmap return addr ;FIXME:mmap space always 0 ??? fild qword [n] ;calc sqrt(n) and save result to max fsqrt fistp qword [max] mov r15,[p] ;r15 = p mov r14,[max] ;r14 = max mov r13,[n] ;r13 = n mov r12,[i] ;r12 = ienter_while: cmp r15,r14 ;if p<=max ja quit_while mov rbx,r15 shl rbx,1 mov r12,rbxenter_for: cmp r12,r13 ja quit_for mov byte [rax + r12],1 add r12,r15 jmp enter_forquit_for: inc r15 mov cl,byte [rax + r15] test cl,cl jnz quit_for jmp enter_whilequit_while: mov cl,byte [rax + r13] test cl,cl jz pre_quit dec r13 jmp quit_whilepre_quit: mov rax,r13quit: mov rsp,rbp pop rcx pop rbx pop rbp ret
javascript、ruby和C效能一瞥(3) :上彙編