【问题标题】:How can I prevent functions from being aligned to 16 bytes boundary when compiling for X86?在为 X86 编译时,如何防止函数与 16 字节边界对齐?
【发布时间】:2017-12-21 12:02:28
【问题描述】:

我在类似嵌入式的环境中工作,其中每个字节都非常宝贵,比未对齐访问的额外周期更重要。我有一些来自操作系统开发示例的简单 Rust 代码:

#![feature(lang_items)]
#![no_std]
extern crate rlibc;
#[no_mangle]
pub extern fn rust_main() {

    // ATTENTION: we have a very small stack and no guard page

    let hello = b"Hello World!";
    let color_byte = 0x1f; // white foreground, blue background

    let mut hello_colored = [color_byte; 24];
    for (i, char_byte) in hello.into_iter().enumerate() {
        hello_colored[i*2] = *char_byte;
    }

    // write `Hello World!` to the center of the VGA text buffer
    let buffer_ptr = (0xb8000 + 1988) as *mut _;
    unsafe { *buffer_ptr = hello_colored };

    loop{}

}

#[lang = "eh_personality"] extern fn eh_personality() {}
#[lang = "panic_fmt"] #[no_mangle] pub extern fn panic_fmt() -> ! {loop{}}

我也使用这个链接器脚本:

OUTPUT_FORMAT("binary")
ENTRY(rust_main)
phys = 0x0000;
SECTIONS
{
  .text phys : AT(phys) {
    code = .;
    *(.text.start);
    *(.text*)
    *(.rodata)
    . = ALIGN(4);
  }
  __text_end=.;
  .data : AT(phys + (data - code))
  {
    data = .;
    *(.data)
    . = ALIGN(4);
  }
  __data_end=.;
  .bss : AT(phys + (bss - code))
  {
    bss = .;
    *(.bss)
    . = ALIGN(4);
  }
  __binary_end = .;
}

我使用opt-level: 3 和LTO 优化它,使用i586 目标编译器和GNU ld 链接器,包括链接器命令中的-O3。我还在链接器上尝试了opt-level: z 和耦合的-Os,但这导致代码更大(它没有展开循环)。就目前而言,opt-level: 3 的大小似乎相当合理。

在将函数与某个边界对齐时似乎浪费了很多字节。在展开循环之后,插入了 7 个nop 指令,然后按预期出现了一个无限循环。在此之后,似乎有另一个无限循环,前面有 7 个 16 位覆盖 nop 指令(即 xchg ax,ax 而不是 xchg eax,eax)。这在一个 196 字节的平面二进制文件中总共浪费了大约 26 个字节。

  • 优化器到底在做什么?
  • 我有哪些选项可以禁用它?
  • 为什么二进制文件中包含无法访问的代码?

