【问题标题】:About gcc-compiled x86_64 code and C code optimization关于 gcc 编译的 x86_64 代码和 C 代码优化
【发布时间】:2012-06-08 20:26:45
【问题描述】:

我编译了以下C代码:

typedef struct {
    long x, y, z;
} Foo;

long Bar(Foo *f, long i)
{
    return f[i].x + f[i].y + f[i].z;
}

使用命令gcc -S -O3 test.c。这是输出中的 Bar 函数:

    .section    __TEXT,__text,regular,pure_instructions
    .globl  _Bar
    .align  4, 0x90
_Bar:
Leh_func_begin1:
    pushq   %rbp
Ltmp0:
    movq    %rsp, %rbp
Ltmp1:
    leaq    (%rsi,%rsi,2), %rcx
    movq    8(%rdi,%rcx,8), %rax
    addq    (%rdi,%rcx,8), %rax
    addq    16(%rdi,%rcx,8), %rax
    popq    %rbp
    ret
Leh_func_end1:

我有几个关于这个汇编代码的问题:

  1. 如果函数体中既没有使用rbp,也没有使用rsp,那么“pushq %rbp”、“movq %rsp, %rbp”和“popq %rbp”的用途是什么?
  2. 为什么rsirdi 自动包含C 函数的参数(分别为if)而不从堆栈中读取它们?
  3. 我尝试将 Foo 的大小增加到 88 个字节(11 个longs)并且leaq 指令变成了imulq。将我的结构设计为具有“更圆”的大小以避免乘法指令(以优化数组访问)是否有意义? leaq 指令被替换为:

    imulq   $88, %rsi, %rcx
    

【问题讨论】:

    标签: c optimization gcc x86 x86-64


    【解决方案1】:
    1. 该函数只是使用这些指令构建自己的堆栈帧。他们并没有什么不寻常的地方。但是,您应该注意,由于此函数的大小很小,在代码中使用它时可能会被内联。但是,编译器总是需要生成函数的“正常”版本。另外,@ouah 在他的回答中所说的话。

    2. 这是因为这就是 AMD64 ABI 指定应将参数传递给函数的方式。

      如果类是INTEGER,则序列的下一个可用寄存器 使用了 %rdi、%rsi、%rdx、%rcx、%r8 和 %r9。

      第 20 页,AMD64 ABI 草案 0.99.5 – 2010 年 9 月 3 日

    3. 这与结构大小没有直接,而是 - 函数必须访问的绝对地址。如果结构的大小是 24 字节,f 是包含结构的数组的地址,i 是必须访问数组的索引,那么每个结构的字节偏移量是i*24 .在这种情况下,乘以 24 是通过 lea 和 SIB 寻址的组合来实现的。第一条lea 指令只计算i*3,然后每个后续指令都使用i*3 并将其进一步乘以8,因此以所需的绝对字节偏移量访问数组,然后使用立即位移来访问各个结构成员((%rdi,%rcx,8)8(%rdi,%rcx,8),和16(%rdi,%rcx,8))。如果您将结构的大小设置为 88 字节,那么结合 lea 和任何类型的寻址,根本无法快速完成这样的事情。编译器只是假设一个简单的imull 在计算i*88 时比一系列移位、加法、leas 或其他任何东西更有效。

    【讨论】:

    • 是的,我都知道。我的问题是是否值得用额外的空间填充结构以使其成为一个“更圆”的数字(如 12 个长而不是 11 个长),从而避免在计算数组索引时使用乘法?
    • @Matt:一般来说没有人能回答这个问题——填充也不是免费的(缓存大小);不要猜测,测量!
    【解决方案2】:
    3. I tried increasing the size of Foo to 88 bytes (11 longs) and the leaq instruction became an imulq. Would it make sense to design my structs to have "rounder" sizes to avoid the multiply instructions (in order to optimize array access)?
    

    leaq 调用(本质上在本 cae 中)计算 k*a+b,其中“k”是 1、2、4 或 8,“a”和“b”是寄存器。如果 "a" 和 "b" 相同,则可以用于 1、2、3、4、5、8 和 9 长的结构。

    像 16 longs 这样的大型结构可能可以通过计算“k”的偏移量并加倍来优化,但我不知道编译器是否会这样做;你必须测试。

    【讨论】:

    • 我用 12 个试了一下,它确实优化了它。 (“leaq (%rsi,%rsi,2), %rcx”然后是“shlq $5, %rcx”)但我的问题是值得将大小从 88 增加到 96 只是为了避免数组访问期间的乘法(假设我会做很多数组访问) .
    • 啊,对不起。如果内存不如性能重要,并且您可以确信 imul 将被避免,那么是的,我会的。 (在此处插入关于预优化和测试验证的标准免责声明。)
    【解决方案3】:
    1. 如果函数体中既没有使用 rbp 也没有使用 rsp,那么 pushq %rbp、movq %rsp、%rbp 和 popq %rbp 的用途是什么?

    在您使用调试器时跟踪帧。添加-fomit-frame-pointer 进行优化(注意它应该在-O3 启用,但在我使用的很多gcc 版本中没有启用它。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-08
      • 1970-01-01
      • 1970-01-01
      • 2014-09-09
      • 2013-03-11
      • 2011-12-16
      相关资源
      最近更新 更多