Вопрос или проблема
Я работаю над встроенной системой Linux (ядро-5.18.18), и я хочу оптимизировать следующий фрагмент кода с использованием SIMD MIPS (MSA).
#define LV_OPA_MAX 253
typedef uint8_t lv_opa_t;
static inline void lv_color_24_24_mix(const uint8_t * src, uint8_t * dest, uint8_t mix)
{
if(mix == 0) return;
if(mix >= LV_OPA_MAX) {
dest[0] = src[0];
dest[1] = src[1];
dest[2] = src[2];
}
else {
lv_opa_t mix_inv = 255 - mix;
dest[0] = (uint32_t)((uint32_t)src[0] * mix + dest[0] * mix_inv) >> 8;
dest[1] = (uint32_t)((uint32_t)src[1] * mix + dest[1] * mix_inv) >> 8;
dest[2] = (uint32_t)((uint32_t)src[2] * mix + dest[2] * mix_inv) >> 8;
}
}
Я разработал следующий прототип функции, используя встроенные функции SIMD MIPS.
static inline void lv_color_24_24_mix_2(const uint8_t * src, uint8_t * dest, uint8_t mix)
{
#define ALIGN16 __attribute__((aligned(16)))
v4i32 v_mix, v_mix_inv;
v4i32 *vs, *vd;
ALIGN16 uint32_t a[4] = {};
ALIGN16 uint32_t b[4] = {};
v4i32 temp;
register uint32_t *c = &temp;
if(mix == 0) return;
if(mix >= LV_OPA_MAX) {
dest[0] = src[0];
dest[1] = src[1];
dest[2] = src[2];
}
else {
lv_opa_t mix_inv = 255 - mix;
vs = (v4i32 *)a;
vd = (v4i32 *)b;
a[0] = (uint32_t)src[0];
a[1] = (uint32_t)src[1];
a[2] = (uint32_t)src[2];
b[0] = (uint32_t)dest[0];
b[1] = (uint32_t)dest[1];
b[2] = (uint32_t)dest[2];
v_mix = __builtin_msa_fill_w(mix);
v_mix_inv = __builtin_msa_fill_w(mix_inv);
temp = __builtin_msa_mulv_w(*vs, v_mix);
temp = __builtin_msa_maddv_w(*vd, v_mix_inv, temp);
temp = __builtin_msa_srli_w(temp, 8);
dest[0] = c[0];
dest[1] = c[1];
dest[2] = c[2];
}
}
После сборки я сравнил размеры двух файлов и получил следующие данные.
55: 00400740 384 FUNC LOCAL DEFAULT 12 lv_color_24_24_mix
56: 004008c0 492 FUNC LOCAL DEFAULT 12 lv_color_24_24_mix_2
Реализация SIMD больше, чем оригинальная!!! :-(.
И запустив коды 10000 раз в цикле (с помощью gprof
), я получил следующие данные.
Каждая выборка считается 0.01 секунды.
% совокупная собственная собственная общая
время секунды секунды вызовы нс/вызов нс/вызов имя
66.75 0.02 0.02 100000 200.25 200.25 lv_color_24_24_mix_2
33.38 0.03 0.01 100000 100.13 100.13 lv_color_24_24_mix
Это также показало, что версия SIMD НЕ лучше, чем не-SIMD версия.
Так возможно ли оптимизировать коды с помощью SIMD MIPS? Я пропустил какие-то важные встроенные функции MIPS, которые могут помочь мне улучшить код?
Ответ или решение
Для оптимизации представленного кода с использованием SIMD MIPS (MSA) можно рассмотреть несколько аспектов. Основные проблемы с производительностью, отмеченные вами, указывают на то, что SIMD реализация не дает значительного выигрыша, а в некоторых случаях даже увеличивает размер кода, что негативно влияет на его производительность. Давайте рассмотрим оптимизацию вашего кода более подробно.
Problema
Основные проблемы:
- Увеличение размеров кода: SIMD версии часто требуют больше инструкций и могут включать дополнительные механизмы и структуры данных.
- Неэффективное использование инструкций SIMD: Если SIMD инструкции используются неправильно, это может привести к эффективности ниже, чем у обычного кода.
Решение
Для того чтобы улучшить вашу SIMD реализацию, предлагаю несколько шагов:
1. Использование более эффективных загруженных данных
Если данные src и dest загружаются в векторные регистры, убедитесь, что вы используете их компактно. Можно использовать векторные регистры для загрузки сразу всех каналов RGBA src и dest, а затем проводить операции над всеми четырьмя каналами сразу. Это позволит улучшить производительность за счет уменьшения количества операций.
2. Устранение ненужных операций
Проверьте, действительно ли вам нужно использовать __builtin_msa_mulv_w
и __builtin_msa_maddv_w
. Вместо того чтобы использовать сложные операции, вы можете использовать более прямые операции, такие как __builtin_msa_mul_u_h
, когда это возможно, чтобы умножить два 8-битных значения и сохранить результат.
3. Ручное распараллеливание
Если ваш код обрабатывает только один набор пикселей, рассмотрите возможность обработки нескольких наборов пикселей одновременно. То есть, если вы работаете с массивами пикселей, вы можете обрабатывать несколько пикселей за раз, загружая их в регистрах MSA и выполняя операции над ними.
Пример улучшенного кода
Вот улучшенный пример вашего кода, в котором применяются вышеперечисленные идеи:
static inline void lv_color_24_24_mix_optimized(const uint8_t * src, uint8_t * dest, uint8_t mix)
{
if (mix == 0) return;
if (mix >= LV_OPA_MAX) {
dest[0] = src[0];
dest[1] = src[1];
dest[2] = src[2];
return;
}
lv_opa_t mix_inv = 255 - mix;
v16u8 v_src = __builtin_msa_ld_b(src, 0);
v16u8 v_dest = __builtin_msa_ld_b(dest, 0);
v16u8 v_mix = __builtin_msa_fill_b(mix);
v16u8 v_mix_inv = __builtin_msa_fill_b(mix_inv);
v16u8 result = __builtin_msa_mulv_b(v_src, v_mix);
result = __builtin_msa_maddv_b(v_dest, v_mix_inv, result);
result = __builtin_msa_srli_b(result, 8);
__builtin_msa_st_b(result, dest, 0);
}
Дополнительные рекомендации
- Профилирование: Используйте инструменты профилирования, такие как
gprof
, для определения узких мест в коде после каждой оптимизации. - Кэширование: Убедитесь, что доступ к памяти осуществляется эффективно, используя кэширование данных.
Заключение
Оптимизация вашей MIPS SIMD реализации может быть достигнута несколькими способами, включая устранение ненужных операций, эффективное использование векторных регистров и распараллеливание обработки данных. Обязательно периодически профилируйте ваш код, чтобы убедиться, что новые изменения действительно улучшают производительность. применяя вышеуказанные рекомендации, вы можете значительно ускорить вашу реализацию, уменьшив при этом размер кода до оптимального.