当前位置: 首页 > 知识库问答 >
问题:

GNU内联程序集优化

楚嘉玉
2023-03-14

我正在尝试为高度优化的x86-64位操作代码编写一个小型库,并且正在摆弄内联ASM。

unsigned long test = 0;
unsigned long bsr;

// bit test and set 39th bit
__asm__ ("btsq\t%1, %0 " : "+rm" (test) : "rJ" (39) );

// bit scan reverse (get most significant bit id)
__asm__ ("bsrq\t%1, %0" : "=r" (bsr) : "rm" (test) );

printf("test = %lu, bsr = %d\n", test, bsr);

在gcc和icc中编译和运行都很好,但是当我检查程序集时,我发现了差异

gcc-s-fverbose-asm-std=gnu99-o3

movq    $0, -8(%rbp)
## InlineAsm Start
btsq    $39, -8(%rbp) 
## InlineAsm End
movq    -8(%rbp), %rax
movq    %rax, -16(%rbp)
## InlineAsm Start
bsrq    -16(%rbp), %rdx
## InlineAsm End
movq    -8(%rbp), %rsi
leaq    L_.str(%rip), %rdi
xorb    %al, %al
callq   _printf

我在想为什么这么复杂?我正在编写高性能代码,其中指令的数量是关键的。我特别想知道为什么gcc在将变量test传递给第二个内联ASM之前会对它进行复制?

    xorl      %esi, %esi                                    # test = 0
    movl      $.L_2__STRING.0, %edi                         # has something to do with printf
    orl       $32832, (%rsp)                                # part of function initiation
    xorl      %eax, %eax                                    # has something to do with printf
    ldmxcsr   (%rsp)                                        # part of function initiation
    btsq      $39, %rsi                                     #106.0
    bsrq      %rsi, %rdx                                    #109.0
    call      printf                                        #111.2

尽管gcc决定将变量保存在堆栈中,而不是寄存器中,但我不明白的是,为什么要在将test传递给第二个ASM之前复制它呢?如果我在第二个asm中将test作为输入/输出变量放入

__asm__(“bsrq\t%1,%0”:“=r”(bsr),“+rm”(测试));

然后那些线就消失了。

movq    $0, -8(%rbp)
## InlineAsm Start
btsq    $39, -8(%rbp) 
## InlineAsm End
## InlineAsm Start
bsrq    -8(%rbp), %rdx
## InlineAsm End
movq    -8(%rbp), %rsi
leaq    L_.str(%rip), %rdi
xorb    %al, %al
callq   _printf

icc版本12.0.2

共有1个答案

毛德曜
2023-03-14

我在Linux上尝试了这样的示例(通过在printf中使用&test强制test的堆栈ref/loc,使其成为“邪恶”):

#include <stdio.h>
int main(int argc, char **argv)
{
    unsigned long test = 0;
    unsigned long bsr;
// bit test and set 39th bit
    asm ("btsq\t%1, %0 " : "+rm" (test) : "rJ" (39) );
// bit scan reverse (get most significant bit id)
    asm ("bsrq\t%1, %0" : "=r" (bsr) : "rm" (test) );
    printf("test = %lu, bsr = %d, &test = %p\n", test, bsr, &test);
    return 0;
}
code generated                                                     gcc version
================================================================================
  400630:       48 83 ec 18             sub    $0x18,%rsp          4.7.2,
  400634:       31 c0                   xor    %eax,%eax           4.6.2,
  400636:       bf 50 07 40 00          mov    $0x400750,%edi      4.4.6
  40063b:       48 8d 4c 24 08          lea    0x8(%rsp),%rcx
  400640:       48 0f ba e8 27          bts    $0x27,%rax
  400645:       48 89 44 24 08          mov    %rax,0x8(%rsp)
  40064a:       48 89 c6                mov    %rax,%rsi
  40064d:       48 0f bd d0             bsr    %rax,%rdx
  400651:       31 c0                   xor    %eax,%eax
  400653:       e8 68 fe ff ff          callq  4004c0 
[ ... ]
---------------------------------------------------------------------------------
  4004f0:       48 83 ec 18             sub    $0x18,%rsp          4.1
  4004f4:       31 c0                   xor    %eax,%eax
  4004f6:       bf 28 06 40 00          mov    $0x400628,%edi
  4004fb:       48 8d 4c 24 10          lea    0x10(%rsp),%rcx
  400500:       48 c7 44 24 10 00 00 00 00      movq   $0x0,0x10(%rsp)
  400509:       48 0f ba e8 27          bts    $0x27,%rax
  40050e:       48 89 44 24 10          mov    %rax,0x10(%rsp)
  400513:       48 89 c6                mov    %rax,%rsi
  400516:       48 0f bd d0             bsr    %rax,%rdx
  40051a:       31 c0                   xor    %eax,%eax
  40051c:       e8 c7 fe ff ff          callq  4003e8 
