Интересно, быстрее ли заменить ветвление на 2 умножения или нет (из-за штрафа за пропуск кеша)?
Вот мой случай:

float dot = rib1.x*-dir.y + rib1.y*dir.x;

if(dot<0){
    dir.x = -dir.x;
    dir.y = -dir.y;
}

И пытаюсь заменить на:

float dot = rib1.x*-dir.y + rib1.y*dir.x;

int sgn = (dot  < 0.0) - (0.0 < dot ); //returns -1 or 1 (no branching here, tested)
dir.x *= sgn;
dir.y *= sgn;
-1
tower120 23 Мар 2014 в 03:19
3
Почему бы вам не протестировать его и не рассказать нам, что вы нашли?
 – 
Pod
23 Мар 2014 в 03:21
1
Я боюсь, что на моем i7 с кешем 8 Мб я никогда не получу промаха кеша в этом тесте.
 – 
tower120
23 Мар 2014 в 03:22
1
Если этого не произойдет, то какая разница? ;) Я полагаю, вы хотите проверить это на ядрах с меньшим кешем? Почему бы просто не провести тест с массивным набором данных, даже большим, чем может обработать ваш i7?
 – 
Pod
23 Мар 2014 в 03:28
3
Проблема с ветвями заключается не в промахах кеша, а в прерывании конвейера инструкций. И, кстати, когда он говорит «8 МБ» кеша, это кеш L3, и он указывает только общую емкость, в то время как промахи кеша относятся к строкам кеша, которые обычно составляют около 64 байт (по крайней мере, на i7 есть).
 – 
Mikael Persson
23 Мар 2014 в 03:32
1
Между прочим, глобальная 50-процентная вероятность не дает информации о предсказуемости. Двадцать взятых, за которыми следуют двадцать невыполненных, можно предсказать довольно хорошо (обычно 90%). С предсказателем «петля», если ветвь постоянно чередуется между взятой и не взятой (т. Е. T, NT, T, NT, T, NT,...), предсказание будет приближаться к 100%. Я скорее подозреваю, что условные перемещения FP будут быстрее, чем ваша целочисленная оценка и умножение FP. Некоторые наборы инструкций SIMD также предоставляют сравнения, которые устанавливают все биты в элементе данных, если они истинны, сдвиг влево на 32 бита и xoring (я полагаю) условно отрицают.
 – 
Paul A. Clayton
23 Мар 2014 в 04:17

2 ответа

Лучший ответ

Стоимость умножения зависит от нескольких факторов, от того, используете ли вы 32-битные или 64-битные числа с плавающей запятой, а также от того, включаете ли вы SSE или нет. Стоимость двух умножений с плавающей запятой составляет 10 циклов согласно этому источнику: http://www.agner.org /optimize/instruction_tables.pdf

Стоимость ветки также зависит от нескольких факторов. Как правило, не беспокойтесь о ветвях в вашем коде. Точное поведение предсказателя ветвления на ЦП будет определять производительность, но в этом случае вы, вероятно, должны ожидать, что ветвление будет в лучшем случае непредсказуемым, так что это может привести к множеству неверных предсказаний ветвления. Стоимость неверного предсказания ветки составляет 10-30 циклов согласно этому источнику: http: // valgrind .org / docs / manual / cg-manual.html

Лучший совет, который можно здесь дать, - это профилировать и тестировать. Я предполагаю, что на современном Core i7 два умножения должны быть быстрее, чем ветвь if the range of input varies sufficiently as to cause sufficient branch mispredictions as to outweigh the cost of the additional multiplication.

Предполагая, что частота пропусков составляет 50%, стоимость перехода составляет в среднем 15 циклов (30 * 0,5), стоимость плавающего элемента составляет 10 циклов.


РЕДАКТИРОВАТЬ : добавлены ссылки, обновлена ​​ориентировочная стоимость инструкции.

1
blockchaindev 23 Мар 2014 в 14:18
1
Предполагая отсутствие SSE и 50% ошибочного предсказания переходов. Неправильное предсказание ветвления составляет порядка 18 циклов. Умножение с плавающей запятой имеет порядок 10 циклов.
 – 
blockchaindev
23 Мар 2014 в 04:09
- Согласно этому valgrind.org/docs/manual/cg-manual.html неправильное предсказание перехода составляет 10-30 тактов. И согласно этому agner.org/optimize/instruction_tables.pdf потребовалось 2 float mul около 10 циклов. В любом случае 30 * 0,5 = 15 (ветвь) против 10 (мульт). В случае, если это заняло не 50%.... Я останусь с ответвлением. Спасибо. Сделайте ответ с этим, я приму это.
 – 
tower120
23 Мар 2014 в 04:21
Я обновил свой ответ, и спасибо за ссылки.
 – 
blockchaindev
23 Мар 2014 в 14:18
1
Умножение FP с одинарной точностью обычно занимает 4 цикла (DP, 5 циклов), два умножения не зависят друг от друга, поэтому его можно выполнить за 5 циклов (6 для DP). Сравнение двух целых чисел может выполняться параллельно и занимать всего 1 цикл, вычитание целых чисел добавит еще один цикл, но преобразование dot из числа с плавающей запятой в целое число и sgn из целого числа в число с плавающей запятой, вероятно, убьет производительность.
 – 
Paul A. Clayton
23 Мар 2014 в 18:19
Спасибо за разъяснение.
 – 
blockchaindev
23 Мар 2014 в 18:26

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

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

С другой стороны, если вы вводите if в 50% случаев, вы находитесь в худшем состоянии: в этом случае я бы попытался найти конвейерную обработку SSE и оптимизировать выполнение с помощью SSE, вероятно, получая некоторые подсказки от этот пост в соответствии со вторым блоком кода.

Однако протестируйте свой код, проверьте созданный ассемблер, чтобы найти лучшее решение для этой оптимизации и получить правильное представление. И в конце концов держите нас в курсе :)

2
Community 23 Май 2017 в 14:49
Мы проповедуем одно и то же: семь раз отмерь, один раз отрежь.
 – 
blockchaindev
23 Мар 2014 в 03:47
Да уж! - если его код может умело использовать SSE, я думаю, он сможет получить что-то большее от второго. Но на самом деле это во многом зависит от количества данных, использования кешей... слишком много факторов влияет на современные архитектуры!
 – 
Sigi
23 Мар 2014 в 03:52
Предположим, что я (и мой компилятор) не использую SSE. Предположим, что ветка входила в 50% случаев. И в худшем случае он будет делать только это "dir.x = -dir.x; dir.y = -dir.y;" когда это не нужно (2-4 цикла впустую)? Или не?
 – 
tower120
23 Мар 2014 в 03:59
Я думаю, что в этом случае вы должны иметь, что в случае 1. предиктор ветвления предсказывает половину времени и конвейеров меньше инструкций, а в другой половине конвейер нарушается из-за неправильного предсказания - во 2. конвейер не нарушается неправильными предсказаниями, но есть еще несколько инструкций для выполнения. Эти две ситуации действительно похожи с точки зрения эффективности. Стало важно, какие этапы пайплайнов свободны, имеется достаточное «давление данных» (то есть — данные уже находятся в кеше L1),… так что опять нужно тестировать. Я не удивлюсь, если они окажутся эквивалентными.
 – 
Sigi
23 Мар 2014 в 05:41