x86 어셈블리에서 크기를 계산하는 가장 빠른 방법


12

작업은 간단합니다. 가능한 적은 클럭 사이클을 사용하여 정수의 크기 순서를 계산하는 쓰기 어셈블리입니다.

  • 크기의 순서는로 정의 log10되지 않습니다 log2.
  • 유효 입력 범위는 다음 0에 , 포함. 해당 범위 밖의 입력에 대한 동작은 정의되어 있지 않습니다.1012
  • 주어진 입력을 제외하고 값은 가장 가까운 정수로 내림 0해야합니다 0. (이것은 부호없는 정수에서 가능한 음의 무한대의 가장 좋은 표현이라고 생각할 수 있습니다).
  • x86 어셈블리 여야합니다.
  • 정수는 정적 / 인라인 정수가 아닌 런타임 값 이어야합니다 . 따라서 컴파일 타임에 무엇인지 알 수 없습니다.
  • 레지스터에 이미로드 된 정수가 있다고 가정하십시오. (그러나 명확성을 위해 답변에 레지스터의 값을 설정하는 것을 포함하십시오).
  • 외부 라이브러리 나 함수를 호출 할 수 없습니다.
  • 인텔 문서 에서 사용 가능한 지침을 자유롭게 사용할 수 있습니다 .
  • 아니 C.
  • ~ 7 개의 Intel Core 아키텍처 중 하나를 사용할 수 있습니다 ( 10 페이지에 나열 됨 ). 이상적으로 Nehalem (Intel Core i7).

정답은 가능한 한 가장 적은 클럭 사이클을 사용하는 것입니다. 즉, 초당 가장 많은 작업을 수행 할 수 있습니다. 대략 클럭 사이클 요약 현재 위치 : http://www.agner.org/optimize/instruction_tables.pdf를 . 답변을 게시 한 후 클럭주기를 계산할 수 있습니다.


'FYL2X'및 기타 FPU 명령이 허용됩니까?
디지털 외상

1
결과는 정수 여야합니까? 그렇다면 어떻게 반올림해야합니까?
Digital Trauma

3
입력과 출력은 모두 정수입니다. 그러나 입력은 10 ^ 12만큼 클 수 있으므로 32 비트 int에는 너무 큽니다. 64 비트 정수 입력이라고 가정합니까?
Paul R

3
선정 기준이 최대 또는 평균주기 수를 기준으로합니까? 그리고 평균이라면 입력 분포는 무엇입니까?
Runer112

2
어떤 프로세서가 목표입니까? 링크 된 문서에는 20 개 이상의 서로 다른 프로세스 (AMD, Intel 및 기타)가 나열되어 있으며 대기 시간에 상당한 차이가 있습니다.
Digital Trauma

답변:


8

7주기, 일정한 시간

이 SO Question 에 대한 나의 대답을 바탕으로 한 해결책 있습니다. BSR을 사용하여 숫자를 보유하는 데 필요한 비트 수를 계산합니다. 많은 비트가 보유 할 수있는 가장 큰 숫자를 나타내는 데 필요한 소수 자릿수를 찾습니다. 그런 다음 실제 숫자가 10의 가장 가까운 거듭 제곱보다 작은 경우 1을 뺍니다.

    .intel_syntax noprefix
    .globl  main
main:
    mov rdi, 1000000000000              #;your value here
    bsr rax, rdi
    movzx   eax, BYTE PTR maxdigits[1+rax]
    cmp rdi, QWORD PTR powers[0+eax*8]
    sbb al, 0
    ret
maxdigits:
    .byte   0
    .byte   0
    .byte   0
    .byte   0
    .byte   1
    .byte   1
    .byte   1
    .byte   2
    .byte   2
    .byte   2
    .byte   3
    .byte   3
    .byte   3
    .byte   3
    .byte   4
    .byte   4
    .byte   4
    .byte   5
    .byte   5
    .byte   5
    .byte   6
    .byte   6
    .byte   6
    .byte   6
    .byte   7
    .byte   7
    .byte   7
    .byte   8
    .byte   8
    .byte   8
    .byte   9
    .byte   9
    .byte   9
    .byte   9
    .byte   10
    .byte   10
    .byte   10
    .byte   11
    .byte   11
    .byte   11
    .byte   12
