Оптимизация преобразования RGBA8888 в RGB565 с помощью NEON - PullRequest
8 голосов
/ 10 октября 2011

Я пытаюсь оптимизировать преобразование формата изображения на iOS, используя набор векторных инструкций NEON.Я предположил, что это хорошо согласуется с этим, потому что он обрабатывает кучу похожих данных.

Мои попытки, однако, не увенчались успехом, достигнув лишь незначительного ускорения по сравнению с наивной реализацией c:

for(int i = 0; i < pixelCount; ++i, ++inPixel32) {
    const unsigned int r = ((*inPixel32 >> 0 ) & 0xFF);
    const unsigned int g = ((*inPixel32 >> 8 ) & 0xFF);
    const unsigned int b = ((*inPixel32 >> 16) & 0xFF);
    *outPixel16++ = ((r >> 3) << 11) | ((g >> 2) << 5) | ((b >> 3) << 0);
}

1-мегапиксельный массив изображений на iPad 2:

формат: [min avg max n = количество выборок таймера] в миллисекундах

C: [14,446 14,632 18,405 n = 1000] мс

NEON: [11,920 12,032 15,336 n = 1000] мс

Моя попытка реализации NEON приведена ниже:

    int i;
const int pixelsPerLoop = 8;
for(i = 0; i < pixelCount; i += pixelsPerLoop, inPixel32 += pixelsPerLoop, outPixel16 += pixelsPerLoop) {
    //Read all r,g,b pixels into 3 registers
    uint8x8x4_t rgba  = vld4_u8(inPixel32);
    //Right-shift r,g,b as appropriate
    uint8x8_t r = vshr_n_u8(rgba.val[0], 3);
    uint8x8_t g = vshr_n_u8(rgba.val[1], 2);
    uint8x8_t b = vshr_n_u8(rgba.val[2], 3);

    //Widen b
    uint16x8_t r5_g6_b5 = vmovl_u8(b);
    //Widen r
    uint16x8_t r16 = vmovl_u8(r);
    //Left shift into position within 16-bit int
    r16 = vshlq_n_u16(r16, 11);
    r5_g6_b5 |= r16;

    //Widen g
    uint16x8_t g16 = vmovl_u8(g);
    //Left shift into position within 16-bit int
    g16 = vshlq_n_u16(g16, 5);

    r5_g6_b5 |= g16;

    //Now write back to memory
    vst1q_u16(outPixel16, r5_g6_b5);        
}
//Do the remainder on normal flt hardware

Код был скомпилирован с помощью LLVM 3.0 в следующую(.loc и лишние метки удалены):

_DNConvert_ARGB8888toRGB565:
    push    {r4, r5, r7, lr}
    mov r9, r1
    mov.w   r12, #0
    add r7, sp, #8
    cmp r2, #0
    mov.w   r1, #0
    it  ne
    movne   r1, #1
    cmp r0, #0
    mov.w   r3, #0
    it  ne
    movne   r3, #1
    cmp.w   r9, #0
    mov.w   r4, #0
    it  ne
    movne   r4, #1
    tst.w   r9, #3
    bne LBB0_8
    ands    r1, r3
    ands    r1, r4
    cmp r1, #1
    bne LBB0_8
    movs    r1, #0
    lsr.w   lr, r9, #2
    cmp.w   r1, r9, lsr #2
    bne LBB0_9
    mov r3, r2
    mov r5, r0
    b   LBB0_5
LBB0_4:
    movw    r1, #65528
    add.w   r0, lr, #7
    movt    r1, #32767
    ands    r1, r0
LBB0_5:
    mov.w   r12, #1
    cmp r1, lr
    bhs LBB0_8
    rsb r0, r1, r9, lsr #2
    mov.w   r9, #63488
    mov.w   lr, #2016
    mov.w   r12, #1
LBB0_7:
    ldr r2, [r5], #4
    subs    r0, #1
    and.w   r1, r9, r2, lsl #8
    and.w   r4, lr, r2, lsr #5
    ubfx    r2, r2, #19, #5
    orr.w   r2, r2, r4
    orr.w   r1, r1, r2
    strh    r1, [r3], #2
    bne LBB0_7
