Использование C++ для низкоуровневой...

26
Использование C++ для низкоуровневой платформозависимой разработки Cyril Lashkevich Viber, Core team C++party, 15.04.15

Transcript of Использование C++ для низкоуровневой...

Page 1: Использование C++ для низкоуровневой платформозависимой разработки — Кирилл Лашкевич

Использование C++ для низкоуровневой

платформозависимой разработки

Cyril Lashkevich Viber, Core team

C++party, 15.04.15

Page 2: Использование C++ для низкоуровневой платформозависимой разработки — Кирилл Лашкевич

Core development• Большая скучная библиотека для всего в 800 тыс строк

• Код обработки звука и видео оптимизированный под платформы:

✴ armv6 (Android)

✴ armv7 (Android, iOS, WP)

✴ arm64 (Android, iOS)

✴ x86, x86_64 (Android, WP)

Page 3: Использование C++ для низкоуровневой платформозависимой разработки — Кирилл Лашкевич

Возможности аппаратных платформ• Media DSP (armv6, armv7)

• NEON SIMD (armv7, arm64)

• SSE SIMD (x86, x86_64)

• AES, SHA (armv7s, arm64, x86_64)

• Синхронизация

• Разное (clz, coprocessor)

Page 4: Использование C++ для низкоуровневой платформозависимой разработки — Кирилл Лашкевич

Доступ к этим возможностям

• Реализация на ассемблере

• Использование ключевого слова asm в С/С++

• Использование builtins

• Использование intrinsics

• Использование возможностей оптимизации в компиляторе -fvectorize

Page 5: Использование C++ для низкоуровневой платформозависимой разработки — Кирилл Лашкевич

Реализация функций на asm✓ Можно почуствовать себя крутым хакером

- Сложно написать код оптимальнее чем компилятор, особенно для ARM

- Не переносимый между компиляторами способ, у каждого ассемблера свой синтаксис и нюансы (реализация в 1 синтаксе + скрипты конвертации)

- Абсолютно не переносимо между архитектурами

- Проблемы с поддержкой кода в проекте

Page 6: Использование C++ для низкоуровневой платформозависимой разработки — Кирилл Лашкевич
Page 7: Использование C++ для низкоуровневой платформозависимой разработки — Кирилл Лашкевич

Ключевое слово asm✓ Подходит для оборачивания отдельных инструкций в функции static inline int16_t SubSatW16(int16_t var1, int16_t var2) {

int32_t s_sub = 0; asm ("qsub16 %0, %1, %2":"=r"(s_sub):"r"(var1), "r"(var2)); return (int16_t)s_sub; }

- Не переносимо между компиляторами и платформами

- Ограничивает возможности оптимизации для компилятора

- Сложно писать, отлаживать и поддерживать

Page 8: Использование C++ для низкоуровневой платформозависимой разработки — Кирилл Лашкевич

{ register int tmp_coeff0; register int tmp_coeff1; __asm __volatile( "ldr %[tmp_coeff0], [%[coeff]]\n\t" "ldr %[tmp_coeff1], [%[coeff], #4]\n\t" "smmulr %[a2], %[tmp_coeff0], %[state0]\n\t" "smmulr %[b2], %[tmp_coeff1], %[state1]\n\t" "ldr %[tmp_coeff0], [%[coeff], #8]\n\t" "ldr %[tmp_coeff1], [%[coeff], #12]\n\t" "smmulr %[a1], %[tmp_coeff0], %[state0]\n\t" "smmulr %[b1], %[tmp_coeff1], %[state1]\n\t" :[a2]"=&r"(a2), [b2]"=&r"(b2), [a1]"=&r"(a1), [b1]"=r"(b1), [tmp_coeff0]"=&r"(tmp_coeff0), [tmp_coeff1]"=&r"(tmp_coeff1) :[coeff]"r"(coefficient), [state0]"r"(state0), [state1]"r"(state1) ); }