powers:
    .quad   0
    .quad   10
    .quad   100
    .quad   1000
    .quad   10000
    .quad   100000
    .quad   1000000
    .quad   10000000
    .quad   100000000
    .quad   1000000000
    .quad   10000000000
    .quad   100000000000
    .quad   1000000000000

우분투를 위해 GCC 4.6.3에서 컴파일하고 종료 코드의 값을 반환합니다.

최신 프로세서의 사이클 테이블을 해석한다고 확신하지 못하지만 Nehalim 프로세서에서 @DigitalTrauma의 방법을 사용하면 7이됩니다 .

ins        uOps Latency
---        -    - 
BSR r,r  : 1    3
MOVZX r,m: 1    -
CMP m,r/i: 1    1 
SBB r,r/i: 2    2

오-내가 글을 쓰기 시작한 후 DigitalTrauma의 것을 보았습니다. 비슷한 아이디어. 자신의 계산 방법을 사용하여 생각 GET 3,1,1,1 = 6 BSR, MOV, CMP, SBB에 대한
AShelly

그렇습니다. a) 저는 어셈블리 프로그래머가 아닙니다. b) 어셈블리는 필사자들이 혼자 남겨 두는 것이 가장 좋습니다 ;-)
Digital Trauma

The integer must be a runtime value, not a static/inline integer. So we don't know what it is at compile time.
고양이

1
오른쪽에 다음 줄이 있습니다. "레지스터에 이미로드 된 정수가 있다고 가정합니다 (그러나 명확성을 위해 응답에 레지스터의 값 설정 포함)." 내가 한 일입니다.
AShelly

movzx eax를 mov al로 교체하십시오. eax의 상위 24 비트는 이미 0이므로 zx는 중복 적이며 비싸다.
피터 페리

6

최상의 경우 8주기, 최악의 경우 12주기

질문에 명확하지 않기 때문에 이것을 Ivy Bridge 대기 시간을 기반으로합니다.

여기서의 접근 방식은 bsr(비트 스캔 역) 명령을 가난한 사람의 log2 ()로 사용하는 것입니다. 결과는 비트 0에서 42까지의 항목을 포함하는 점프 테이블의 인덱스로 사용됩니다. 64 비트 데이터에 대한 작업이 암시 적으로 필요하다고 가정하면 bsr명령 사용이 정상입니다.

최상의 경우 입력 가능한 점프 테이블 항목은 bsr결과를 크기에 직접 매핑 할 수 있습니다 . 예를 들어 32-63 범위의 입력의 bsr경우 결과는 5이며 1의 크기로 매핑됩니다.이 경우 명령 경로는 다음과 같습니다.

Instruction    Latency

bsrq                 3
jmp                  2
movl                 1
jmp                  2

total                8

최악의 경우 입력 bsr결과는 두 가지 가능한 크기로 매핑되므로 점프 가능 항목은 cmp입력이> 10 n 인지 확인하기 위해 추가 로 하나 를 수행 합니다. 예를 들어 64-127 범위의 입력의 bsr경우 결과는 6입니다. 해당 점프 테이블 항목은 입력> 100인지 확인하고 그에 따라 출력 크기를 설정합니다.

최악의 경우 경로 외에도에 사용할 64 비트 즉시 값을로드하는 추가 mov 명령이 cmp있으므로 최악의 경우 경로는 다음과 같습니다.

Instruction    Latency

bsrq                 3
jmp                  2
movabsq              1
cmpq                 1
ja                   2
movl                 1
jmp                  2

total               12

코드는 다음과 같습니다.

    /* Input is loaded in %rdi */
    bsrq    %rdi, %rax
    jmp *jumptable(,%rax,8)
.m0:
    movl    $0, %ecx
    jmp .end
.m0_1:
    cmpq    $9, %rdi
    ja  .m1
    movl    $0, %ecx
    jmp .end
.m1:
    movl    $1, %ecx
    jmp .end
.m1_2:
    cmpq    $99, %rdi
    ja  .m2
    movl    $1, %ecx
    jmp .end
.m2:
    movl    $2, %ecx
    jmp .end
.m2_3:
    cmpq    $999, %rdi
    ja  .m3
    movl    $2, %ecx
    jmp .end
.m3:
    movl    $3, %ecx
    jmp .end