LBB0_8:
    mov r0, r12
    pop {r4, r5, r7, pc}
LBB0_9:
    sub.w   r1, lr, #1
    movs    r3, #32
    add.w   r3, r3, r1, lsl #2
    bic r3, r3, #31
    adds    r5, r0, r3
    movs    r3, #16
    add.w   r1, r3, r1, lsl #1
    bic r1, r1, #15
    adds    r3, r2, r1
    movs    r1, #0
LBB0_10:
    vld4.8  {d16, d17, d18, d19}, [r0]!
    adds    r1, #8
    cmp r1, lr
    vshr.u8 d20, d16, #3
    vshr.u8 d21, d17, #2
    vshr.u8 d16, d18, #3
    vmovl.u8    q11, d20
    vmovl.u8    q9, d21
    vmovl.u8    q8, d16
    vshl.i16    q10, q11, #11
    vshl.i16    q9, q9, #5
    vorr    q8, q8, q10
    vorr    q8, q8, q9
    vst1.16 {d16, d17}, [r2]!
Ltmp28:
    blo LBB0_10
    b   LBB0_4

Полный код доступен на https://github.com/darknoon/DNImageConvert Буду признателен за любую помощь, спасибо!

Ответы [ 5 ]

10 голосов
/ 01 ноября 2011

Вот вам оптимизированная для рук реализация NEON для XCode:

/* IT DOESN'T WORK!!! USE THE NEXT VERSION BELOW.
 * BGRA2RGB565.s
 *
 * Created by Jake "Alquimista" Lee on 11. 11. 1..
 * Copyright 2011 Jake Lee. All rights reserved.
 */


    .align 2
    .globl _bgra2rgb565_neon
    .private_extern _bgra2rgb565_neon

// unsigned int * bgra2rgb565_neon(unsigned int * pDst, unsigned int * pSrc, unsigned int count);


//ARM
pDst        .req    r0
pSrc        .req    r1
count       .req    r2

//NEON
blu         .req    d16
grn         .req    d17
red         .req    d18
alp         .req    d19
rg          .req    red
gb          .req    blu

_bgra2rgb565_neon:
    pld     [pSrc]
    tst     count, #0x7
    movne   r0, #0
    bxne    lr

loop:
    pld     [pSrc, #32]
    vld4.8  {blu, grn, red, alp}, [pSrc]!
    subs    count, count, #8
    vshr.u8 red, red, #3
    vext.8  rg, grn, red, #5
    vshr.u8 grn, grn, #2
    vext.8  gb, blu, grn, #3
    vst2.8  {gb, rg}, [pDst]!
    bgt     loop

    bx      lr

Эта версия будет во много раз быстрее, чем вы предлагали:

  • увеличена частота попаданий в кеш через PLD

  • преобразование в "long" необязательно

  • меньше инструкций в цикле

Тем не менее, есть место для оптимизации, вы можете изменить цикл так, чтобы он преобразовывал 16 пикселей за итерацию вместо 8. Затем вы можете запланировать инструкции, чтобы полностью избежать двух срывов (что просто невозможно в этой 8 / итерационной версии выше) и дополнительно воспользоваться возможностью двойного выпуска NEON.

Я этого не делал, потому что это усложнило бы понимание кода.

Важно знать, что должен делать VEXT.

Теперь решать вам. :)

Я подтвердил, что этот код правильно скомпилирован в Xcode. Хотя я почти уверен, что он работает правильно, я не могу этого гарантировать, поскольку у меня нет тестовой среды. В случае неисправности, пожалуйста, дайте мне знать. Тогда я исправлю это соответственно.

суа

=============================================== ===============================

Ну вот и улучшенная версия.

Из-за того, что инструкция VSRI не допускает двух операндов, кроме целевого, было невозможно создать более надежный в отношении назначения регистра.

Пожалуйста, проверьте формат изображения вашего исходного изображения. (точный порядок байтов элементов)

Если это не B, G, R, A, который является стандартным и стандартным для iOS, ваше приложение сильно пострадает от внутренних преобразований iOS.

Если это абсолютно невозможно изменить по какой-либо причине, дайте мне знать. Я напишу новую версию, соответствующую ей.

PS: я забыл удалить подчеркивание в начале прототипа функции. Теперь это прошло.

