Самый быстрый способ скопировать память с шагом в C?

Я пытаюсь скопировать 1 или 2 цветных канала из данных изображения RGBA как можно быстрее (это самая медленная часть моего кода, и это замедляет работу всего приложения). Есть ли быстрый способ копирования с шагом?

Данные просто выложены как RGBARGBARGBA и т.д., и мне нужно скопировать только значения R, или в другом случае - только значения RG.

То, что я до сих пор, это примерно так, чтобы скопировать значения R:

for(int i=0; i<dataSize; i++){
    dest[i] = source[i*4];
}

Для значений RG я делаю:

for(int i=0; i<dataSize; i+=2){
    dest[i] = source[i*2];
    dest[i+1] = source[(i*2)+1];
}

Все данные представляют собой неподписанные 1-байтовые значения. Есть ли более быстрый способ? Я уже частично развернул цикл (делая 64 значения за итерацию - незначительное ускорение за этим). Платформа - это Armv7 (iOS), поэтому использование NEON (SIMD) может оказаться полезным, к сожалению, у меня нет опыта!

Изменение данных, к сожалению, не имеет значения, оно обеспечивается функцией opengl readPixels(), и iOS не поддерживает чтение как L, LA, RG и т.д., насколько я мог сказать.

Ответ 1

Если у вас все в порядке с iOS4 и выше, вы можете найти vDSP и ускорить фреймворк. Проверьте документацию для всех видов обработки изображений при скорости варпа.

#import <Accelerate/Accelerate.h>

Я не знаю, что вы делаете дальше, но если вы делаете какие-либо вычисления в данных изображения и хотите его в форме с плавающей запятой, вы можете использовать vDSP_vfltu8 для преобразования одного канала исходных байтовых данных в одинарная точность с плавающей точкой с использованием одной строки (исключая управление памятью);

vDSP_vfltu8(srcData+0,4,destinationAsFloatRed,1,numberOfPixels)
vDSP_vfltu8(srcData+1,4,destinationAsFloatGreen,1,numberOfPixels)
vDSP_vfltu8(srcData+2,4,destinationAsFloatBlue,1,numberOfPixels)
vDSP_vfltu8(srcData+3,4,destinationAsFloatAlpha,1,numberOfPixels)

Если вам нужно создать изображение из данных с плавающей запятой, используйте vDSP_vfuxu8, чтобы вернуться в другую сторону - так что

vDSP_vfixu8(destinationAsFloatRed,1,outputData+0,4,numberOfPixels);
vDSP_vfixu8(destinationAsFloatGreen,1,outputData+1,4,numberOfPixels);
vDSP_vfixu8(destinationAsFloatBlue,1,outputData+2,4,numberOfPixels);
vDSP_vfixu8(destinationAsFloatAlpha,1,outputData+3,4,numberOfPixels);

Очевидно, вы можете просто обработать 1 или 2 канала, используя описанную выше технику.

Документация довольно сложная, но результаты хорошие.

Ответ 2

В зависимости от скомпилированного кода вы можете захотеть заменить умножение на 2 с добавлением второго индекса цикла (назовите его j и продвиньте его на 4):

for(int i=0, j=0; i<dataSize; i+=2, j+=4){
    dest[$i] = source[$j];
    dest[$i+1] = source[$j+1];
}

В качестве альтернативы вы можете заменить умножение на сдвиг на 1:

for(int i=0, j=0; i<dataSize; i+=2, j+=4){
    dest[$i] = source[$i<<1];
    dest[$i+1] = source[($i<<1)+1];
}

Ответ 3

Я больше парень while - вы можете преобразовать его в for, я уверен,

i = j = 0;
while (dataSize--) {
    dst[i++] = src[j++]; /* R */
    dst[i++] = src[j++]; /* G */
    j += 2;              /* ignore B and A */
}

Что касается того, что он быстрее, вы должны измерить.

Ответ 4

Как всегда загрузка и хранение - самые дорогие операции. Вы можете оптимизировать свой код следующим образом:

  • Загрузите один int (RGBA)
  • Сохраните требуемую часть в регистре (временная переменная)
  • Переместите данные в нужное место в переменной temp.
  • Сделайте это до тех пор, пока не будет заполнен размер данных родного прозерцателя (4 раза для символов на 32-битной машине).
  • сохранить временную переменную в памяти.

Код просто быстро набирается, чтобы передать идею.

unsigned int tmp;
unsigned int *dest;

