在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指令?
Digital Trauma'3

1
结果应该是整数吗?如果是这样,应如何取整?
Digital Trauma'3

3
所以输入和输出都是整数,是吗?但是输入可能高达10 ^ 12,因此对于32位int来说太大了。那么,我们是否假设使用64位整数输入?
Paul R

3
获胜标准是基于最大周期数还是平均周期数?如果是平均值,投入的分布是什么?
Runer112

2
针对哪个处理器?链接的文档列出了20多个不同的进程(AMD,Intel,其他进程),并且延迟有很大的不同。
Digital Trauma'3

Answers:


8

7个周期,恒定时间

这是基于我对此SO问题的回答的解决方案。它使用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

在ubuntu的GCC 4.6.3上编译并在退出代码中返回值。

我不确定为任何现代处理器解释该周期表,但是使用@DigitalTrauma的方法,在Nehalim处理器上,我得到了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的作品。类似的想法。使用他的计数方法,我认为BSR,MOV,CMP,SBB的
比率

是的,我想你的击败了我。只是表明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

用mov替换movzx eax。eax的前24位将已经为零,因此zx是多余的(而且价格昂贵)。
彼得·费里

6

最佳情况8个循环,最坏情况12个循环

由于问题尚不清楚,因此我将其基于“常春藤桥”延迟。

这里的方法是将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并相应地设置输出幅度。

除最坏情况的路径外,我们还有一条附加的mov指令来加载64位立即数以供在中使用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 */

这主要是由gcc汇编程序输出生成的,用于我编写的概念验证C代码。请注意,C代码使用可计算的goto来实现跳转表。它还使用__builtin_clzll()gcc内置函数,该函数可编译为bsr指令(加上xor)。


在达到这一目的之前,我考虑了几种解决方案:

  • FYL2X计算自然对数,然后FMUL通过必要的常数。如果这是一次[tag:instruction:golf]竞赛,那么大概可以赢得比赛。但是FYL2X对于常春藤桥,延迟时间为90-106。

  • 硬编码的二进制搜索。这实际上可能具有竞争力-我将其留给其他人实施:)。

  • 完成结果查询表。我敢肯定,这在理论上是最快的,但是如果摩尔定律继续保持下去,可能需要几年才能使用1TB的查找表-尚不实用。


如有必要,我可以计算所有允许输入的平均延迟。
Digital Trauma'3

jmp而且jcc没有延迟,只有吞吐量成本。分支预测+投机执行意味着控制依赖项不是数据依赖项链的一部分。
彼得·科德斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.