编译器优化:将变量从栈移动到寄存器

4
这里是代码:

#include <cstring>
#include <cstdint>
#include <cstddef>

uint64_t uint5korr(const std::byte *p)
{
  uint64_t result= 0;
  std::memcpy(&result, p, 5);
  return result;
}

https://godbolt.org/z/vULPAZ

这里的clang将result优化为寄存器,而gcc没有。我怀疑这可能与我取变量地址有关,因为不能取寄存器的地址。

这是gcc中缺少的一种优化,还是clang在某种程度上违反了标准?


1
很明显,如果不进行基准测试,在这里将变量放在堆栈上会更慢。基本上,我希望gcc能够优化这段代码,我想知道这样做是否正确。 - Eugene Kosov
1
我相信标准中没有关于它们如何进行优化的要求,只要它们能正常工作。但是我没有任何引用来支持我的说法。正在寻找。 - Kenny Ostrom
1
除非这段代码被运行在一个非常紧密的循环中,每秒数十万次以上,否则我怀疑你永远无法测量到任何有意义的性能差异。那么,为什么要在意呢? - Jesper Juhl
1
最好点击克隆编译器按钮,这样我们可以在同一个窗口中比较版本。 - phuclv
1
为什么你只复制了5个字节?我还没有完全按照标准检查过这个问题,但是在我看来,这很可能是未定义的行为。std::uint64_t是一个平凡可复制的类型,但是你在这里并没有复制整个对象表示... - Michael Kenzel
显示剩余10条评论
2个回答

1

是的,这种优化是合法的。从正确的地址读取了5个字节(不是8个);没有必要再将它们存储一遍,只为了进行return、是否取地址等操作时再次读取。我和Michael Kenzel一样对此持怀疑态度,但这只能巩固该优化的有效性。


uint64_t 保证是一个没有填充位的固定宽度为 64 位的类型。其对象表示是纯二进制位值。写入其对象表示的低五个字节不是未定义行为,但字节序是由实现依赖的。也就是说,在任何给定的实现中,它必须对所有调用者保持一致。 - Peter Cordes

1

这不是一个的回答。

虽然GCC本身似乎确实缺少优化,但是如果使用部分memcpy的值,则是未定义行为。我会向GCC提交错误报告,以获得关于该主题的明确回应。

GCC/Clang/MSVC完美优化的加载40位宽整数的方法:

std::uint64_t load_u40(const std::byte *p)
{
  std::uint8_t lo = 0;
  std::memcpy(&lo, p, 1);
  std::uint32_t hi = 0;
  std::memcpy(&hi, p + 1, 4);
  return (static_cast<std::uint64_t>(hi) << 8) | lo;
}

https://godbolt.org/z/4Kk9IM


这怎么成为GCC的错误了? - aschepler
谢谢您的回答!这是我在 GCC 缺陷追踪器上关于此主题的工单链接:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89804 - Eugene Kosov
1
@aschepler,错失优化机会/生成次优代码被视为一个错误。https://gcc.gnu.org/bugzilla/buglist.cgi?keywords=missed-optimization - Nikita Kniazev
写入对象表示的一部分不是未定义行为。字节序是实现定义的;依赖它的代码可能不可移植,但这不是未定义行为。此外,uint64_t是一个固定宽度类型,保证没有填充位或陷阱表示;这也适用于unsigned long,但如果您使用该值,则可能会比预期更奇怪。请注意,您的版本也受字节序影响。 - Peter Cordes
@PeterCordes 我并没有说在对象的一部分写入数据是未定义行为,但是在此之后读取对象是不被标准所定义的。basic.types/2 只涵盖了整个对象(包括填充)的复制。一个人可能会说它被basic.types/4所覆盖,但我并不信服,并且实现定义的行为并不好,因为它可能因编译器而异。评论中对字节顺序敏感性提出了质疑,并由作者标记为不是问题。 - Nikita Kniazev
UB 使其不能安全地在 任何 特定实现上使用。如果编写非可移植代码,实现定义意味着您可以在测试后使用它来查看它在您关心的一个实现上做了什么(例如嵌入式或标准库实现)。极大不同。我相当确定除了字节顺序之外,对于 uint64_t 来说它是明确定义的,这个类型保证没有填充位。如果实现没有以这种方式工作的类型,则它是一种可选类型,实现不必定义它。相当确定这个观点是保证对象表示布局。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接