Число с плавающей запятой против числа с фиксированной запятой: скорость на процессоре Intel I5

У меня есть программа на C / C ++, которая включает в себя интенсивные математические вычисления 32-битной матрицы с плавающей запятой, такие как сложение, вычитание, умножение, деление и т. Д.

Могу ли я ускорить свою программу, преобразовав 32-битные числа с плавающей запятой в 16-битные числа с фиксированной запятой? Насколько я могу увеличить скорость?

В настоящее время я работаю над процессором Intel I5. Я использую Openblas для выполнения матричных вычислений. Как мне повторно реализовать функции Openblas, такие как cblas_dgemm, для выполнения вычислений с фиксированной точкой?

Я знаю, что SSE (Simple SIMD Extensions) одновременно работает с 4x32 = 8x16 = 128-битными данными, то есть 4 32-битными типами с плавающей запятой или 8 16-битными типами с фиксированной запятой. Я предполагаю, что после преобразования из 32-битной с плавающей точкой в ​​16-битную с фиксированной точкой моя программа будет в два раза быстрее.


person Shawn    schedule 24.09.2016    source источник
comment
Маловероятно, особенно в Haswell и более поздних версиях с его инструкциями FMA с плавающей запятой, если у вас нет очень конкретных вариантов использования, которые могут быть полезны, например, pmaddubsw или PMULHRSW.   -  person Iwillnotexist Idonotexist    schedule 24.09.2016
comment
Рискуя заявить очевидное, можете ли вы получить доступ к графическому процессору? В таком случае вы можете посмотреть github.com/xianyi/clOpenBLAS.   -  person Ahmed Masud    schedule 24.09.2016
comment
@ Iwillnotexist, вы говорите, что 32-битный тип с плавающей запятой и 16-битный тип с фиксированной точкой имеют примерно одинаковую производительность на процессоре Intel 5?   -  person Shawn    schedule 24.09.2016
comment
@Ahmed, у меня нет доступа к GPU в моей программе c / c ++. В любом случае спасибо за указатель.   -  person Shawn    schedule 24.09.2016
comment
Привет, Шон, вот интересный ресурс, на который я наткнулся nicolas.limare.net/pro / notes / 2014/12 / 12_arit_speed; он также ссылается на agner.org/optimize, который может оказаться полезным; снова жаль, что это не прямой ответ и кажется немного тупым :)   -  person Ahmed Masud    schedule 24.09.2016
comment
Еще один интересный ресурс: bitbucket.org/MDukhan/yeppp   -  person Ahmed Masud    schedule 24.09.2016
comment
Если пропускная способность памяти - это все ваше узкое место, то использование данных 1/2 размера сделает их в 2 раза быстрее. Маловероятно, что пропускная способность памяти является единственным ограничивающим фактором. Использование большего количества процессоров, вероятно, еще больше ускорит вашу программу и будет масштабироваться на машинах с большим количеством процессоров, и вы не потеряете столько точности и диапазона, как при использовании 16-битных значений. Вы не предоставили достаточно информации, чтобы быть уверенным, что ваша проблема может быть легко решена параллельно.   -  person doug65536    schedule 24.09.2016
comment
@ doug6556, моя программа будет работать на одном ядре процессора Intel, и моя программа не может быть распараллелена для работы ни на графическом процессоре, ни на нескольких ядрах процессора.   -  person Shawn    schedule 25.09.2016
comment
Посмотрев таблицы Агнера Фога, я могу подтвердить, что на Haswell + float - лучший вариант. Haswell может поддерживать 32 FLOP / цикл / ядро, используя 2 восьмиэлементных векторных FMA / цикл / ядро, но может поддерживать только 1 pmaddubsw или pmulhrsw + 1 paddw / цикл / ядро ​​(2 векторные операции с шестнадцатью элементами = 32 16-битных целочисленных операции / цикл / ядро ​​всего). Таким образом, вы получите гораздо большую точность и меньшую сложность, просто используя OpenBLAS.   -  person Iwillnotexist Idonotexist    schedule 25.09.2016
comment
@IwillnotexistIdonotexist, большое спасибо за ответ. Есть ли у вас какие-нибудь предложения по ускорению моей программы вычислений с плавающей запятой?   -  person Shawn    schedule 25.09.2016
comment
Я не могу этого сделать, если вы не покажете нам свой код в его нынешнем виде, но в целом наиболее требовательной к вычислениям частью программы с плавающей запятой обычно является то, что можно уподобить матрице. -матричное умножение (для которого существуют библиотеки BLAS, OpenBLAS - одна из лучших) или матричная факторизация (для этого есть LAPACK, который использует BLAS или Eigen). На Haswell убедитесь, что используются инструкции FMA.   -  person Iwillnotexist Idonotexist    schedule 25.09.2016
comment
@IwillnotexistIdonotexist, в моем коде я в основном пытаюсь ускорить библиотеку глубокого обучения Caffe, преобразовав 32-битную плавающую точку в 16-битную фиксированную точку. Библиотека глубокого обучения Caffe использует множество cblas_sgemm для выполнения вычисления матрично-матричного произведения.   -  person Shawn    schedule 25.09.2016
comment
Ах, значит, вы тоже знакомы с машинным обучением! Вы могли бы добровольно предложить это заранее (!). Это CNN? Если это так, подумайте о просмотре свертки Винограда и Пакет Intel DNN. Помимо этого, фиксированная точка является активной областью исследований, но процессоры Intel не подходят для этого. Как сказал Дуг, единственный вариант использования, где это действительно выгодно для процессоров Intel, - это если вы испытываете исключительно узкое место в памяти, и даже тогда только <2x. Не убийца.   -  person Iwillnotexist Idonotexist    schedule 25.09.2016


