微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Google 基准框架 DoNotOptimize

如何解决Google 基准框架 DoNotOptimize

我对 Google Benchmark Framework (definition from here) 的函数 void DoNotOptimize 的实现有点困惑:

template <class Tp>
inline BENCHMARK_ALWAYS_INLINE void DoNotOptimize(Tp const& value) {
  asm volatile("" : : "r,m"(value) : "memory");
}

template <class Tp>
inline BENCHMARK_ALWAYS_INLINE void DoNotOptimize(Tp& value) {
#if defined(__clang__)
  asm volatile("" : "+r,m"(value) : : "memory");
#else
  asm volatile("" : "+m,r"(value) : : "memory");
#endif
}

所以它具体化了变量,如果是非常量的,也会告诉编译器忘记它之前的值。 ("+r" 是 RMW 操作数)。

而且总是使用 "memory" clobber,这是编译器阻止重新排序加载/存储的障碍,即确保所有全局可访问对象的内存与 C++ 抽象同步机器,并假设它们也可能已被修改


我离成为低级代码的专家还很远,但据我了解实现,该函数充当了读/写屏障。所以 - 基本上 - 它确保传入的值要么在寄存器中,要么在内存中。

虽然如果我想保留一个函数的结果(应该进行基准测试),这似乎是完全合理的,但我对留给编译器的自由度感到有些惊讶。

我对给定代码的理解是,每当调用 DoNotOptimize 时,编译器可能会插入一个具体化点,这意味着在重复执行时(例如,在循环中)会产生显着的开销。 当不应优化出的值只是单个标量值时,如果编译器确保该值驻留在寄存器中似乎就足够了。

例如区分指针和非指针不是一个好主意吗:

template< class T >
inline __attribute__((always_inline)) 
void do_not_optimize( T&& value ) noexcept {
    if constexpr( std::is_pointer_v< T > ) {
        asm volatile("":"+m"(value)::"memory");
    } else {
        asm volatile("":"+r"(value)::);
    }
}

解决方法

您想知道 "memory" 的破坏吗?是的,这可能会导致其他内容溢出,但有时这正是您想要在您尝试环绕重复循环的迭代之间的内容。

请注意,"memory" 破坏器不会影响无法从全局变量访问的对象。 (Escape analysis)。所以它不会导致 for(int i = ...) 中的循环计数器之类的东西被溢出/重新加载。

寄存器中实现指定变量的值(并且为了常量传播或 CSE 目的而忘记它的值)正是这个函数的重点,而且很便宜。除非东西真的被优化掉了,否则这个值已经在寄存器中了。

(除非是 tmp1 = a+b; / tmp2 = tmp1+c 的情况,但编译器宁愿先执行 b+c。在这种情况下,强制 tmp1 被物化将迫使它实际上执行 {{ 1}}。通常这不是问题,因为人们通常不会在属于较大计算的临时对象上使用 DoNotOptimize。)


我认为在阻止更多事情方面犯这种错误是有意的,例如提升循环不变量和其他 CSE 负载或跨迭代或在基准测试中重复循环。看到人们只对计算的最终结果或其他东西使用 a+b 是很常见的;如果它没有“内存”破坏,它就更不可能阻止编译器准备一次值(或某些不变的部分),并且每次迭代只benchmark::DoNotOptimize()将它实现在寄存器中.

那些完全了解他们正在尝试进行基准测试以检查编译器生成的 asm 的人当然可能希望使用 mov 使编译器实现它并忘记它对值的了解,不会触发其他全局变量的任何溢出。

asm("" : "+g"(var)); 是 clang 的一种解决方法,它倾向于为 "+r,m""+rm" 发明一个临时内存。GCC 尽可能选择寄存器。)


"+g" 用于指针

不,这会迫使编译器溢出指针 value 本身,这是您不想要的。您只想确保指向的内存也是同步的,以防这是用户所期望的,因此“内存”破坏在那里是有意义的。

或者没有“记忆”破坏的另一种方式:

"+m"

或者对于一个完整的指向对象数组 (How can I indicate that the memory *pointed* to by an inline ASM argument may be used?)

asm volatile("" : "+r"(ptr),"+m"(*ptr));

但如果 // deref pointer-to-array of unspecified size asm volatile("" : "+r"(ptr),"+m"( *(T (*)[]) ptr ); 为 NULL,其中任何一个都可能会中断,因此通用定义对所有指针使用其中任何一个都不安全。

手动使用这些,您可能会在寄存器中的指针本身或指向的内存上省略 ptr,以强制具体化该值,而不会在以后忘记它。

您也可以省略 + 操作数,并且只是确保指向的内存是同步的,而不强制将确切的指针存在于寄存器中。编译器仍然必须能够生成引用内存的寻址模式,您可以通过让 asm 模板扩展操作数来查看它选择的内容:

"+r"(ptr)

您不需要 asm( "nop # mem operand picked %0" : "+m" (*ptr) ); ,它可以是像 nop 这样的纯 asm 注释行,但是 Godbolt 编译器资源管理器(本示例中为 https://godbolt.org/z/doPGsse9c)默认过滤注释,因此使用指令很方便。但是,如果您只想查看 GCC 的 asm 输出,它甚至不必是有效的。例如# hi mom,operand at %0nop # mem operand picked 40(%rdi)

GCC 的 asm 模板纯粹是一种文本替换,如 printf 以将文本放入输出文件中 GCC 选择展开 asm 语句的位置。然而,Clang 是不同的。它有一个内置的汇编器,可以在内联汇编上运行。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?