Что означает static_cast & lt; volatile void & gt; `для оптимизатора?

Когда люди пытаются выполнить строгие тесты в различных библиотеках, я иногда вижу такой код:

auto std_start = std::chrono::steady_clock::now();
for (int i = 0; i < 10000; ++i)
for (int j = 0; j < 10000; ++j)
volatile const auto __attribute__((unused)) c = std_set.count(i + j);
auto std_stop = std::chrono::steady_clock::now();

volatile здесь используется, чтобы оптимизатор не заметил, что результат тестируемого кода отбрасывается, а затем отбрасывает все вычисления.

Когда тестируемый код не возвращает значение, скажем, что это void do_something(int)то иногда я вижу такой код:

auto std_start = std::chrono::steady_clock::now();
for (int i = 0; i < 10000; ++i)
for (int j = 0; j < 10000; ++j)
static_cast<volatile void> (do_something(i + j));
auto std_stop = std::chrono::steady_clock::now();

Это правильное использование volatile? Что такое volatile void? Что это значит с точки зрения компилятора и стандарта?

В стандарте (N4296) на [dcl.type.cv] это говорит:

7 [Примечание: volatile — это подсказка для реализации, чтобы избежать агрессивной оптимизации объекта.
потому что значение объекта может быть изменено средствами, не обнаруживаемыми реализацией. Более того,
для некоторых реализаций volatile может указывать на то, что для доступа к
предмет. См. 1.9 для подробной семантики. В общем, семантика volatile предназначена для
то же самое в C ++, как и в C. — конец примечания]

В разделе 1.9 указывается много рекомендаций относительно модели выполнения, но в отношении volatile речь идет о «доступе к volatile объект «. Мне не ясно, что выполнение заявления который был приведен к volatile void значит, при условии, что я правильно понимаю код, и что делать, если создается какой-либо барьер для оптимизации.

5

Решение

static_cast<volatile void> (foo()) не работает как способ требовать компилятор на самом деле вычислить foo() в любом из gcc / clang / MSVC / ICC, с включенной оптимизацией.

#include <bitset>

void foo() {
for (int i = 0; i < 10000; ++i)
for (int j = 0; j < 10000; ++j) {
std::bitset<64> std_set(i + j);
//volatile const auto c = std_set.count();     // real work happens
static_cast<volatile void> (std_set.count());  // optimizes away
}
}

компилируется просто ret со всеми 4 основными компиляторами x86. (MSVC испускает asm для автономных определений std::bitset::count() или что-то, но прокрутите вниз для его тривиального определения foo(),

(Исходный код + вывод asm для этого и следующего примера на Исследователь компилятора Мэтта Годболта)


Может быть, есть некоторые компиляторы, где static_cast<volatile void>() делает что-то, и в этом случае это может быть более легкий способ написать цикл повторения, который не тратит инструкции на сохранение результата в память, а только вычисляет его. (Иногда это может быть то, что вы хотите в микробенчмарке).

Накапливая результат с tmp += foo() (или же tmp |=) и вернуть его из main() или распечатать его с printf может также быть полезным, вместо хранения в volatile переменная. Или различные специфичные для компилятора вещи, такие как использование пустой строки asm заявление, чтобы сломать способность компилятора оптимизировать без добавления каких-либо инструкций.


Увидеть CppCon2015 Чендлера Каррута рассказывает об использовании perf исследовать оптимизации компилятора, где он показывает функция выхода из оптимизатора для GNU C. Но его escape() Функция написана так, чтобы требовать, чтобы значение находилось в памяти (передавая asm a void* к нему, с "memory" затирать). Нам это не нужно, нам просто нужно, чтобы компилятор имел значение в регистре или памяти, или даже непосредственную константу. (Маловероятно, что наш цикл будет полностью развернут, потому что он не знает, что оператор asm содержит ноль инструкций.)


Этот код компилируется в просто popcnt без каких-либо дополнительных магазинов, на gcc.

// just force the value to be in memory, register, or even immediate
// instead of empty inline asm, use the operand in a comment so we can see what the compiler chose.  Absolutely no effect on optimization.
static void escape_integer(int a) {
asm volatile("# value = %0" : : "g"(a));
}

// simplified with just one inner loop
void test1() {
for (int i = 0; i < 10000; ++i) {
std::bitset<64> std_set(i);
int count = std_set.count();
escape_integer(count);
}
}

#gcc8.0 20171110 nightly -O3 -march=nehalem  (for popcnt instruction):

test1():
# value = 0              # it peels the first iteration with an immediate 0 for the inline asm.
mov     eax, 1
.L4:
popcnt  rdx, rax
# value = edx            # the inline-asm comment has the %0 filled in to show where gcc put the value
add     rax, 1
cmp     rax, 10000
jne     .L4
ret

Clang решает поместить значение в память, чтобы удовлетворить "g" ограничение, которое довольно глупо. Но clang, как правило, делает это, когда вы задаете ему ограничение inline-asm, которое включает в себя память в качестве опции. Так что это не лучше, чем у Чендлера escape Функция для этого.

# clang5.0 -O3 -march=nehalem
test1():
xor     eax, eax
#DEBUG_VALUE: i <- 0
.LBB1_1:                                # =>This Inner Loop Header: Depth=1
popcnt  rcx, rax
mov     dword ptr [rsp - 4], ecx
# value = -4(%rsp)                # inline asm gets a value in memory
inc     rax
cmp     rax, 10000
jne     .LBB1_1
ret

ICC18 с -march=haswell Является ли это:

test1():
xor       eax, eax                                      #30.16
..B2.2:                         # Preds ..B2.2 ..B2.1
# optimization report
# %s was not vectorized: ASM code cannot be vectorized
xor       rdx, rdx              # breaks popcnt's false dep on the destination
popcnt    rdx, rax                                      #475.16
inc       rax                                           #30.34
# value = edx
cmp       rax, 10000                                    #30.25
jl        ..B2.2        # Prob 99%                      #30.25
ret                                                     #35.1

Это странно, ICC используется xor rdx,rdx вместо xor eax,eax, Это приводит к потере REX-префикса и не распознается как нарушение зависимости в Silvermont / KNL.

1

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]