Как предотвратить встраивание 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
итераций функции.Установите переменный ток в конце каждого цикла вместе с суммированием суммы.
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
отношений, так что я не могу быть уверен, что некоторые компилятор не решат сделать то жеnum_runs * result_of_1_run
оптимизации.Также этот метод кажется нежелательным, так как теперь у меня есть накладные расходы (хотя и крошечные) для установки волатильного int на каждом запуске моего цикла.
Есть ли канонический способ предотвращения Clang/GCC от оптимизации этого результата. Может быть, с прагмой или чем-то еще? Бонусные очки, если этот идеальный метод работает со всеми компиляторами.
Держите функцию в другой единице трансляции (исходный файл) не позволяйте компилятору понять, что это чистая функция. Компилятор не может оптимизировать функции, если он знает только свой прототип. – EOF
@EOF Если LTO не включен, убедитесь, что это не так. –
Я иногда использую макрос для вставки материала в сборку. # define GCC_SPLIT_BLOCK (str) __asm __ ("// \ n \ t //" str "\ n \ t // \ n"); Я не уверен, может быть, это будет трюк –