Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

Внутренние функции компилятора GCC для обработки данных в векторной форме

Авторы: George Koharchik, Kathy Jones, Перевод: А.Панин
Это продолжение статьи. Начало смотри здесь.

Типы векторов, компилятор и отладчик

Для того, чтобы использовать аппаратное обеспечение для работы с векторами, необходимо сообщить компилятору об использовании внутренних функций для генерации SIMD-кода, подключить заголовочный файл, задающий типы векторов и использовать векторный тип для преобразования ваших данных в векторную форму.

Примеры параметров командной строки для использования SIMD-инструкций приведены в таблице 1. (Эта статья описывает только те параметры, что приведены в таблице, а GCC позволяет использовать гораздо большее их количество.)

Таблица 1. Параметры командной строки GCC для генерации SIMD-кода
Процессор Параметры
X86/MMX/SSE1/SSE2 -mfpmath=sse -mmmx -msse -msse2
ARM Neon -mfpu=neon -mfloat-abi=softfp
Freescale Altivec -maltivec -mabi=altivec
Необходимые заголовочные файлы:
  • arm_neon.h - ARM Neon (типы данных и прототипы внутренних функций)
  • altivec.h - Freescale Altivec (типы данных и прототипы внутренних функций)
  • mmintrin.h - X86 MMX
  • xmmintrin.h - X86 SSE1
  • emmintrin.h - X86 SSE2

X86: MMX, SSE, SSE2 типы данных и отладка

Архитектура X86 совместима с технологиями MMX, SSE1 и SSE2, поддерживающими следующие типы данных:
  • MMX: __m64 64 бита для хранения целочисленных значений, разделенные на восемь 8-битных целочисленных значений, четыре 16-битных значения или два 32-битных значения.
  • SSE1: __m128 128 бит для хранения четырех чисел с плавающей точкой одинарной точности.
  • SSE2: __m128i 128 бит для хранения упакованных целых чисел любого размера, __m128d 128 бит для хранения двух чисел с плавающей точкой двойной точности.
По той причине, что отладчик не располагает информацией о том, как вы используете эти типы, вывод значений переменных векторов на архитектуре X86 в отладчиках gdb/ddd представлен в упакованной форме вместо списка элементов. Для получения значений каждого из элементов, необходимо сообщить отладчику информацию о том, как декодировать упакованную форму при помощи команды "print (type[]) x". Например, если вы используете переменную:
__m64 avariable; /* хранит 4 значения типа short */
Вы можете сообщить ddd о том, что нужно вывести значения элементов типа short при помощи команды:
print (short[]) avariable
Если вы работаете с вектором, состоящим из элементов типа char и хотите, чтобы при отладке gdb выводил элементы вектора в виде чисел, а не в виде символов, вы можете сделать это при помощи параметра "/". Например:
print/d acharvector

Эта команда позволяет вывести значение переменной acharvector в виде серии значений в десятичной системе счисления.

PowerPC Altivec: типы данных и отладка

Процессоры PowerPC, поддерживающие технологию Altivec (также известную как VMX и Velocity), используют типы данных с добавленным ключевым словом "vector". Все типы данных имеют размер 16 байт. Ниже приведены некоторые типы векторов Altivec:
  • vector unsigned char: вектор из 16 беззнаковых однобайтных значений
  • vector signed char: вектор из 16 знаковых однобайтных значений
  • vector bool char: вектор из 16 беззнаковых однобайтных значений, трактующихся как логические (0 false, 255 true)
  • vector unsigned short: вектор из 8 беззнаковых двухбайтных значений
  • vector unsigned short: вектор из 8 беззнаковых двухбайтных значений
  • vector bool short: вектор из 8 беззнаковых двухбайтных значений, трактующихся как логические (0 false, 65535 true)
  • vector unsigned int: вектор из 4 беззнаковых четырехбайтных значений
  • vector signed int: вектор из 4 знаковых четырехбайтных значений
  • vector bool int: вектор из 4 беззнаковых четырехбайтных значений, трактующихся как логические (0 false, 2^32-1 true)
  • vector float: вектор из 4 значений с плавающей точкой

Отладчик выводит эти векторы в виде наборов значений отдельных элементов.

ARM Neon: типы данных и отладка

В процессорах ARM, поддерживающих расширения Neon, поддерживается обработка типов данных, названия которых соответствуют шаблону: [тип_элемента]x[количество_элементов]_t. Эти типы данных включают в себя приведенные в списке:
  • uint64x1_t - одно 64-битное беззнаковое целочисленное значение
  • uint32x2_t - два 32-битных беззнаковых целочисленных значения
  • uint16x4_t - четыре 16-битных беззнаковых целочисленных значения
  • uint8x8_t - восемь 8-битных беззнаковых целочисленных значений
  • int32x2_t - два 32-битных знаковых целочисленных значения
  • int16x4_t - четыре 16-битных знаковых целочисленных значения
  • int8x8_t - восемь 8-битных знаковых целочисленных значений
  • int64x1_t - одно 64-битное знаковое целочисленное значение
  • float32x2_t - два 32-битных значения с плавающей точкой
  • uint32x4_t - четыре 32-битных беззнаковых целочисленных значения
  • uint16x8_t - восемь 16-битных беззнаковых целчисленных значений
  • uint8x16_t - шестнадцать 8-битных беззнаковых целочисленных значений
  • int32x4_t - четыре 32-битных знаковых целочисленных значения
  • int16x8_t - восемь 16-битных знаковых целочисленных значений
  • int8x16_t - шестнадцать 8-битных знаковых целочисленных значений
  • uint64x2_t - два 64-битных беззнаковых целочисленных значения
  • int64x2_t - два 64-битных знаковых целочисленныхзначения
  • float32x4_t - четыре 32-битных значения с плавающей точкой
  • uint32x4_t - четыре 32-битных беззнаковых целочисленных значения
  • uint16x8_t - восемь 16-битных беззнаковых целочисленных значений