完整的组装清单如下:

   0:   c6 05 c4 87 0b 00 48    movb   $0x48,0xb87c4
   7:   c6 05 c5 87 0b 00 1f    movb   $0x1f,0xb87c5
   e:   c6 05 c6 87 0b 00 65    movb   $0x65,0xb87c6
  15:   c6 05 c7 87 0b 00 1f    movb   $0x1f,0xb87c7
  1c:   c6 05 c8 87 0b 00 6c    movb   $0x6c,0xb87c8
  23:   c6 05 c9 87 0b 00 1f    movb   $0x1f,0xb87c9
  2a:   c6 05 ca 87 0b 00 6c    movb   $0x6c,0xb87ca
  31:   c6 05 cb 87 0b 00 1f    movb   $0x1f,0xb87cb
  38:   c6 05 cc 87 0b 00 6f    movb   $0x6f,0xb87cc
  3f:   c6 05 cd 87 0b 00 1f    movb   $0x1f,0xb87cd
  46:   c6 05 ce 87 0b 00 20    movb   $0x20,0xb87ce
  4d:   c6 05 cf 87 0b 00 1f    movb   $0x1f,0xb87cf
  54:   c6 05 d0 87 0b 00 57    movb   $0x57,0xb87d0
  5b:   c6 05 d1 87 0b 00 1f    movb   $0x1f,0xb87d1
  62:   c6 05 d2 87 0b 00 6f    movb   $0x6f,0xb87d2
  69:   c6 05 d3 87 0b 00 1f    movb   $0x1f,0xb87d3
  70:   c6 05 d4 87 0b 00 72    movb   $0x72,0xb87d4
  77:   c6 05 d5 87 0b 00 1f    movb   $0x1f,0xb87d5
  7e:   c6 05 d6 87 0b 00 6c    movb   $0x6c,0xb87d6
  85:   c6 05 d7 87 0b 00 1f    movb   $0x1f,0xb87d7
  8c:   c6 05 d8 87 0b 00 64    movb   $0x64,0xb87d8
  93:   c6 05 d9 87 0b 00 1f    movb   $0x1f,0xb87d9
  9a:   c6 05 da 87 0b 00 21    movb   $0x21,0xb87da
  a1:   c6 05 db 87 0b 00 1f    movb   $0x1f,0xb87db
  a8:   90                      nop
  a9:   90                      nop
  aa:   90                      nop
  ab:   90                      nop
  ac:   90                      nop
  ad:   90                      nop
  ae:   90                      nop
  af:   90                      nop
  b0:   eb fe                   jmp    0xb0
  b2:   66 90                   xchg   %ax,%ax
  b4:   66 90                   xchg   %ax,%ax
  b6:   66 90                   xchg   %ax,%ax
  b8:   66 90                   xchg   %ax,%ax
  ba:   66 90                   xchg   %ax,%ax
  bc:   66 90                   xchg   %ax,%ax
  be:   66 90                   xchg   %ax,%ax
  c0:   eb fe                   jmp    0xc0
  c2:   66 90                   xchg   %ax,%ax

【问题讨论】:

  • 我不知道Rust,但是反汇编中的第二个无限循环可能是最后你的源代码中的第二个无限循环。为循环分支目标提供 16 字节对齐是一种非常常见的性能优化,尽管显然无限循环的性能可能并不重要。
  • 尝试将-C llvm-args=-align-all-blocks=1 添加到rustc 选项中。
  • pub extern panic_fmt() 的代码包含在二进制文件中可能是因为您将其声明为导出的公共函数或因为您 didn't declared panic_fmt correcly。我目前无法构建您的代码,因此无法验证这一点。
  • 你确定你没有为小事出汗吗?这里的 26 字节可能是整个占用空间的 13%,但对于非平凡的应用程序来说,这不太可能扩展——也就是说,它将远小于 13%。什么是“嵌入式”?并非所有的嵌入式系统都是资源受限的;如果以 i586(通常具有大型 SDRAM)为目标,那么字节对齐是否真的会成为一个重要示例中的一个重要问题?
  • @Clifford 我什至会说问题至少应该是三个——“为什么这里对齐”、“我如何删除对齐”、“为什么包含其他代码” .我希望 25K+ 代表用户会更好一些:-(。

标签: assembly optimization x86 rust embedded


【解决方案1】:

作为Ross states,将函数和分支点对齐到 16 字节是 Intel 推荐的常见 x86 优化,尽管它有时可能效率较低,例如在您的情况下。对于编译器来说,最佳地决定是否对齐是一个难题,我相信 LLVM 只是选择始终对齐。 See more info on Performance optimisations of x86-64 assembly - Alignment and branch prediction.

正如red75prime's comment hints(但没有解释),LLVM 使用align-all-blocks 的值作为分支点的字节对齐,因此将其设置为 1 将禁用对齐。请注意,这适用于全球,建议使用比较基准。

【讨论】:

  • 一年后回到这个... align-all-functions=1 实际上会将所有函数对齐在 2 字节边界上。同时,align-all-functions=0 将使用平台默认值(仅对齐某些函数,但将它们对齐到 16 或 32 字节边界)。对于我的用例来说,大小比性能重要得多
猜你喜欢
  • 1970-01-01
  • 2020-03-26
  • 2012-04-30
  • 1970-01-01
  • 2016-12-19
  • 1970-01-01
  • 1970-01-01
  • 2021-04-15
  • 1970-01-01
相关资源
最近更新 更多