2010-05-28 34 views
4

我有一個32位的數字,想算知道有多少位是1NASM:指望有多少位在32位的數字設置爲1

我想這個僞代碼:

mov eax, [number] 
while(eax != 0) 
{ 
    div eax, 2 
    if(edx == 1) 
    { 
    ecx++; 
    } 
    shr eax, 1 
} 

有沒有更高效的方法?

我在x86處理器上使用NASM。

(我只是用匯編開始,所以請不要告訴我從圖書館的extern使用的代碼,因爲我甚至不知道如何將它們;))

(我剛剛發現How to count the number of set bits in a 32-bit integer?這也包含我的解決方案。還有其他的解決方案發布,但不幸我似乎無法弄清楚,我怎麼會用匯編語言編寫它們)

+0

顯然,你不應該實際使用'div',[這是最慢的整數指令一個(https://stackoverflow.com/questions/40354978/why-is-this-c-code-faster -than-MY-手寫具組件進行測試最collat​​/40355466#40355466)。只需用'test al,1'檢查EAX的低位。或'shr eax,1' /'adc ecx,0'將是實現該僞代碼的有效方式。 – 2018-02-19 14:54:31

回答

7

最有效的方法(無論如何,就執行時間而言)查找表。顯然,你不會有一個40億條目表,但是你可以把32位分解成8位塊,只需要一個256條表,或者進一步分成4位塊,只需要16個條目。祝你好運!

+0

如果預付費用是一個問題,您可以隨時構建查找表。你知道只有一個條目的值爲0 1,這就是0x00。因此,如果查找表中的條目爲0,則知道需要計算該條目,但是一旦您計算了一次,就可以將其存儲在那裏。這樣,當你開始時,你不必計算全部256個。 – corsiKa 2010-05-28 18:30:01

+0

@ glowcoder,這是一個很好的建議。不過,這個問題聽起來像是家庭作業問題,所以我認爲這有點矯枉過正。我想說,預生成表格要簡單得多。 – 2010-05-28 18:31:46

+1

你可以在15-20條指令中完成32位的人口數量(參見例如Warren的Hacker's Delight)。將這個詞分解爲8位塊,做4次表查找,然後總結4個結果可能不會像這樣高效,並且它不適用於優化,例如, SIMD,GPGPU等 – 2010-05-28 18:33:55

4

我的x86彙編是有點生疏,但想到:

clc   ; clear carry 
xor ecx, ecx ; clear ecx 

shl eax, 1  ; shift off one bit into carry 
adc ecx, 0  ; add carry flag to ecx 
; ... repeat the last two opcodes 31 more times 

ecx包含您的位計數。

x86 shift instructions設置CF到最後一位移出,其中adc ecx, 0讀取它。

+0

你不需要'clc',因爲'shl eax'無條件地將'CF'設置爲移出的位。 'adc'可能是最好的方法來實現這種樸素的方式,但是當'eax'變爲零時你可以退出循環,而不是總是做32次迭代。但是,任何類型的一次一位的循環比最好的[bithack](https://graphics.stanford.edu/~seander/bithacks.html)或LUT('pshufb')選項慢得多。 – 2016-05-12 00:46:18

5

在具有SSE4支持的處理器中,您可以使用POPCNT指令爲您執行此操作。

最天真的算法實際上比你想象的要快(DIV指令真的很慢)。

mov eax, [number] 
xor ecx,ecx 
loop_start: 
    test eax,1 
    jnz next 
    inc ecx 
next: 
    shr eax, 1 
    mov eax,ecx 

關於你提到的有關以前的SO答案評論,我會在那裏舉個例子答案,並指導您如何我會轉換。

long count_bits(long n) {  
    unsigned int c; // c accumulates the total bits set in v 
    for (c = 0; n; c++) 
    n &= n - 1; // clear the least significant bit set 
    return c; 
} 

(我假設你知道如何定義一個函數和有趣的東西)。 需要的是一個非常簡單的循環,一個計數器變量(傳統上,ecx既是索引也是計數器)和位測試指令。

mov edx,n 
    xor ecx,ecx 
loop_start: 
    test edx,edx 
    jz end 
    mov ebx,edx 
    dec ebx 
    and edx,ebx 
    inc ecx 
    jmp loop_start 
end: 
    mov eax,ecx 
    ret 

實施像組裝漢明權重算法並不複雜,但只是夠複雜了,你寧願不做爲初始作業的問題。

0

這個程序給你一個32位數的1。試試:)

extern printf      
SECTION .data     
msg: db "The number of 1 bits are: %d",10,0 
inta1: dd 1234567 
num: dd 2147483647 
SECTION .text      

global main     
main:  
    mov eax, [num] 
    mov ecx,32 
    mov edx,0 
.loop: dec ecx 
    cmp ecx,0 
    jl .exit 
    shr eax,1 
    jnc .loop 
    inc edx 
jmp .loop 
.exit: 
    push edx 
    push dword msg   
    call printf    
    add  esp, 8 
+0

另請參閱[@ ChrisDodd非常相似的答案](http://stackoverflow.com/a/37171656/224132)來解釋此用戶關於如何計數位的問題。 (但這不是剽竊,因爲邏輯是不同的,效率較低,並且圍繞它的'main'程序是原創的。)還要注意,在這個結尾處使用'ret'指令會使它不會崩潰。 – 2016-05-12 01:00:04

1
 mov eax,[c] 
     xor ebx,ebx 
SSS: shr eax,1 ; after shift, if eax=0 ZF flag=1 
     jz XXX  ; end (no more bit on eax) 
     adc bl 
     jmp SSS 
XXX: adc bl 
     movb [Nbit],bl 
+0

你可以修改循環在底部只有一個'jnz',而不是'jmp'和'jz'。進入時,跳轉到循環中間的'shr'。 SSS:'adc' /'shr' /'jnz SSS' /'adc'。由於可以進行額外的迭代,因此您可以在開始時剝離一些展開的迭代,以便進入循環。例如'mov ebx,eax' /'和ebx,1' /'shr eax,2' /然後落入第一個'adc'的循環中。當然,如果你關心性能,你不會使用這個天真的循環(除非你的值幾乎總是爲0到3或者什麼的,當這可能比bithack更快時) – 2017-08-06 18:54:16

0

使用BSF(位向前掃描)可能是一個有點比普通的換擋效率更高。

xor   edx,edx 
mov   eax,num 
bsf   ecx,eax 
je   end_bit_count 
; align? 
loop_bit_count: 
inc   ecx 
inc   edx 
shr   eax,cl 
bsf   ecx,eax 
jne   loop_bit_count 
end_bit_count: 
+0

對於設置了少量比特的輸入可能是的,但是在哪裏這些比特是稀疏的,而不是聚集在首先被移出的那一端。但是請注意,變量計數'shl'在Sandybridge系列上花費了3個微軟,並且'bsf'對輸出有一個錯誤的依賴,所以這裏是'ecx'上的循環運行依賴鏈。 https://stackoverflow.com/questions/21390165/why-does-breaking-the-output-dependency-of-lzcnt-matter。 (儘管2週期dep鏈可能不是瓶頸) – 2018-02-18 15:17:40

+0

無論如何,使用'n&(n-1)'bithack清除最低設置位將比BSF/SHR更好。使用'inc ecx'/lea edx,[rax-1]'/'和eax,edx' /'jnz loop_bit_count'(如果初始eax = 0跳過循環,或者無分支地將初始ecx設置爲-1,如果輸入爲零)。或者在一個設置ZF的指令中使用BMI1'blsr'來執行'n&(n-1)'。 – 2018-02-18 15:21:42

+0

**但是如果您關心優化**,非循環實現幾乎肯定是最好的選擇,因爲分支預測失誤會導致數據相關分支性能下降,除非模式非常可預測。 (你的答案的全部想法是循環'popcnt(n)'次,而不是固定的32次。)[包含乘法移位的bithack,它們屬於](https://stackoverflow.com/questions/) 109023/how-to-set-number-of-set-bits-in-a-32-bit-integer)非常好,可以在x86 asm中高效地實現(如果需要,可以通過編譯器)。 – 2018-02-18 15:25:04

相關問題