Я нахожусь в процессе оптимизации моего кода с использованием SSE3. В коде есть одна точка, которая заставляет меня сдвигать все элементы вектора на один элемент
v[0] = 0 //v is some char* and N = v.size()
for(int i = 1;i<N;i++){
v[i] = v[i-1];
}
Насколько я могу судить, SSE не поддерживает смещение вектора, поэтому мне придется кодировать его с нуля.
Но тогда у меня возникла идея, что если я просто уменьшу указатель.
v = (v-1);
v[0] = 0;
Таким образом, операция будет постоянной и вообще не потребует никаких операций.
Я уже проверил это, и это работает для моей тестовой программы.
Однако я не уверен, что эта операция безопасна.
Это действительно глупая идея?
SSE
поддерживает сдвиг, либо побитовое смещение элементов внутри вектора, либо смещение целых регистров по границам байтов.
Предполагая, что ваш вектор имеет тип 16 раз uint8_t
, операция, которую вы ищете
psrldq xmm, 1 ;packed shift right logical double quad word
с внутренним
vec = _mm_srli_si128(vec, 1); // shift by 1 byte
На ваш первый вопрос: пока v
является указателем на символ, уменьшая или увеличивая его, совершенно безопасно. Разыменование не может, это зависит от вашей программы.
На ваш второй вопрос: да, это похоже на глупую идею. Если вы попытаетесь оптимизировать с SSE
и вы выполняете некоторые задачи с указателями на байты, вы, скорее всего, делаете что-то не так, и вы вызываете проблемы, если вы пытаетесь загрузить 16 из ваших v
в SSE
register — либо segfaults из-за смещения, либо из-за снижения производительности из-за принудительного использования компилятора movdqu
,
Самый простой ответ: вместо цикла, который вы опубликовали, используйте memmove (v + 1, v, N-1). Это, вероятно, будет работать так же быстро, как сборка с ручным кодированием в любой приличной системе, потому что это является сборка с ручным кодированием, используя правильную смесь movdqu / movdqa / movntdqa и разворачивания петли.
Более сложный ответ: я думаю, глядя на более широкую картину, что это очень маловероятно, что вам действительно нужно сдвинуть данные. Скорее всего, вам может понадобиться доступ соседний элемент и текущий элемент, например, выполняют какие-то вычисления как для v [i], так и для v [i-1].
Если вы используете SIMD-код для этого, стандартная техника состоит в том, чтобы (например) загрузить байты 0..15 в xmm0, 16..31 в xmm1, а затем перемешать оба регистра, чтобы получить элементы 1..16 в xmm2. Затем вы можете выполнить вычисления с помощью xmm0 (здесь соответствует векторизованному v [i-1]) и xmm2 (векторизованному v [i]). Это не «сдвиг» в смысле логического / арифметического сдвига, а скорее сдвиг линии SIMD.
Пример: работа с байтами в сборке
movdqa mem, xmm0 // load bytes 0..15
loop:
// increment mem by 16
movdqa mem, xmm1 // load bytes 16..31
movdqa xmm0, xmm2 // make a copy
movdqa xmm1, xmm3 // make a copy
psrldq xmm2, 1 // ends up with bytes 1..15 and a zero
pslldq xmm3, 15 // ends up with zeros and byte 16
por xmm2, xmm3 // ends up with bytes 1..16
// do something with xmm3 and xmm0 here, they contain bytes 1..16 and 0..15 respectively
// in other words xmm3 is a lane-shifted
movdqa xmm1, xmm0 // use our copy of bytes 16..31 to continue the loop
// goto loop
Почему бы не сделать это: «что если я просто уменьшу указатель … v = (v-1);»
Это будет сбой:
char* v = (char*)malloc(...);
v=(v-1);
v[0] = 0; // or any read or write of v[0]
Если v указывает где-то в середине (а не в начале) блока выделенной памяти, тогда декремент будет работать нормально, но вы должны быть уверены, что это всегда так (например, память выделена в той же функции, которая будет использовать этот трюк).
Уменьшение указателя сначала вызовет доступ за пределами 0-го элемента, а также это сместит ваш вектор. Векторные операции, за исключением данных, должны быть правильно выровнены, чтобы быть производительными. Если данные не выровнены, планировщик команд должен разделить чтение из памяти на две выборки, что приведет к снижению производительности.
SSE предлагает операции сдвига битов для целых векторов, см. Ответ @hirschhornsalz.