i386 (x86-32) makine kodu, 8 bayt (imzasızlar için 9B)
+ 1B b = 0
giriş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 eax
ve 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
. ( div
Bir neden #DE
sı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ı . cdq
Dö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 ( xchg
Intel 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,edx
Bunun yerine geri gitmek cdq
bir bayt mal olacak. div
aynı boyuttadır idiv
ve diğer her şey aynı kalabilir ( xchg
veri hareketi ve inc/loop
hala çalışmak için).
İlginç bir şekilde, 64bit işlemsel boyutu ( rax
ve 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 ecx
2B'dir: tek bayt inc r32
ve dec r32
opkodlar REX önekleri olarak değiştirildi. inc/loop
64bit modunda herhangi bir kod boyutunu kaydetmez, bu yüzden de olabilir test/jnz
. 64bit tamsayılarda çalıştırma, REX öneklerinde, loop
veya 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ş jrcxz
insn sadece 2B'dir ve ecx=0
giriş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 ( sub
sı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 noprefix
modda çalışacaktı , çünkü kullanılan tüm insns ya tek / operand değil xchg
. Gerçekten faydalı bir gözlem değil.