Самый быстрый способ скопировать память с шагом в C? - PullRequest
12 голосов
/ 27 июня 2011

Я пытаюсь скопировать 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) может быть полезным, к сожалению, у меня нулевой опыт!

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

Ответы [ 8 ]

6 голосов
/ 27 июня 2011

Если у вас все в порядке с 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 канала, используя вышеописанную технику.

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

4 голосов
/ 27 июня 2011

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

  • Загрузить одно целое (RGBA)
  • Сохранить необходимую часть в регистре (временная переменная)
  • Сдвинутьданные в нужное место во временной переменной.
  • Делайте это до тех пор, пока размер данных собственного обработчика не заполнится (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;
}
3 голосов
/ 27 июня 2011

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

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

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

3 голосов
/ 27 июня 2011

В зависимости от скомпилированного кода вы можете заменить умножение на 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];
}
2 голосов
/ 05 сентября 2013

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

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

2 голосов
/ 27 июня 2011

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

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 сравнением.ИМО единственный способ получить приличное ускорение.

1 голос
/ 31 июля 2014

Надеюсь, я не слишком поздно на вечеринку!Я только что выполнил нечто подобное на iPad, используя встроенные функции ARM NEON.Я получаю ускорение в 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);
}
1 голос
/ 27 июня 2011

Вам комфортно с 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, но вы можете попробовать поискать такие оптимизации.

...