Может ли использование char [] в качестве параметров, return и т. Д. Повлиять на производительность?

Во-первых, чтобы сделать код 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 для компиляции сгенерированного кода.

5

Решение

Да, это может повредить выступлениям — но не всегда. Проблема в явном доступе отдельных байтов.

«Интеллектуальный» компилятор распознает, что вы обращаетесь к непрерывной памяти и пытаетесь оптимизировать ее. Однако по какой-то причине это не работает вообще с 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 ассемблера и сравнения, я не могу этого объяснить.

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

4

Другие решения


По вопросам рекламы ammmcru@yandex.ru
Adblock
detector