Отладчик выводит эти векторы как наборы отдельных элементов.

В директории samples/simple вы найдете ряд примеров использования этих типов данных.

Теперь, когда мы рассмотрели типы данных векторов, давайте поговорим о разработке программ для работы с векторами.

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

Выравнивание данных является еще одним аспектом, за которым нужно следить при перемещении данных. Используйте атрибут GCC "aligned" для выравнивания исходных и целевых участков с данными в 16-битных границах для улучшения производительности. Например:
float anarray[4] __attribute__((aligned(16))) = { 1.2, 3.5, 1.7, 2.8 };

Невозможность выравнивания данных может привести к получению верного результата, получению неверного результата без сообщения об ошибке или краху приложения. Существуют техники для работы с невыровненными данными, но они приводят к снижению производительности по сравнению с обработкой выровненных данных. Вы можете найти примеры в архиве с исходными кодами.

В коде примеров использованы внутренние функции компилятора для X86, Altivec и Neon. Эти функции используют политику наименований для удобства расшифровки их назначения. Подробнее об этой политике наименований:

Внутренние функции Altivec используют префикс "vec_". Перегрузка функций в стиле C++ позволяет им принимать аргументы различных типов.

Внутренние функции Neon используют названия в соответствии с шаблоном [операция][флаги]_[тип]. Флаг "q" обозначает, что функция работает с векторами из четырех значений общей длиной в 128 бит ("quad word").

Внутренние функции X86 используют шаблон _mm_[операция]_[суффикс].
    суффикс   s число с плавающей точкой одинарной точности
              d число с плавающей точкой двойной точности
              i128 знаковое 128-битное целое число
              i64 знаковое 64-битное целое число
              u64 беззнаковое 64-битное целое число
              i32 знаковое 32-битное целое число
              u32 беззнаковое 32-битное целое число
              i16 знаковое 16-битное целое число
              u16 беззнаковое 16-битное целое число
              i8 знаковое 8-битное целое число
              u8 беззнаковое 8-битное целое число
              pi# 64-битный вектор из упакованных #-битных целых чисел
              pu# 64- битный вектор из упакованных #-битных беззнаковых целых чисел
              epi# 128- битный вектор из упакованных #-битных беззнаковых целых чисел
              epu# 128- битный вектор из упакованных #-битных беззнаковых целых чисел
              ps 128- битный вектор из упакованных чисел с плавющей точкой одинарной точности
              ss 128-битный вектор из одного числа с плавающей точкой одинарной точности
              pd 128-битный вектор из чисел с плавающей точкой двойной точности
              sd 128-битный вектор из одного числа с плавающей точкой (128-битного) двойной точности 
              si64 64-битный вектор из одного 64-битного целого числа
              si128 128-битный вектор

В таблице 2 приведены внутренние функции, использованные в коде примеров.

Таблица 2. Часть внутренних функций компилятора для обработки векторов в примерах.
Операция Altivec Neon MMX/SSE/SSE2
Загрузка данных из памяти в вектор vec_ld vld1q_f32 _mm_set_epi16
vec_splat vld1q_s16 _mm_set1_epi16
vec_splat_s16 vsetq_lane_f32 _mm_set1_pi16
vec_splat_s32 vld1_u8 _mm_set_pi16
vec_splat_s8 vdupq_lane_s16 _mm_load_ps
vec_splat_u16 vdupq_n_s16 _mm_set1_ps
vec_splat_u32 vmovq_n_f32 _mm_loadh_pi
vec_splat_u8 vset_lane_u8 _mm_loadl_pi
Перемещение данных из вектора в память vec_st vst1_u8
vst1q_s16 _mm_store_ps
vst1q_f32
vst1_s16
Сложение векторов vec_madd vaddq_s16 _mm_add_epi16
vec_mladd vaddq_f32 _mm_add_pi16
vec_adds vmlaq_n_f32 _mm_add_ps
Вычитание векторов vec_sub vsubq_s16
Умножение векторов vec_madd vmulq_n_s16 _mm_mullo_epi16
vec_mladd vmulq_s16 _mm_mullo_pi16
vmulq_f32 _mm_mul_ps
vmlaq_n_f32
Арифметический сдвиг vec_sra vshrq_n_s16 _mm_srai_epi16
vec_srl _mm_srai_pi16
vec_sr
Байтовые перестановки vec_perm vtbl1_u8 _mm_shuffle_pi16
vec_sel vtbx1_u8 _mm_shuffle_ps
vec_mergeh vget_high_s16
vec_mergel vget_low_s16
vdupq_lane_s16
vdupq_n_s16
vmovq_n_f32
vbsl_u8
Конвертация типов данных vec_cts vmovl_u8 _mm_packs_pu16
vec_unpackh vreinterpretq_s16_u16
vec_unpackl vcvtq_u32_f32
vec_cts vqmovn_s32 _mm_cvtps_pi16
vec_ctu vqmovun_s16 _mm_packus_epi16
vqmovn_u16
vcvtq_f32_s32
vmovl_s16
vmovq_n_f32
Комбинирование векторов vec_pack vcombine_u16
vec_packsu vcombine_u8
vcombine_s16
Максимальное значение _mm_max_ps
Минимальное значение _mm_min_ps
Логические операции над векторами и _mm_andnot_ps
_mm_and_ps
_mm_or_ps
Округление значений vec_trunc
Разное _mm_empty

Это продолжение статьи. Вернуться к началу. Перейти к следующей части.