Попалась в руки книга "C++ Ultra-Low Latency: Multithreading and Low-Level Optimizations". Начал ее листать, т.к. темой низкоуровневых оптимизаций на C++ никогда не занимался. Мне всегда было интересно писать корректно работающий код, который был бы понятным и сопровождабельным, который бы было просто использовать правильно, но сложно неправильно, но в плане скорости работы кода никогда не упарывался. В общем, как однажды сказали про мой код: "получение гарантий корректности времени компиляции при этом не используя Haskell" 🙂
Решил немного просветиться на эту тему выжимания производительности, говорят, что учиться никогда не поздно.
Про саму книгу ничего не скажу, только начал с ней знакомиться, а начало тупо пропустил, т.к. там много говорится о специфике HFT, а к HFT вообще не имею никакого отношения. Перешел сразу к главам, где про конкретные приемы рассказывается. И вот в разделе про Branch Prediction наткнулся на вещи, которые мне прям как бальзам на душу, а именно:
Дело в том, что для меня всегда наиболее естественно было писать в стиле:
if(some_condition) {
... // тут много строчек кода с выполнением основной логики.
...
...
}
else {
return some_error_code;
}
Т.е. большинство действий сосредотачивается именно в ветке then.
При этом регулярно встречал рекомендации, что в if-ах в then должны быть наиболее короткие блоки кода. Мол так код воспринимается лучше: когда в then короткий блок, то мы еще помним контекст когда доходим до else. А вот если в then длинный блок, то когда мы доберемся до else, то на фоне действий из then уже не будем понимать где находимся. Такие рекомендации декларируют в качестве "хорошего" стиля вот такой:
if(!some_condition) {
return some_error_code;
}
else {
... // тут много строчек кода с выполнением основной логики.
...
...
}
Или даже вот такой:
if(!some_condition) {
return some_error_code;
}
... // тут много строчек кода с выполнением основной логики.
...
...
Но оба эти стиля мне не нравятся на каком-то интуитивном уровне. Особенно последний (про этот стиль я уже высказывался: например, в контексте языка Go). Хотя, если мы в проекте придерживаемся принципов defensive programming, то начало метода/функции из if-ов для проверки входных параметров/состояния объекта, т.е. что-то вроде:
int f(int a, int b, int c) {
if(a < 0) return invalid_parameter_a;
if(b < 10 || b > 100) return invalid_parameter_b;
if(c > 1000) return invalid_parameter_c;
... // Далее основная логика.
}
то такие короткие if-ы -- это нормально. Но когда проверки входных данных завершены и идет основной код метода/функции, то if-ы с короткими then или if-ы, в которых только return, на мой взгляд, ухудшают код (хуже только циклы, внутри которых короткие if-ы с continue).
И вот листая книгу "C++ Ultra-Low Latency: Multithreading and Low-Level Optimizations" вдруг натыкаюсь на подтверждение того, что привычный для меня способ написания if-ов имеет под собой обоснование еще и с точки зрения эвристик компилятора по обеспечению branch predictions.

Комментариев нет:
Отправить комментарий