/*
 * BGRA2RGB565.s
 *
 * Created by Jake "Alquimista" Lee on 11. 11. 1..
 * Copyright 2011 Jake Lee. All rights reserved.
 *
 * Version 1.1
 * - bug fix
 *
 * Version 1.0
 * - initial release
 */


    .align 2
    .globl _bgra2rgb565_neon
    .private_extern _bgra2rgb565_neon

// unsigned int * bgra2rgb565_neon(unsigned int * pDst, unsigned int * pSrc, unsigned int count);


//ARM
pDst        .req    r0
pSrc        .req    r1
count       .req    r2

//NEON
blu         .req    d16
grn         .req    d17
red         .req    d18
alp         .req    d19

gb          .req    grn
rg          .req    red

_bgra2rgb565_neon:
    pld     [pSrc]
    tst     count, #0x7
    movne   r0, #0
    bxne    lr

.loop:
    pld     [pSrc, #32]
    vld4.8  {blu, grn, red, alp}, [pSrc]!
    subs    count, count, #8

    vsri.8  red, grn, #5
    vshl.u8 gb, grn, #3
    vsri.8  gb, blu, #3

    vst2.8  {gb, rg}, [pDst]!
    bgt     .loop

    bx      lr
1 голос
/ 21 января 2014

Если вы работаете на iOS или OS X, вы можете быть рады обнаружить vImageConvert_RGBA8888toRGB565 () и друзей в Accelerate.framework. Эта функция округляет 8-битные значения до ближайшего значения 565.

Для еще лучшего сглаживания, качество которого практически не отличается от 8-битного цвета, попробуйте vImageConvert_AnyToAny ():

vImage_CGImageFormat RGBA8888Format = 
{
    .bitsPerComponent = 8,
    .bitsPerPixel = 32,
    .bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaNoneSkipLast,
    .colorSpace = NULL,  // sRGB or substitute your own in
};

vImage_CGImageFormat RGB565Format = 
{
    .bitsPerComponent = 5,
    .bitsPerPixel = 16,
    .bitmapInfo = kCGBitmapByteOrder16Little | kCGImageAlphaNone,
    .colorSpace = RGBA8888Format.colorSpace,  
};


err = vImageConverterRef converter = vImageConverter_CreateWithCGImageFormat(
         &RGBA8888Format, &RGB565Format, NULL, kvImageNoFlags, &err );

err = vImageConvert_AnyToAny( converter, &src, &dest, NULL, kvImageNoFlags );

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

0 голосов
/ 14 октября 2011

(Я не знаком ни с NEON, ни с системой памяти Ipad2, но это то, что мы привыкли делать с 88110 pixel-ops, которые были ранним предшественником современных расширений SIMD)

Насколько велика задержка памяти?

Не могли бы вы скрыть это, развернув внутренний цикл и выполнив инструкции NEON для «предыдущих» значений, пока ARM извлекает «следующие» значения из памяти? Краткое сканирование руководства NEON подразумевает, что вы можете выполнять инструкции ARM и NEON параллельно.

0 голосов
/ 18 октября 2011

Я не думаю, что преобразование vld4_u8 в vld4q_u8 приведет к какому-либо улучшению производительности.

Код кажется достаточно простым. Я не очень хорош в ASM, и поэтому мне понадобится время, чтобы глубоко в него разобраться.

Неон кажется достаточно простым. Но я не уверен, что r5_g6_b5 | = g16 используется вместо vorrq_u16

Пожалуйста, обратите внимание и на уровень оптимизации. Насколько я слышал, уровень оптимизации неонового кода достигает максимума 1. Таким образом, производительность может отличаться, если оптимизация по умолчанию принимается во внимание как для ссылочного кода, так и для неонового кода, поскольку уровень оптимизации эталона с помощью DEFAULT может быть отличается.

Я не могу найти ни одной области в неоне, которая могла бы улучшить текущий код.

0 голосов
/ 14 октября 2011

Возможно, вы захотите использовать vld4q_u8 () вместо vld4_u8 () и соответствующим образом изменить оставшуюся часть кода. Трудно сказать, где может быть проблема, но в противном случае ассемблер выглядит не так уж плохо.

...