Ответы (1)


Резюме: современное оборудование FPU трудно превзойти с фиксированной точкой, даже если у вас в два раза больше элементов на вектор.

Современные библиотеки BLAS, как правило, очень хорошо настроены для производительности кеша (с блокировкой кеша / мозаикой цикла), а также для пропускной способности инструкций. Поэтому их очень трудно превзойти. В частности, в DGEMM есть много возможностей для такой оптимизации, потому что он выполняет O (N ^ 3) работу с данными O (N ^ 2), поэтому стоит транспонировать только кусок одного ввода размером с кеш и тому подобное.

Что может помочь, так это сокращение узких мест в памяти за счет сохранения ваших чисел с плавающей запятой в 16-битном формате half-float. Аппаратной поддержки для математических вычислений в этом формате нет, есть пара инструкций для преобразования между этим форматом и обычными 32-битными векторами с плавающей запятой при загрузке / хранении: VCVTPH2PS (__m256 _mm256_cvtph_ps(__m128i)) и VCVTPS2PH (__m128i _mm256_cvtps_ph(__m256 m1, const int imm8_rounding_control). Эти две инструкции составляют расширение F16C, впервые поддерживаемое AMD Bulldozer и Intel IvyBridge.

IDK, если какие-либо библиотеки BLAS поддерживают этот формат.


Фиксированная точка:

SSE / AVX не имеет инструкций по целочисленному делению. Однако, если вы делите только на константы, вам может не понадобиться настоящая инструкция div. Так что это главный камень преткновения для фиксированной точки.

Еще одним большим недостатком фиксированной точки является дополнительная стоимость смещения для исправления положения десятичной (двоичной?) Точки после умножения. Это съест любой выигрыш, который вы могли бы получить от вдвое большего количества элементов на вектор с 16-битной фиксированной точкой.

SSE / AVX на самом деле имеет неплохой набор упакованных 16-битных умножений (лучше, чем для любого другого размера элемента). Есть упакованное умножение, производящее младшую половину, высокую половину (со знаком или без знака), и даже такое, которое берет 16 бит из 2 бит ниже верха, с округлением (PMULHRSW.html). Skylake запускает их по два за такт с задержкой в ​​5 циклов. Существуют также целочисленные инструкции умножения-сложения, но они выполняют горизонтальное сложение между парами результатов умножения. (См. таблицы insn Агнера Фога, а также класс x86 тег wiki для ссылок на производительность.) Haswell и предыдущие не имеют такого большого числа операций сложения и умножения целочисленных векторов единицы. Часто узкие места кода связаны с общей пропускной способностью uop, а не с конкретным портом выполнения. (Но в хорошей библиотеке BLAS может даже быть настроенный вручную ассемблер.)

Если ваши входные и выходные данные являются целыми числами, часто быстрее работать с целочисленными векторами вместо преобразования в числа с плавающей запятой. (например, см. мой ответ на Масштабирование байтовых значений пикселей (y = ax + b) с SSE2 (как числа с плавающей запятой)?, где я использовал 16-битную фиксированную точку для работы с 8-битными целыми числами).

Но если вы действительно работаете с числами с плавающей запятой и вам нужно много умножать и делить, просто используйте аппаратные FPU. Они невероятно мощны в современных процессорах и сделали фиксированную точку устаревшей для многих задач. Как указывает @Iwill, инструкции FMA - еще одно большое увеличение пропускной способности FP (а иногда и задержки).


Целочисленные инструкции сложения / вычитания / сравнения (но не умножения) также имеют меньшую задержку, чем их аналоги FP.

person Peter Cordes    schedule 25.09.2016
comment
Целочисленное добавление / добавление уменьшения - это не только меньшая задержка, но и более высокая пропускная способность! На Haswell вы можете поддерживать два padd* на портах 1 и 5 и два vmovap на портах 2 и 3 на каждый CC. При 16-битной фиксированной точке это работает до 32 добавлений / CC, в то время как добавление с плавающей запятой, даже реализованное с помощью FMA d = a*1.0+c, может достигать только 16 добавлений / CC. Для бонусных очков padd* можно сделать насыщающим без штрафных санкций. - person Iwillnotexist Idonotexist; 25.09.2016
comment
@IwillnotexistIdonotexist: да, все отлично для добавления / подписки, вы получаете все преимущества вдвое большего количества элементов на вектор. - person Peter Cordes; 25.09.2016