for(int i=0; i<dataSize; i+=4){
    tmp  = (source[i] & 0xFF);
    tmp |= (source[i+1] & 0xFF) << 8;
    tmp |= (source[i+2] & 0xFF) << 16;
    tmp |= (source[i+3] & 0xFF) << 24;

    *dest++ = tmp;
}

Ответ 5

Ответ от Роджера, вероятно, является самым чистым решением. Всегда полезно иметь библиотеку, чтобы ваш код был небольшим. Но если вы хотите оптимизировать код C, вы можете попробовать разные вещи. Сначала вы должны проанализировать, насколько велик ваш dataSize. Затем вы можете выполнять разворот тяжелых циклов, возможно, в сочетании с копированием int вместо байтов: (псевдокод)

while(dataSize-i > n) { // n being 10 or whatever
   *(int*)(src+i) = *(int*)(dest+i); i++; // or i+=4; depending what you copy
   *(int*)(src+i) = *(int*)(dest+i);
   ... n times
}

а затем выполните остальные действия:

switch(dataSize-i) {
    case n-1: *(src+i) = *(dest+i); i++;
    case n-2: ...
    case 1: ...
}

он становится немного уродливым.. но он уверен, что это быстро:)

вы можете оптимизировать еще больше, если знаете, как ведет себя dataSize. Может быть, это всегда сила 2? Или четное число?


Я просто понял, что вы не можете сразу копировать 4 байта:), но всего 2 байта. Во всяком случае, я просто хотел показать вам, как закончить развернутый цикл с помощью оператора switch только с 1 сравнением. ИМО - единственный способ получить достойное ускорение.

Ответ 6

Является ли ваш вопрос актуальным? Я опубликовал мою ASM-ускоренную функцию для копирования байтов с шагом несколько дней назад. Это примерно в два раза быстрее, чем соответствующий код C. Вы можете найти его здесь: https://github.com/noveogroup/ios-aux Его можно изменить, чтобы скопировать слова в случае копирования RG-байтов.

UPD: Я обнаружил, что мое решение быстрее, чем C-код, только в режиме отладки, когда оптимизация компилятора отключена по умолчанию. В режиме освобождения C-код оптимизирован (по умолчанию) и работает так же быстро, как мой ASM-код.

Ответ 7

Вам комфортно с ASM? Я не знаком с процессорами ARM, но на Blackfin Analog Devices эта копия фактически БЕСПЛАТНА, так как это можно сделать параллельно с вычислительной операцией:

i0 = _src_addr;
i1 = _dest_addr;
p0 = dataSize - 1;

r0 = [i0++];
loop _mycopy lc0 = p0;
loop_begin _mycopy;
    /* possibly do compute work here | */ r0 = [i0++] | W [i1++] = r0.l;
loop_end _mycopy;
W [i1++] = r0.l;

Итак, у вас есть 1 цикл на пиксель. Обратите внимание, что как есть, это полезно для копии RG или BA. Как я уже сказал, я не знаком с ARM и абсолютно ничего не знаю об iOS, поэтому я не уверен, что у вас даже есть доступ к коду ASM, но вы можете попытаться найти такие оптимизации.

Ответ 8

Надеюсь, я не опоздал на вечеринку! Я только что сделал что-то подобное на iPad, используя ARM NEON intrinsics. Я получаю 2-3-кратное ускорение по сравнению с другими перечисленными ответами. Обратите внимание, что приведенный ниже код сохраняет только первый канал и требует, чтобы данные были кратными 32 байтам.

uint32x4_t mask = vdupq_n_u32(0xFF);

for (unsigned int i=0, j=0; i < dataSize; i+=32, j+=8) {

    // Load eight 4-byte integers from the source
    uint32x4_t vec0 = vld1q_u32((const unsigned int *) &source[i]);
    uint32x4_t vec1 = vld1q_u32((const unsigned int *) &source[i+16]);

    // Zero everything but the first byte in each of the eight integers
    vec0 = vandq_u32(vec0, mask);
    vec1 = vandq_u32(vec1, mask);

    // Throw away two bytes for each of the original integers
    uint16x4_t vec0_s = vmovn_u32(vec0);
    uint16x4_t vec1_s = vmovn_u32(vec1);

    // Combine the remaining bytes into a single vector
    uint16x8_t vec01_s = vcombine_u16(vec0_s, vec1_s);

    // Throw away the last byte for each of the original integers
    uint8x8_t vec_o = vmovn_u16(vec01_s);

    // Store to destination
    vst1_u8(&dest[j], vec_o);
}