Во-первых, чтобы сделать код C ++ более читабельным; Я программирую компилятор, и я дал это:
var swap = ( int x, y ) => { //Assign method that returns two ints, and gets two ints as parameter to variable named swap.
var NewX = y
var NewY = x
}
var increment = ( int x ) => {
var Result = x + 1
}
ПРИМЕЧАНИЕ. Функции возвращают любую переменную с заглавной буквы. swap
можно использовать как ... = swap( x, y ).NewX
, но increment
можно использовать как просто ... = increment( x )
,
После некоторой оптимизации он сгенерировал 🙁 Сделано swap
а также increment
актуальная функция вместо переменных и оптимизированная swap
стек)
template<int BytesCount> struct rawdata { //struct from some header
char _[ BytesCount ];
inline char &operator[] (int index) {
return _[ index ];
}
};
//...
rawdata<8> generatedfunction0( rawdata<8> p ) { // var swap = ( int x, y ) => {
return{ p[ 4 ], p[ 5 ], p[ 6 ], p[ 7 ], p[ 0 ], p[ 1 ], p[ 2 ], p[ 3 ] };
}
rawdata<4> generatedfunction1( rawdata<4> p ) { // var increment = ( int x ) => {
rawdata<4> r = { p[ 0 ], p[ 1 ], p[ 2 ], p[ 3 ] };
++*( ( int* )&r[ 0 ] );
return r;
}
Я почти уверен, что ++*( ( int* )&r[ 0 ] );
не будет делать бесполезную косвенность, но как насчет return{ p[ 4 ], p[ 5 ], p[ 6 ], p[ 7 ], p[ 0 ], p[ 1 ], p[ 2 ], p[ 3 ] };
? Есть ли источник, который гарантирует, что он оптимизирует его так, как если бы он помещал в массив два целых числа вместо 8 или более инструкций, которые помещают побайтово? Я говорю не только об этом конкретном случае, но о чем-то похожем.
Если это зависит, то я использую GCC для компиляции сгенерированного кода.
Да, это может повредить выступлениям — но не всегда. Проблема в явном доступе отдельных байтов.
«Интеллектуальный» компилятор распознает, что вы обращаетесь к непрерывной памяти и пытаетесь оптимизировать ее. Однако по какой-то причине это не работает вообще с gcc, clang или icc (не тестируйте msvc). У оптимизаторов компиляторов еще есть возможности для совершенствования, и стандарт IIRC не требует какой-либо оптимизации.
Своп:
Итак, давайте обработаем каждую функцию, начиная с swap. Я добавил еще 2 функции для полноты, смотрите после фрагмента кода:
#include <stdint.h>
rawdata<8> genSWAP(rawdata<8> p)
{
return { p[ 4 ], p[ 5 ], p[ 6 ], p[ 7 ], p[ 0 ], p[ 1 ], p[ 2 ], p[ 3 ] };
}
rawdata<8> genSWAPvar(rawdata<8> p)
{
return { p._[ 4 ], p._[ 5 ], p._[ 6 ], p._[ 7 ], p._[ 0 ], p._[ 1 ], p._[ 2 ], p._[ 3 ] };
}
rawdata<8> genSWAP32(rawdata<8> p)
{
rawdata<8> res = p;
uint32_t* a = (uint32_t*)&res[0];
uint32_t* b = (uint32_t*)&res[4];
uint32_t tmp = *a;
*a = *b;
*b = tmp;
return res;
}
genSWAP
: ваша функцияgenSWAPvar
: так же, как у вас, но без использования operator[]
вы определилиgenSWAP32
: точно упаковывать ваши байты 32 бита на 32 битаВы можете просмотреть сгенерированный асм здесь.
genSWAP
а также genSWAPvar
ничем не отличаются, то есть перегружены operator[]
просто оптимизирован. Однако каждый байт доступен в памяти индивидуально, а также обрабатывается индивидуально. Это плохо, так как на 32-битных архитектурах процессор загружает 4 байта сразу из памяти (8 для 64-битных архитектур). Короче говоря, gcc / clang / icc испускает инструкции, чтобы противостоять реальным возможностям 32-битных архитектур …
genSWAP32
Это более эффективный способ — выполнять минимальное количество загрузок (для 32-битных) и правильно использовать регистры (обратите внимание, что для 64-битных архитектур должна быть возможность выполнять только одну загрузку вместо 2).
И наконец, некоторые реальные меры: на Идеоне genSWAP32
почти в 4 раза быстрее (что имеет смысл, потому что он имеет 2 нагрузки вместо 8 и меньше вычислительных инструкций).
инкремент:
То же самое здесь, ваша функция против «оптимизированной»:
rawdata<4> genINC(rawdata<4> p)
{
rawdata<4> r = { p[ 0 ], p[ 1 ], p[ 2 ], p[ 3 ] };
++*( ( int* )&r[ 0 ] );
return r;
}
rawdata<4> genINC32(rawdata<4> p)
{
rawdata<4> res = p;
uint32_t* a = (uint32_t*)&res[0];
++*a;
return res;
}
Для clang и icc убийцей является не приращение, а инициализация, когда вы получаете доступ к каждому байту индивидуально. gcc и icc, вероятно, делают это по умолчанию, потому что порядок байтов может отличаться от 0 1 2 3
, Удивительно, но Clang распознает, что порядок байтов и оптимизирует это правильно — без разницы перфорации.
Затем происходит нечто интересное: genINC32
функция медленнее на gcc, но быстрее на msvc (* я не вижу кнопку постоянной ссылки на rise4fun, поэтому иди туда и вставьте проверенный код на ideone). Не видя сгенерированного msvc ассемблера и сравнения, я не могу этого объяснить.
В заключение, хотя компилятор может корректно оптимизировать весь ваш код, не полагайтесь на это сейчас, поэтому не обращайтесь к каждому байту отдельно, если это не нужно.