Жестко, так как может показаться, что конструкция 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.