Intereting Posts
Алгоритм в C – игра с цифрами – число с 3 в единицах места Как пропустить первую строку при fscanning .txt-файле? какова разница между определением char a и char (* a) ? Почему `free` в C не берет количество байтов, которые нужно освободить? Всегда ли stdlib rand () дает одну и ту же последовательность? Что такое выражения с побочными эффектами и почему они не должны передаваться макросу? Как написать короткий блок встроенной расширенной сборки gnu для замены значений двух целых переменных? Какова типичная продолжительность нажатия клавиши Неизвестная ошибка GCC при компиляции для ARM NEON (Critical) Как преобразовать строку ac в свою экранированную версию в c? Некоторая проблема, касающаяся отпечатков и указателей Что здесь происходит? sizeof (short_int_variable + char_variable) Как определить, доступен ли файловый дескриптор? Конвертировать буфер Char * в hex для печати в c Проблема с кодом в C

Какие методы избежать условного разветвления вы знаете?

Иногда цикл, в котором процессор проводит большую часть времени, имеет очень частое предсказание предсказания ветвления (неверное предсказание) (около .5 вероятности). Я видел несколько методов для очень изолированных streamов, но никогда не список. Те, которые, как я знаю, уже исправляют ситуации, когда условие можно переключить на bool и что 0/1 используется каким-то образом для изменения. Можно ли избежать других условных ветвей?

например (псевдокод)

loop () { if (in[i] < C ) out[o++] = in[i++] ... } 

Можно переписать, возможно, потерять некоторую читаемость, с чем-то вроде этого:

 loop() { out[o] = in[i] // copy anyway, just don't increment inc = in[i] < C // increment counters? (0 or 1) o += inc i += inc } 

Кроме того, я видел методы в wild change && to & in conditional в определенных контекстах, которые сейчас исчезают. Я новичок на этом уровне оптимизации, но он уверен, что там должно быть больше.

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

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

Вот пример следующего кода на языке C:

 if (b > a) b = a; 

В сборке без каких-либо переходов, используя бит-манипуляцию (и крайний комментарий):

 sub eax, ebx ; = a - b sbb edx, edx ; = (b > a) ? 0xFFFFFFFF : 0 and edx, eax ; = (b > a) ? a - b : 0 add ebx, edx ; b = (b > a) ? b + (a - b) : b + 0 

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

Использование примера Matt Joiner:

 if (b > a) b = a; 

Вы также можете сделать следующее, без необходимости копать код сборки:

 bool if_else = b > a; b = a * if_else + b * !if_else; 

Обобщение приведенного вами примера – «заменить условную оценку математикой»; условно-отраслевое избегание в значительной степени сводится к этому.

То, что происходит с заменой && на & заключается в том, что, поскольку && является короткозамкнутым, это само по себе является условной оценкой. & получает те же логические результаты, если обе стороны равны 0 или 1 и не являются короткозамкнутыми. То же самое относится к || и | за исключением того, что вам не нужно убеждаться, что стороны ограничены 0 или 1 (опять же, только для логических целей, т. е. вы используете результат только Booleanly).

GCC уже достаточно умен, чтобы заменить условные обозначения более простыми инструкциями. Например, более новые процессоры Intel обеспечивают cmov (условное перемещение). Если вы можете использовать его, SSE2 предоставляет некоторые инструкции для сравнения 4 целых числа (или 8 шорт или 16 символов) за раз.

Дополнительно для вычисления минимума вы можете использовать (см. Эти магические трюки ):

 min(x, y) = x+(((yx)>>(WORDBITS-1))&(yx)) 

Однако обратите внимание на такие вещи, как:

 c[i][j] = min(c[i][j], c[i][k] + c[j][k]); // from Floyd-Warshal algorithm 

даже никакие прыжки не подразумеваются намного медленнее, чем

 int tmp = c[i][k] + c[j][k]; if (tmp < c[i][j]) c[i][j] = tmp; 

Лучше всего предположить, что в первом fragmentе вы чаще всего загрязняете кеш, а во втором - нет.

На этом уровне все зависит от оборудования и зависит от компилятора. Является ли ваш компилятор достаточно умным для компиляции <без потока управления? gcc на x86 достаточно умен; lcc нет. На старых или встроенных наборах инструкций может быть невозможно вычислить <без потока управления.

Помимо этого предупреждения, подобного Кассандре, трудно сделать какие-либо полезные общие утверждения. Итак, вот некоторые общие утверждения, которые могут быть бесполезными:

  • Современное оборудование для outlookирования ветвей ужасно хорошо. Если бы вы могли найти настоящую программу, где предсказание плохой ветви стоило бы более 1% -2% -ного замедления, я был бы очень удивлен.

  • Счетчики производительности или другие инструменты, которые сообщают вам, где искать неверные outlookы отрасли, незаменимы.

  • Если вам действительно нужно улучшить такой код, я бы посмотрел на планирование трассировки и разворот цикла:

    • Loop unrolling реплицирует тела контуров и дает вашему оптимизатору больше streamа управления для работы.

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

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

На мой взгляд, если вы достигнете такого уровня оптимизации, вероятно, пора перейти на ассемблерный язык.

По сути, вы рассчитываете на компилятор, создающий конкретный шаблон сборки, чтобы использовать эту оптимизацию в C в любом случае. Трудно догадаться, какой код компилятор будет генерировать, поэтому вам придется смотреть на него в любое время, когда будет сделано небольшое изменение – почему бы просто не сделать это в сборке и не поделать с ним?

Большинство процессоров обеспечивают outlookирование отрасли, которое составляет более 50%. На самом деле, если вы получите 1% -ное улучшение в outlookировании отрасли, вы, вероятно, можете опубликовать статью. Если вам интересно, есть гора бумаг на эту тему.

Тебе лучше беспокоиться о хитах и ​​промахах кеша.

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

 if (a) { if (b) { result = q; } else { result = r; } } else { if (b) { result = s; } else { result = t; } } 

Если a и b почти случайны (например, из произвольных данных), и это находится в плотном цикле, то неудачи предсказания ветвления могут действительно замедлить это. Может быть написано как:

 // assuming a and b are bools and thus exactly 0 or 1 ... static const table[] = { t, s, r, q }; unsigned index = (a << 1) | b; result = table[index]; 

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

Этот уровень оптимизации вряд ли будет иметь значительную разницу во всех, кроме самых горячих горячих точек. Предполагая, что он делает (не доказывая это в конкретном случае), является формой угадывания , и первое правило оптимизации не действует на догадки .