Жестко, так как может показаться, что конструкция p[u+1]
встречается в нескольких местах в самых внутренних циклах кода, которые я поддерживаю, так что при правильной микро-оптимизации это делает часы разницы в операции, которая работает в течение нескольких дней.
Обычно *((p+u)+1)
является наиболее эффективным. Иногда *(p+(u+1))
является наиболее эффективным. Редко *((p+1)+u)
лучше. (Но обычно оптимизатор может преобразовать *((p+1)+u)
в *((p+u)+1)
, когда последний лучше, и не может преобразовать *(p+(u+1))
с любым из других).
p
является указателем, а u
является беззнаковым. В фактическом коде по крайней мере один из них (скорее всего, и тот и другой) уже будет в регистре (ов) в точке, в которой вычисляется выражение. Эти факты имеют решающее значение для моего вопроса.
В 32-битном (до того, как мой проект опустил поддержку для этого), все три имеют точно такую же семантику, и любая половина достойного компилятора просто выбирает лучший из трех, и программисту никогда не нужно заботиться.
В этих 64-битных применениях программист знает, что все три имеют одну и ту же семантику, но компилятор не знает. Насколько компилятор знает, решение о том, когда продлить u
с 32-битного на 64-битный, может повлиять на результат.
Каков самый чистый способ сообщить компилятору, что семантика всех трех одинакова, и компилятор должен выбрать самый быстрый из них?
В одном 64-битном компиляторе Linux я получил почти там p[u+1L]
, что заставляет компилятор разумно выбирать между обычно лучшими *((p+u)+1)
и иногда лучше *(p+( (long)(u) + 1) )
. В редком случае *(p+(u+1))
был еще лучше второго из них, немного потеряно.
Очевидно, что это не очень хорошо в 64-битной Windows. Теперь, когда мы отказались от 32-битной поддержки, возможно, p[u+1LL]
достаточно портативен и достаточно хорош. Но могу ли я лучше?
Обратите внимание, что использование std::size_t
вместо unsigned
для u
устранит всю эту проблему, но создаст большую проблему с производительностью. Кастинг u
до std::size_t
прямо там почти достаточно, и, возможно, лучшее, что я могу сделать. Но это довольно многословие для несовершенного решения.
Простое кодирование (p+1)[u]
делает выбор более оптимальным, чем p[u+1]
. Если код был менее шаблонным и более стабильным, я мог бы установить их все на (p+1)[u]
затем профиль, а затем переключить несколько назад на p[u+1]
. Но шаблоны имеют тенденцию разрушать этот подход (отдельная строка источника появляется в многих местах в профиле, добавляя до серьезного времени, но не индивидуально серьезное время).
Компиляторы, которые должны быть эффективными для этого, - это GCC, ICC и MSVC.