Подтвердить что ты не робот

Помогает компилятору оптимизировать разветвленные последовательности кода

У меня есть последовательности кода в C/С++, которые содержат много ветки, что-то вроде этого:

if( condition1 )
    return true;
if( condition2 )
    return true;
...
return false;

(что эквивалентно условию возврата 1 || условие2 ||...;)

Оценка каждого из условий требует нескольких обращений к памяти (только для чтения), но компилятор пропускает важную возможность оптимизации, не перемещая обращения к памяти до того, как будет оценено предыдущее условие. Причина в том, что доступ к памяти состояния2 может быть segfault, когда условие 1 истинно. Ну, я знаю, что они этого не делают, и я хотел бы, чтобы компилятор делал разумную вещь и смешивал некоторые из этих кодовых последовательностей, когда это уместно для производительности, например. для использования уровня инструкций - parallelism. Я также не хочу менять условие на логическое или (не короткое замыкание), потому что одна из ветвей, скорее всего, выскочит.

Любые идеи о том, как это можно сделать (желательно использовать gcc)?

Спасибо.

4b9b3361

Ответ 1

Для оценки каждого из условий требуется несколько обращений к памяти

Почему бы вам не избежать оценки короткого замыкания в отдельных условиях, но пусть это произойдет для условий?

Использование операторов, которые не являются короткозамкнутыми для встроенных типов

Именно то, как вы достигаете первого, зависит от характера этих условий (т.е. condition1, condition2 в вашем коде) - если вы ничего не видите о них, я могу говорить только в общих чертах: где они внутренне содержат короткое замыкание операторы вместо этого преобразуют логическое значение в целочисленное представление и используют, например, bitwise-OR (или даже "+" или "*", если он лучше читает и работает в вашем конкретном использовании). Побитовые операторы, как правило, более безопасны, так как они имеют более низкий приоритет - нужно только быть осторожными, если в ваших условиях уже есть побитовые операторы.

Чтобы проиллюстрировать:

OLD: return (a > 4 && b == 2 && c < a) ||   // condition1
            (a == 3 && b != 2 && c == -a);  // condition2

NEW: return (a > 4 & b == 2 & c < a) ||
            (a == 3 & b != 2 & c == -a);

Также будьте осторожны, если вы использовали неявное преобразование чисел/указателей в bool до... вы хотите нормализовать их до bool, чтобы их младшие значащие биты отражали их логическое значение:

OLD: return my_int && my_point && !my_double;
NEW: return bool(my_int) & bool(my_point) & !my_double;  // ! normalises before bitwise-&

Вы также можете сравнить с...

     bool condition1 = a > 4 & b == 2 & c < a;
     bool condition2 = a == 3 & b != 2 & c == -a;
     return condition1 || condition2;

... который может быть быстрее - возможно, только в общем случае "return false" и, возможно, когда последнее условие N или два являются решающим фактором в "return true".

Пользовательские операторы избегают оценки короткого замыкания

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

Мысли

В более общем плане вы выиграете от этого, если у вас есть большое количество утверждений, объединенных в каждом отдельном условии - тем более, если функция имеет тенденцию работать, чтобы вернуть false.

"AProgrammer" тоже имеет большое значение - при наличии спекулятивного исполнения на современных процессорах процессор может уже опережать порядок, предполагаемый оценкой короткого замыкания (в каком-то специальном режиме, чем либо позволяет избежать или блокировать любые ошибки памяти от разыменования недействительные указатели, делит на 0 и т.д.). Таким образом, возможно, что вся попытка оптимизации может оказаться бессмысленной или даже контрпродуктивной. Требуется бенчмаркинг всех альтернатив.

Ответ 2

Не могли бы вы просто переместить части условия самостоятельно?

ie

 const bool bCondition1Result = <condition1>;
 const bool bCondition2Result = <condition2>;

и т.д.

Для лучшей оптимизации все еще... re-jig порядок ваших условий, чтобы первый из них был первым. Таким образом, это будет происходить раньше, чем раньше (это может сделать очень мало различий).

Ответ 3

Взгляните на функции __ builtin_expect, предоставленные gcc. Когда вы определяете макросы вероятно/маловероятные, как это делается с ядром linux, их можно использовать интуитивно, мало влияя на читаемость кода.

Ответ 4

Не беспокойтесь. Процессор уже имеет внеуровневое исполнение, спекулятивное выполнение и прогнозирование ветвлений. Очень маловероятно, чтобы любая разница на этом уровне могла иметь какое-либо значение. Уровень parallelism уровня инструкций выполняется неявным образом процессором, а не явно компилятором. Возможно, GCC ничего не сделал, потому что ничего не получилось.

В этой заметке вам нужно иметь какое-то условие, которое может повлиять на время работы нетривиального приложения.

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