i386 (x86-32) 기계 코드, 8 바이트 (부호없는 경우 9B)
b = 0
입력 을 처리해야 할 경우 + 1B
amd64 (x86-64) 머신 코드, 9 바이트 (부호없는 경우 10B, 부호있는 또는 부호없는 64b 정수의 경우 14B 13B)
amd64에서 부호없는 10 9B (입력 = 0)
입력은 32 비트 비 제로인 서명 의 정수 eax
와 ecx
. 에서 출력 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
이 루프 구조는 테스트 케이스 where에 실패합니다 ecx = 0
. ( 0으로 나누면 하드웨어 실행이 div
발생합니다 #DE
. (Linux에서는 커널이 SIGFPE
(부동 소수점 예외)를 전달합니다 .) 루프 진입 점이 바로 앞에 있으면 inc
문제를 피할 수 있습니다. x86-64 버전은이를 처리 할 수 있습니다. 무료로 아래를 참조하십시오.
Mike Shlanta의 대답은 이것의 출발점이었습니다 . 내 루프는 그의 것과 같은 일을하지만 부호있는 정수의 cdq
경우 1보다 짧습니다 xor edx,edx
. 그리고 네, 하나 또는 두 개의 입력 모두 음수로 올바르게 작동합니다. Mike의 버전은 uop 캐시에서 더 빠르게 실행되고 더 적은 공간을 차지합니다 ( xchg
Intel CPU에서는 3 개의 uops이며 loop
대부분의 CPU에서는 실제로 느립니다 ). 그러나이 버전은 머신 코드 크기에서 승리합니다.
처음에는 질문에 부호없는 32 비트가 필요하다는 것을 알지 못했습니다 . xor edx,edx
대신에 돌아 가면 cdq
1 바이트가 필요합니다. div
와 크기가 같고 idiv
다른 모든 xchg
데이터 는 동일하게 유지 될 수 있습니다 ( 데이터 이동 및 inc/loop
여전히 작동).
흥미롭게도 64 비트 피연산자 크기 ( rax
및 rcx
)의 경우 부호있는 버전과 부호없는 버전의 크기가 같습니다. 서명 된 버전에는 cqo
(2B)에 대한 REX 접두사가 필요 하지만 서명되지 않은 버전은 여전히 2B를 사용할 수 있습니다 xor edx,edx
.
64 비트 코드에서 inc ecx
2B는 1 바이트 inc r32
이며 dec r32
opcode는 REX 접두어로 용도 변경되었습니다. inc/loop
64 비트 모드에서 코드 크기를 저장하지 않으므로 test/jnz
. 64 비트 정수에 작동하는 것은 제외하고, REX 접두사의 지시에 따라 다른 바이트를 추가 loop
하거나 jnz
. 나머지가 낮은 32b에서 모두 0을 가질 수 gcd((2^32), (2^32 + 1))
있으므로 (예를 들어 ) 전체 rcx를 테스트해야하고로 바이트를 저장할 수 없습니다 test ecx,ecx
. 그러나 느린 jrcxz
insn은 2B에 불과 하며 입력 을 처리하기 위해 루프 상단에 배치 할 수 있습니다ecx=0
.
## 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 및 64b 버전의 경우 Godbolt Compiler Explorer에서 소스 및 asm 출력main
을 실행 하는 전체 실행 가능 테스트 프로그램 . 테스트 및 32 비트에 대한 (일 ), 64 비트를 ( ) 및 X32 ABI ( ) .printf("...", gcd(atoi(argv[1]), atoi(argv[2])) );
-m32
-m64
-mx32
x86-64 모드에서도 부호없는 9B이며 임의의 레지스터에서 입력 중 하나를 취할 수있는 반복 뺄셈 만 사용하는 버전도 포함 됩니다 . 그러나 입력시 입력이 0 인 것을 처리 할 수 없습니다 ( sub
x-0이 절대로하지 않는 0을 생성 할 때 감지 ).
32 비트 버전 용 GNU C 인라인 asm 소스 (로 컴파일 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;
}
일반적으로 asm으로 전체 함수를 작성하지만 GNU C 인라인 asm은 선택한 regs에 입 / 출력을 가질 수있는 스 니펫을 포함하는 가장 좋은 방법 인 것 같습니다. 보다시피, GNU C 인라인 asm 구문은 어색하고 시끄 럽습니다. 또한 asm 을 배우는 것은 정말 어려운 방법 입니다.
.att_syntax noprefix
사용 된 모든 insns가 single / no operand 또는이므로 실제로 컴파일하고 모드에서 작동 합니다 xchg
. 실제로 유용한 관찰은 아닙니다.