float foo(float f, float g) { float h; __asm { VADD h, f, 0.5*g; // h = f + 0.5*g } return h; }

Page 9: Использование C++ для низкоуровневой платформозависимой разработки — Кирилл Лашкевич

Builtins

✓ Кроссплатформенный доступ к некотороым низкоуровневым возможностям

✓ Проблема переносимости между компиляторами легко решается

- Очень мало возможностей:clz, ctz, popcount, bswap, prefetch, expect Interlocked…

Page 10: Использование C++ для низкоуровневой платформозависимой разработки — Кирилл Лашкевич

Intrinsics✓ Код пишется на С++/С, не надо думать о размещений регистров

✓ Все возможности аппаратной платформы доступны

✓ У компилятора есть много возможностей для оптимизации

✓ Хорошо переносимо между компиляторами и даже архитектурами (ARM C Language Extensions standart): armv7, arm64 + x86/x86_64 с адаптером

- Код выглядит как последовательность вызовов функций, где каждая функция соответсвует одной ассемблерной инструкции

Page 11: Использование C++ для низкоуровневой платформозависимой разработки — Кирилл Лашкевич

void vp8_dequantize_b_neon(BLOCKD *d, short *DQC) { int16x8x2_t qQ, qDQC, qDQ;

qQ = vld2q_s16(d->qcoeff); qDQC = vld2q_s16(DQC);

qDQ.val[0] = vmulq_s16(qQ.val[0], qDQC.val[0]); qDQ.val[1] = vmulq_s16(qQ.val[1], qDQC.val[1]);

vst2q_s16(d->dqcoeff, qDQ); }

Page 12: Использование C++ для низкоуровневой платформозависимой разработки — Кирилл Лашкевич

static inline uint8x8_t mul_gf_8x8(poly8x8_t a, poly8x8_t b) { const poly8x8_t Pp = vdup_n_p8(0x1D); const uint8x8_t low4mask = vdup_n_u8(0x0F); const uint8x8_t high4mask = vdup_n_u8(0xF0); uint16x8_t mul = vreinterpretq_u16_p16(vmull_p8(a, b)); uint8x8_t c12 = vshrn_n_u16(mul, 8); c12 = vand_u8(c12, high4mask); const poly16x8_t phigh = vmull_p8(vreinterpret_p8_u8(c12), Pp); mul = veorq_u16(mul, vreinterpretq_u16_p16(phigh)); const poly8x8_t dhigh = vreinterpret_p8_u8( vand_u8(vshrn_n_u16(mul, 8), low4mask)); const poly8x8_t plow = vmul_p8(dhigh, Pp); uint8x8_t res = vmovn_u16(mul); res = veor_u8(res, plow); return res; }

Page 13: Использование C++ для низкоуровневой платформозависимой разработки — Кирилл Лашкевич

-fvectorize

✓ Компилятор все делает сам

- Не работает

Page 14: Использование C++ для низкоуровневой платформозависимой разработки — Кирилл Лашкевич

Процесс оптимизации

Clang NEON

intrinsicsC/C++

implementation

Page 15: Использование C++ для низкоуровневой платформозависимой разработки — Кирилл Лашкевич

Массивы переменной длиныFILE * concat_fopen (char *s1, char *s2, char *mode) { char str[strlen (s1) + strlen (s2) + 1]; strcpy (str, s1); strcat (str, s2); return fopen (str, mode); }

• Появились в C99 и C++11, но с разными возможностями

• В C99 sizeof для них выполняется в runtime

Page 16: Использование C++ для низкоуровневой платформозависимой разработки — Кирилл Лашкевич

Массивы переменной длины

✓ Все временные массивы которые имеет смысл выделить на стеке

✓ Хорошо оптимизируются компилятором

✓ alloca как замена

void tester (int len; char data[len][len], int len) { /* ... */ }

Page 17: Использование C++ для низкоуровневой платформозависимой разработки — Кирилл Лашкевич

Ключевое слово restrict

• Модификатор для объявления указателей и методов

• Гарантия от программиста компилятору, что указатель указывает на уникальный блок памяти

• Очень помогает компилятору в оптимизации

- При неправильном использовании UB где угодно

Page 18: Использование C++ для низкоуровневой платформозависимой разработки — Кирилл Лашкевич

Ключевое слово volatile

• Единственное применение lock-free и atomic операции

• Не дает гарантий при использовании в многопоточных программах

• Влияет на тип при специализации шаблона

Page 19: Использование C++ для низкоуровневой платформозависимой разработки — Кирилл Лашкевич

void f(size_t * ptrA, size_t * ptrB, size_t * val) { *ptrA += *val; *ptrB += *val; }

void f(size_t * restrict ptrA, size_t * restrict ptrB, size_t * restrict val) { *ptrA += *val; *ptrB += *val; }

void * memcpy(void *restrict dst, const void *restrict src, size_t n);

void * memmove(void *dst, const void *src, size_t len);

Page 20: Использование C++ для низкоуровневой платформозависимой разработки — Кирилл Лашкевич

Loop unroll

• Основа для использования SIMD unroll для длины SIMD

• Duff’s device для обработки хвостов

register short *to, *from; register count; { register n = (count + 7) / 8; switch (count % 8) { case 0: do { *to = *from++; case 7: *to = *from++; case 6: *to = *from++; case 5: *to = *from++; case 4: *to = *from++; case 3: *to = *from++; case 2: *to = *from++; case 1: *to = *from++; } while (--n > 0); } }

Page 21: Использование C++ для низкоуровневой платформозависимой разработки — Кирилл Лашкевич

Loop unroll C++

template <size_t N> struct uint_{ };   template <size_t N, typename Lambda, typename IterT> inline void unroller(const Lambda& f, const IterT& iter, uint_<N>) { unroller(f, iter, uint_<N-1>()); f(iter + N); }   template <typename Lambda, typename IterT> inline void unroller(const Lambda& f, const IterT& iter, uint_<0>) { f(iter); }

Page 22: Использование C++ для низкоуровневой платформозависимой разработки — Кирилл Лашкевич

Loop unroll uint8x16_t v_cache[cache_size]; uint8x16x2_t uv; #pragma unroll for (int i = 0; i < cache_size; i++) { uv = vld2q_u8(buf); buf += 32; vst1q_u8(ubuf, uv.val[0]); ubuf += 16; v_cache[i] = uv.val[1]; }

Page 23: Использование C++ для низкоуровневой платформозависимой разработки — Кирилл Лашкевич

Шаблоныstatic inline uint8_t* ConvertBiplanarUVtoPlanar(uint8_t *buf,

int height, int stride) { switch (stride) { case 1280: convertAllLines<1280>(buf, height); break; case 960: convertAllLines<960>(buf, height); break; case 720: convertAllLines<720>(buf, height); break; }

static inline uint8_t* ConvertBiplanarUVtoPlanar(uint8_t *buf, int height, int stride) { switch (stride) { case 1280: convertAllLines(1280, buf, height); break; case 960: convertAllLines(1280, buf, height); break; case 720: convertAllLines(1280, buf, height); break; }

Page 24: Использование C++ для низкоуровневой платформозависимой разработки — Кирилл Лашкевич

constexpr• Вычисления на этапе компиляции без выноса мозга

• …почти

• constexpr функции сильно ограничены в c++11

• При невозможности вычисления на этапе компиляции значение будет вычислятся в runtime, без ошибки компиляции

- Параметры функции нельзя пометить как constexpr

- Поддержка в VS пока не полная

Page 25: Использование C++ для низкоуровневой платформозависимой разработки — Кирилл Лашкевич

static constexpr GF generate_gf (void) { GF r = {0, 0, 0}; gf mask = 1; r.exp[8] = 0; for (int i = 0; i < 8; i++, mask <<= 1) { r.exp[i] = mask; r.log[r.exp[i]] = i; if (Pp[i] == '1') r.exp[8] ^= mask; } r.log[r.exp[8]] = 8; mask = 1 << 7; for (int i = 9; i < 255; i++) { r.exp[i] = (r.exp[i - 1] >= mask) ? r.exp[8] ^ ((r.exp[i - 1] ^ mask) << 1) : r.exp[i] = r.exp[i - 1] << 1; r.log[r.exp[i]] = i; } r.log[0] = 255; for (int i = 0; i < 255; i++) r.exp[i + 255] = r.exp[i]; r.inverse[0] = 0; r.inverse[1] = 1; for (int i = 2; i <= 255; i++) r.inverse[i] = r.exp[255 - r.log[i]]; return r; }

Page 26: Использование C++ для низкоуровневой платформозависимой разработки — Кирилл Лашкевич