如何阻止GCC / Clang内联和优化纯函数的多次调用?
我正在尝试对此表单的代码进行基准测试
int __attribute__ ((noinline)) my_loop(int const* array, int len) {
// Use array to compute result.
}
我的基准代码看起来像这样:
int main() {
const int number = 2048;
// My own aligned_malloc implementation.
int* input = (int*)aligned_malloc(sizeof(int) * number, 32);
// Fill the array with some random numbers.
make_random(input, number);
const int num_runs = 10000000;
for (int i = 0; i < num_runs; i++) {
const int result = my_loop(input, number); // Call pure function.
}
// Since the program exits I don't free input.
}
正如预期的那样,Clang似乎能够将其转变为O2的无操作(甚至可能在O1)。
我试图对我的实现进行实际测试的一些事情是:
将中间结果累积为整数并在结尾处打印结果:
const int num_runs = 10000000;
uint64_t total = 0;
for (int i = 0; i < num_runs; i++) {
total += my_loop(input, number); // Call pure function.
}
printf("Total is %llu\n", total);
可悲的是,这似乎不起作用。 Clang至少足够聪明,意识到这是一个纯粹的功能,并将基准转换为这样的东西:
int result = my_loop();
uint64_t total = num_runs * result;
printf("Total is %llu\n", total);
在每次循环迭代结束时使用释放语义设置原子变量:
const int num_runs = 10000000;
std::atomic<uint64_t> result_atomic(0);
for (int i = 0; i < num_runs; i++) {
int result = my_loop(input, number); // Call pure function.
// Tried std::memory_order_release too.
result_atomic.store(result, std::memory_order_seq_cst);
}
printf("Result is %llu\n", result_atomic.load());
我的希望是,因为原子会引入happens-before
关系,所以Clang将被迫执行我的代码。但遗憾的是,它仍然进行了上述优化,并将原子的值设置为num_runs * result
,而不是运行函数的num_runs
次迭代。
在每个循环结束时设置一个volatile int,并总计总和。
const int num_runs = 10000000;
uint64_t total = 0;
volatile int trigger = 0;
for (int i = 0; i < num_runs; i++) {
total += my_loop(input, number); // Call pure function.
trigger = 1;
}
// If I take this printf out, Clang optimizes the code away again.
printf("Total is %llu\n", total);
这似乎可以解决问题,我的基准似乎也有效。出于多种原因,这并不理想。
根据我对C ++ 11内存模型的理解volatile set operations
没有建立happens before
关系,所以我不能确定某些编译器不会决定做同样的事情{ {1}}优化。
此方法似乎也不合需要,因为现在我在循环的每次运行中都有设置volatile int的开销(无论多么微小)。
是否存在阻止Clang / GCC优化此结果的规范方法。也许有一个pragma或什么?如果这种理想方法适用于编译器,则可以获得奖励。
答案 0 :(得分:1)
您可以将指令直接插入到装配中。我有时使用宏来分割组件,例如将负载与计算和分支分开。
#define GCC_SPLIT_BLOCK(str) __asm__( "//\n\t// " str "\n\t//\n" );
然后在源代码中插入
GCC_SPLIT_BLOCK(&#34;请保持此状态&#34;)
你的职能之前和之后