i386 (x86-32) makine kodu, 8 bayt (imzasızlar için 9B)
+ 1B b = 0girişi ele almamız gerekiyorsa .
amd64 (x86-64) makine kodu, 9 bayt (imzasızlar için 10B veya imzalı veya imzasız 64b tamsayılar için 14B 13B)
10 = 9B giriş veya 0 ile kesen amd64'te işaretsiz olanlar için
Girişler 32bit sıfırdan farklıdır imzalı tamsayılar eaxve ecx. Çıktı eax.
## 32bit code, signed integers: eax, ecx
08048420 <gcd0>:
8048420: 99 cdq ; shorter than xor edx,edx
8048421: f7 f9 idiv ecx
8048423: 92 xchg edx,eax ; there's a one-byte encoding for xchg eax,r32. So this is shorter but slower than a mov
8048424: 91 xchg ecx,eax ; eax = divisor(from ecx), ecx = remainder(from edx), edx = quotient(from eax) which we discard
; loop entry point if we need to handle ecx = 0
8048425: 41 inc ecx ; saves 1B vs. test/jnz in 32bit mode
8048426: e2 f8 loop 8048420 <gcd0>
08048428 <gcd0_end>:
; 8B total
; result in eax: gcd(a,0) = a
Bu döngü yapısı, test durumunda başarısız olur ecx = 0. ( divBir neden #DEsıfıra bölme donanım Execption. (Linux'ta, çekirdek bir teslim SIGFPE)) (kayan nokta istisna. Döngü giriş noktası hemen önce olsaydı inc, biz sorunu önleyeceğini. X86-64 versiyonu halledebilirim ücretsiz, aşağıya bakınız.
Mike Shlanta'nın cevabı bunun başlangıç noktasıydı . cdqDöngünüm onunla aynı şeyi yapıyor, ancak işaretli tamsayılar için çünkü ondan daha kısa bir xor edx,edx. Ve evet, negatif girişlerden biri veya ikisi ile doğru çalışıyor. Mike'ın sürümü daha hızlı çalışacak ve uop önbelleğinde daha az yer kaplayacak ( xchgIntel CPU'larda 3 ay ve loopçoğu CPU'da gerçekten yavaş ), ancak bu sürüm makine kodu boyutunda kazanacaktır.
İlk başta sorunun imzasız 32 bit gerektirdiğini fark etmedim . xor edx,edxBunun yerine geri gitmek cdqbir bayt mal olacak. divaynı boyuttadır idivve diğer her şey aynı kalabilir ( xchgveri hareketi ve inc/loophala çalışmak için).
İlginç bir şekilde, 64bit işlemsel boyutu ( raxve rcx) için, imzalı ve imzasız sürümleri aynı boyuttadır. İmzalı sürüm cqo(2B) için bir REX ön ekine ihtiyaç duyar , ancak imzasız sürüm hala 2B kullanabilir xor edx,edx.
64bit kodunda inc ecx2B'dir: tek bayt inc r32ve dec r32opkodlar REX önekleri olarak değiştirildi. inc/loop64bit modunda herhangi bir kod boyutunu kaydetmez, bu yüzden de olabilir test/jnz. 64bit tamsayılarda çalıştırma, REX öneklerinde, loopveya dışında bir komut başına bir bayt ekler jnz. Kalanın tümünün sıfırın 32b'de (örn. gcd((2^32), (2^32 + 1))) Olması mümkündür, bu nedenle tüm rcx'i test etmemiz gerekir ve bir bayttan tasarruf edemeyiz test ecx,ecx. Ancak, yavaş jrcxzinsn sadece 2B'dir ve ecx=0girişte işlemek için onu döngünün en üstüne koyabiliriz :
## 64bit code, unsigned 64 integers: rax, rcx
0000000000400630 <gcd_u64>:
400630: e3 0b jrcxz 40063d <gcd_u64_end> ; handles rcx=0 on input, and smaller than test rcx,rcx/jnz
400632: 31 d2 xor edx,edx ; same length as cqo
400634: 48 f7 f1 div rcx ; REX prefixes needed on three insns
400637: 48 92 xchg rdx,rax
400639: 48 91 xchg rcx,rax
40063b: eb f3 jmp 400630 <gcd_u64>
000000000040063d <gcd_u64_end>:
## 0xD = 13 bytes of code
## result in rax: gcd(a,0) = a
32 ve 64b versiyonları için Godbolt Compiler Explorer'da kaynak ve asm çıktısınımain çalıştıran bir program içeren tam çalıştırılabilir test programı . 32bit ( ), 64bit ( ) ve x32 ABI ( ) için test edildi ve çalışıyor .printf("...", gcd(atoi(argv[1]), atoi(argv[2])) ); -m32-m64-mx32
Ayrıca dahil: yalnızca tekrarlanan çıkarma işlemini kullanan , x86-64 modunda bile imzasızlar için 9B olan ve rastgele bir sicildeki girişlerinden birini alabilen bir sürüm . Bununla birlikte, girişte 0 olan her iki girişi de kaldıramaz ( subsıfır ürettiğinde algılar , ki bu x - 0 asla yapmaz).
32bit sürümü için GNU C satır içi asm kaynağı (derleme ile gcc -m32 -masm=intel)
int gcd(int a, int b) {
asm (// ".intel_syntax noprefix\n"
// "jmp .Lentry%=\n" // Uncomment to handle div-by-zero, by entering the loop in the middle. Better: `jecxz / jmp` loop structure like the 64b version
".p2align 4\n" // align to make size-counting easier
"gcd0: cdq\n\t" // sign extend eax into edx:eax. One byte shorter than xor edx,edx
" idiv ecx\n"
" xchg eax, edx\n" // there's a one-byte encoding for xchg eax,r32. So this is shorter but slower than a mov
" xchg eax, ecx\n" // eax = divisor(ecx), ecx = remainder(edx), edx = garbage that we will clear later
".Lentry%=:\n"
" inc ecx\n" // saves 1B vs. test/jnz in 32bit mode, none in 64b mode
" loop gcd0\n"
"gcd0_end:\n"
: /* outputs */ "+a" (a), "+c"(b)
: /* inputs */ // given as read-write outputs
: /* clobbers */ "edx"
);
return a;
}
Normalde bir fonksiyonun tamamını asm olarak yazardım, ama GNU C satır içi asm, hangi regs'de olursa olsun giriş / çıkış yapabilen bir snippet'i dahil etmenin en iyi yolu gibi görünüyor. Gördüğünüz gibi GNU C satır içi asm sözdizimi çirkin ve gürültülü yapar. Aynı zamanda bir var gerçekten zor yolu öğrenmek asm .
Aslında, derleme ve .att_syntax noprefixmodda çalışacaktı , çünkü kullanılan tüm insns ya tek / operand değil xchg. Gerçekten faydalı bir gözlem değil.