.m3_4:
    cmpq    $9999, %rdi
    ja  .m4
    movl    $3, %ecx
    jmp .end
.m4:
    movl    $4, %ecx
    jmp .end
.m4_5:
    cmpq    $99999, %rdi
    ja  .m5
    movl    $4, %ecx
    jmp .end
.m5:
    movl    $5, %ecx
    jmp .end
.m5_6:
    cmpq    $999999, %rdi
    ja  .m6
    movl    $5, %ecx
    jmp .end
.m6:
    movl    $6, %ecx
    jmp .end
.m6_7:
    cmpq    $9999999, %rdi
    ja  .m7
    movl    $6, %ecx
    jmp .end
.m7:
    movl    $7, %ecx
    jmp .end
.m7_8:
    cmpq    $99999999, %rdi
    ja  .m8
    movl    $7, %ecx
    jmp .end
.m8:
    movl    $8, %ecx
    jmp .end
.m8_9:
    cmpq    $999999999, %rdi
    ja  .m9
    movl    $8, %ecx
    jmp .end
.m9:
    movl    $9, %ecx
    jmp .end
.m9_10:
    movabsq $9999999999, %rax
    cmpq    %rax, %rdi
    ja  .m10
    movl    $9, %ecx
    jmp .end
.m10:
    movl    $10, %ecx
    jmp .end
.m10_11:
    movabsq $99999999999, %rax
    cmpq    %rax, %rdi
    ja  .m11
    movl    $10, %ecx
    jmp .end
.m11:
    movl    $11, %ecx
    jmp .end
.m11_12:
    movabsq $999999999999, %rax
    cmpq    %rax, %rdi
    ja  .m12
    movl    $11, %ecx
    jmp .end
.m12:
    movl    $12, %ecx
    jmp .end

jumptable:
    .quad   .m0
    .quad   .m0
    .quad   .m0
    .quad   .m0_1
    .quad   .m1
    .quad   .m1
    .quad   .m1_2
    .quad   .m2
    .quad   .m2
    .quad   .m2_3
    .quad   .m3
    .quad   .m3
    .quad   .m3
    .quad   .m3_4
    .quad   .m4
    .quad   .m4
    .quad   .m4_5
    .quad   .m5
    .quad   .m5
    .quad   .m5_6
    .quad   .m6
    .quad   .m6
    .quad   .m6
    .quad   .m6_7
    .quad   .m7
    .quad   .m7
    .quad   .m7_8
    .quad   .m8
    .quad   .m8
    .quad   .m8_9
    .quad   .m9
    .quad   .m9
    .quad   .m9
    .quad   .m9_10
    .quad   .m10
    .quad   .m10
    .quad   .m10_11
    .quad   .m11
    .quad   .m11
    .quad   .m11_12
    .quad   .m12
    .quad   .m12
    .quad   .m12

.end:
/* output is given in %ecx */

이것은 주로 내가 작성한 개념 증명 C 코드에 대한 gcc 어셈블러 출력에서 ​​생성되었습니다 . C 코드는 계산 가능한 goto를 사용하여 점프 테이블을 구현합니다. 또한 __builtin_clzll()gcc 내장을 사용하여 명령어로 컴파일됩니다 bsr(+ xor).


이 솔루션에 도달하기 전에 몇 가지 솔루션을 고려했습니다.

  • FYL2X자연 로그를 계산 한 다음 FMUL필요한 상수 로 계산합니다 . [tag : instruction : golf] 콘테스트 인 경우 아마도 이길 것입니다. 그러나 FYL2X아이비 브릿지의 대기 시간은 90-106입니다.

  • 하드 코딩 된 이진 검색. 이것은 실제로 경쟁적 일 수 있습니다-나는 그것을 구현하기 위해 다른 사람에게 맡길 것입니다 :).

  • 전체 검색 결과 표. 이것은 이론적으로 가장 빠르지 만 무어의 법칙이 계속 유지된다면 몇 년 안에 1TB 조회 테이블이 필요합니다.


필요한 경우 모든 허용 된 입력에 대한 평균 대기 시간을 계산할 수 있습니다.
Digital Trauma

jmp그리고 jcc단지 비용 처리량, 지연 시간이 없습니다. 분기 예측 + 추측 실행 평균 제어 종속성은 데이터 종속성 체인의 일부가 아닙니다.
Peter Cordes
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.