2015-07-23 3 views
1

Как предотвратить встраивание 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 от оптимизации этого результата. Может быть, с прагмой или чем-то еще? Бонусные очки, если этот идеальный метод работает со всеми компиляторами.

+1

Держите функцию в другой единице трансляции (исходный файл) не позволяйте компилятору понять, что это чистая функция. Компилятор не может оптимизировать функции, если он знает только свой прототип. – EOF

+0

@EOF Если LTO не включен, убедитесь, что это не так. –

+0

Я иногда использую макрос для вставки материала в сборку. # define GCC_SPLIT_BLOCK (str) __asm ​​__ ("// \ n \ t //" str "\ n \ t // \ n"); Я не уверен, может быть, это будет трюк –

ответ

1

Вы можете вставить инструкцию непосредственно в сборку. Иногда я использую макрос для разделения сборки, например. отделяя нагрузки от вычислений и ветвления.

#define GCC_SPLIT_BLOCK(str) __asm__("//\n\t// " str "\n\t//\n"); 

Затем в источнике вставки

GCC_SPLIT_BLOCK («Имейте это, пожалуйста»)

до и после того, как ваши функции

+0

Я могу подтвердить, что это работает на моей версии Clang, по крайней мере, не прибегая к отдельным единицам перевода. – Rajiv

+0

Для этого не требуется ассемблер, который использует '//' для запуска строки комментария? – EOF

+0

Точное выражение выше делает, но вы можете сделать то же самое с использованием синтаксиса Intel и AT & T и использования встроенных функций компилятора, если вы используете Microsoft и 64-разрядные. –