[ ... ]
---------------------------------------------------------------------------------
  400500:       48 83 ec 08             sub    $0x8,%rsp           3.4.5
  400504:       bf 30 06 40 00          mov    $0x400630,%edi
  400509:       31 c0                   xor    %eax,%eax
  40050b:       48 c7 04 24 00 00 00 00         movq   $0x0,(%rsp)
  400513:       48 89 e1                mov    %rsp,%rcx
  400516:       48 0f ba 2c 24 27       btsq   $0x27,(%rsp)
  40051c:       48 8b 34 24             mov    (%rsp),%rsi
  400520:       48 0f bd 14 24          bsr    (%rsp),%rdx
  400525:       e8 fe fe ff ff          callq  400428 
[ ... ]
---------------------------------------------------------------------------------
  4004e0:       48 83 ec 08             sub    $0x8,%rsp           3.2.3
  4004e4:       bf 10 06 40 00          mov    $0x400610,%edi
  4004e9:       31 c0                   xor    %eax,%eax
  4004eb:       48 c7 04 24 00 00 00 00         movq   $0x0,(%rsp)
  4004f3:       48 0f ba 2c 24 27       btsq   $0x27,(%rsp)
  4004f9:       48 8b 34 24             mov    (%rsp),%rsi
  4004fd:       48 89 e1                mov    %rsp,%rcx
  400500:       48 0f bd 14 24          bsr    (%rsp),%rdx
  400505:       e8 ee fe ff ff          callq  4003f8 
[ ... ]

虽然创建的代码有很大的不同(包括bsr是将test作为寄存器还是作为内存访问),但是没有一个被测试的rev重新创建您所显示的程序集。我怀疑您在MacOSX上使用的4.2.x版本中存在一个bug,但我没有您的testcase或特定的编译器版本可用。

编辑:上面的代码明显不同,因为它强制test进入堆栈;如果没有做到这一点,那么我测试过的所有“普通”gcc版本都将执行直接对BTS$39,%RSI/BSR%RSI,%RDX

但是,我发现clang在其中创建了不同的代码:

 140:   50                      push   %rax
 141:   48 c7 04 24 00 00 00 00         movq   $0x0,(%rsp)
 149:   31 f6                   xor    %esi,%esi
 14b:   48 0f ba ee 27          bts    $0x27,%rsi
 150:   48 89 34 24             mov    %rsi,(%rsp)
 154:   48 0f bd d6             bsr    %rsi,%rdx
 158:   bf 00 00 00 00          mov    $0x0,%edi
 15d:   30 c0                   xor    %al,%al
 15f:   e8 00 00 00 00          callq  printf@plt>
 类似资料:
  • 这里是一个虚拟的*z++=*x++**y++指令。请注意,x、y和z指针寄存器必须指定为输入/输出,因为asm会修改它们。 在第一个示例中,在输入操作数中列出和有什么意义?同一份文件指出: 特别是,如果不将输入操作数指定为输出操作数,就无法指定输入操作数被修改。

  • 我必须做一个大学项目,我们必须使用缓存优化来提高给定代码的性能,但我们不能使用编译器优化来实现它。 我在阅读参考书目时的一个想法是将基本块的开头与行缓存大小对齐。但你能做一些类似的事情吗: 为了实现我的目标?我不知道在指令对齐方面是否有可能做到这一点。我见过一些技巧,如实现数据对齐的mm\U malloc,但没有针对指令的技巧。有人能告诉我这件事吗?

  • 问题内容: 我想知道JVM / javac是否足够聪明 进入 或在释放情况下剥离对foo()的不必要调用(因为代码无法到达): 对于第一个示例,我的感觉是肯定的,而对于第二个示例,我的感觉“不确定”,但是有人可以给我一些指针/链接来确认这一点吗? 问题答案: 将提供字节码,该字节码是生成该字节码的原始Java程序的忠实表示(在某些可以优化的特定情况下除外: 常量折叠 和 消除死代码 )。但是,当J

  • 假设我有以下 C 代码: 当< code>x == INT_MAX时,这是未定义的行为。现在假设我用内联汇编执行了加法: 问题:当< code>x == INT_MAX时,内联汇编版本是否仍然调用未定义的行为?还是未定义的行为只适用于C代码?

  • 说明 作为优秀的开发者,在日常编码中,应积极培养书写高执行效率代码的意识。不过项目运行效率是一个系统性工程,不应该只停留在代码层面上,有时更应该考虑整个项目架构,包括项目中使用的软件等。 本文罗列了一些常见的优化项目,并且对其做了约束。 1. 配置信息缓存 生产环境中的 应该 使用『配置信息缓存』来加速 Laravel 配置信息的读取。 使用以下 Artisan 自带命令,把 config 文件夹

  • 对于这种特定的情况,目标平台是一个ARM7系统,代码正在用GCC5.3.0进行编译。正在执行的系统调用具有与C函数调用相同的调用约定。经过一些尝试和错误,我得到了上面的“工作”,但我还不相信它是正确的,并且会一直工作,服从于优化编译器的奇思妙想。 我希望能够删除“内存”clobber,并确切地告诉GCC哪些内存将被修改,但是GCC扩展的Asm文档讨论了如何为特定寄存器赋值,然后是内存约束,但如果它