Одной строкой

Вот эта вот гадость: Трудовые будни или еще раз о формате DNG

Починена вот тут: FastRawViewer 1.3.9 beta (билд 1051)

Comments

Я тут пытаюсь нащупать лучший способ сделать из массива байтов (т.е. целых чисел в диапазоне [0..255]) массив float'ов (32-ъх битных IEEE754) в диапазоне [-1.0..1.0] — т.е. с рескейлом и сдвигом. Смотрю я на варианты «что угодно», SSE4 (ведь нет уже живого железа без него? SSE3 это первый Core!), AVX1, AVX2+FMA (потому что, кажется, AVX2 без FMA3 не бывает). Смотрю на SkyLake (i7-6700K зажатый ровно на 40, что бы никакие трубобусты мне бенчмарки не пачкали). А, да, gcc 6.3.0 и clang 3.9.1 дают качественно одинаковые результаты. И нахожусь в некотором недоумении.

(1) Если разрешить -mavx2 -mfma3, то код писать вообще не надо. В смысле, тупой цикл по 1 элементу зараз на plain С с одним умножением и одним вычитанием даёт чуть больше 9G в секунду на 4GHz проце. Компилятор развернул и векторизовал ВСЁ. Я не смог написать руками лучше, хотя пытался раз 5. Мой лучший ручной результат с AVX2 — 8G в секунду.

(2) А со всем остальным лучше всего справляется ручной код не лезущий дальше SSE2. Ну, простенький, несколько анпэков, конверт, мул-суб, всё.

Что меня удивляет (кроме ума компилятора): что, например, с AVX всё равно лучше SSE2. Т.е. пока у нас нет FMA и, главное, _mm256_unpack{hi|lo}_epi{8|16}, от 256-битных регистров тут толку нет.

Это я что-то не так делаю? Ну не верю я, что такая типовая на вид задача не может выиграть от AVX, только от AVX2 :)

И, кстати, какова твоя статистика (есть ли она у тебя) с какого SSE можно начинать что бы уже никого по сути не обидеть?

"что, например, с AVX всё равно лучше SSE2"

vzeroupper делается? Там переход AVX/SSE очень дорогой (без явного обнуления), т.е. ради пары инструкций оно того не стоит.

Ну и второй вопрос, а вот если входных значений всего 256, то таблица не быстрее?

vzeroupper не делается, но там вообще распаковка адовая получается:
                const __m128i mem0 = _mm_load_si128((__m128i*)from);
               
                const __m128i tmp16l = _mm_unpacklo_epi8(mem0, zero);
                const __m128i tmp16h = _mm_unpackhi_epi8(mem0, zero);

                const __m128i tmp32ll = _mm_unpacklo_epi16(tmp16l, zero);
                const __m128i tmp32hl = _mm_unpackhi_epi16(tmp16l, zero);
               
                const __m128i tmp32lh = _mm_unpacklo_epi16(tmp16h, zero);
                const __m128i tmp32hh = _mm_unpackhi_epi16(tmp16h, zero);
               
                const __m256i tmp0 = _mm256_insertf128_si256(zero256, tmp32ll, 0);
                const __m256i tmp1 = _mm256_insertf128_si256(zero256, tmp32lh, 0);

                const __m256 in0 = _mm256_castsi256_ps(_mm256_insertf128_si256(tmp0, tmp32hl, 1));
                const __m256 in1 = _mm256_castsi256_ps(_mm256_insertf128_si256(tmp1, tmp32hh, 1));

                const __m256 out0 = _mm256_sub_ps(_mm256_mul_ps(in0, mul), del);
                const __m256 out1 = _mm256_sub_ps(_mm256_mul_ps(in1, mul), del);

                /* Store 16 floats */
                _mm256_store_ps(to,      out0);
                _mm256_store_ps(to +  8, out1);
               
                count -= 16;
                from += 16;
                to += 16;
Куда его тут? Да, я ещё интерливинг пытался тут вставить (ну типа как половина данных готова так с ними и работать а не групировать операции по смыслу как я тут скопипастил) и удвоить объём работы в одном проходе цикла (тоже как с группировкой по смыслу так и сгруппировкой по локальности данных) и то и другое даёт изменнеия в пределах статпогрещности. Да, в этом коде zero, zero256, mul, del — константы, понятно. Нули, множитель (512/255) и просто 1 в соотвествующих веторах.

Таблица! Это было первое, что я попытался сделать. Это медленней в 2-3 раза чем следующий самый медленный вариант!

А вот ключи у компилятора какие, там _mm_unpacklo_epi16 (к примеру) транслируется в VPUNPCKLWD или без V?
если без, то вот и этот самый микс.
Вообще, я не понимаю зачем читать-распаковывать по 128 бит, а потом сливать в 256. Кто не дает сразу 256?

И, кстати, 9G/sec - это в каких единицах, входной поток или выходной?

Сразу в 256 бит распаковывать можно только в AVX2 (_mm256_unpack{hi|lo}_epi{8|16}()) — или я не понимаю как. Если подскажешь буду благодарен.

ключи для avx "-O3 -mfpmath=sse -ffast-math -mavx" и код с v в начале, да:

  3c:   c5 e9 60 c4             vpunpcklbw %xmm4,%xmm2,%xmm0
  40:   c5 e9 68 d4             vpunpckhbw %xmm4,%xmm2,%xmm2
  44:   c5 f9 61 cb             vpunpcklwd %xmm3,%xmm0,%xmm1
  48:   c5 f9 69 fb             vpunpckhwd %xmm3,%xmm0,%xmm7
  4c:   c5 e9 61 c3             vpunpcklwd %xmm3,%xmm2,%xmm0
  50:   c5 e9 69 d3             vpunpckhwd %xmm3,%xmm2,%xmm2
  54:   c4 e3 75 18 cf 01       vinsertf128 $0x1,%xmm7,%ymm1,%ymm1
  5a:   c4 e3 7d 18 c2 01       vinsertf128 $0x1,%xmm2,%ymm0,%ymm0

9G/sec это семплы. Т.е. 9G байтов переаботано в 9G флоатов (36G байтов, соотвественно).

Ну от 36GB/sec out + 9GB/sec in - это, поди, память насытилась уже, больше нету?
Во всяком случае похоже же, у меня DDR4-3200 вот в районе 45Gb/sec вроде и есть.

stream_ps() вместо store_ps() может несколько ускорить.

Ну, у меня всё в кэш лезет. Мегабайт байтов, соответственно 4 мегабайта флотов, по кругу. Если я побольше данных сделаю всё в DDR4-2400 упрётся (да, я не так богат что бы 3200), это понятно.

Это-та скорость меня устраивает, вопрос как из AVX простого выжать похожее.

(3200 и 2400 стоят сейчас одинаково).

Если в кэш, то не надо stream_ps.

Когда я покупал разница на 8G модули была раза в полтора! 3200 все уже считались оверклокерскими, с радиаторами, красивой упаковкой, такое.

Сейчас память подорожала заметно (смотрел цену на DDR3 - процентов на 30-40) и похоже что дешевая быстрее.

Т.е. 4x16 не только с радиатором, но и кулером отдельным - стоила ну в пределах 10% разницы относительно 2400 приличного производителя (т.е. не хрен с горы неясный, а там Samsung/crucial и прочие подобные слова). Ну то есть это конкретный корсар на самом деле был дешевым, потому что можно было найти и еще подороже раза в полтора.

А вообще, вот у тебя в случае AVX+SSE (относительно SSE)
- две лишних упаковки
- но зато ты sub/mul делаешь с длинными векторами т.е. два действия вместо четырех.

Поди, одно аккурат стоит другого, два действия поэкономил, два добавил?

Вот Агнер наш Фог пишет, что insertf128 - вообще два такта. А sub/mul - по одному, вестимо.

Да, похоже на то. Интересно, можно ли упаковать в AVX лучше, чем написал...

Ну и на минуточку, а где собственно преобразование int-float?

Тааак, это я накопипастил криво, из варианта с битовой магией вместо castsi256(). Не то, короче, скопипастил. И, кажется, померял какую-то лажу. Ща буду перемеривать. Хотя битовая магия вообще ничего не даёт, никакого выигрыша против castsi256

Я про cast вообще не понял.
Или битовая магия (cast + sub), или cvttps_epi32
А одного каста - мало ж.

Именно этот код (не повезло ему) был вообще неправильным. Я перемерял правильно, с cvt, и результаты с точностью до статпогрешности те же. Ничего конверсия не стоит, не в ней дело.

А битовая магия выглядит так же только там вместо старших нулей всё пакуется константой 0x4700 во-первых и множитель другой во-вторых. Т.е. с точки зрения паковки и mul-sub всё в точногсти так же, только константы другие (и каст вместо конверсии).

И ещё про AVX. Оказывается, по крайней мере у Зионов, AVX не только здорово ограничивает частоту турбобуста (https://www.microway.com/knowledge-center-articles/detailed-specificatio...) но и тактуется отдельно (таблица три тут http://www.intel.com/content/dam/www/public/us/en/documents/specificatio...) заметно медленней базовой частоты из спецификации процессора.

Для меня это вот новость. Интересно, что у i3/i5/i7?

По мне, из таблиц не следует, что блоки AVX тактуются отдельно.

Я долго спорил с человеком, показавшим мне эти ссылки (_slw), но он меня убедил, что заголовок таблички 3 в PDF («Intel AVX Core frequency (GHz)») сложно интерпритировать по другому. AVX Core как ещё понять? И числа там уж совсем низкие относительно частоты в спецификации.

А что такое "Intel® AVX Turbo Boost Technology Maximum Core Frequency" (более правые столбцы в той же табличке)?

До скольки турбобуст разгоняет. Но вообще, да, тоже аргумент. Мутные формулировки.

Но AVX heavy, кстати (Prime95, маленьким FFT) жрет ватты как не в себе, это вот факт.

AVX Typical (Handbrake, Linpack AVX-овый) - сильно меньше.

Собственно, вот на Анандтехе (внизу страницы): http://www.anandtech.com/show/8423/intel-xeon-e5-version-3-up-to-18-hasw...
И у Интела разжевано: http://www.intel.com/content/dam/www/public/us/en/documents/white-papers...

AVX base - гарантированная. AVX Turbo - как пойдет, в зависимости от нагрева.
Частоты таки общие для всего горшка.

О, вот вторая ссылка, да, однозначно отвечает.

Значит у i7 (7xxx) жизнь устроена, насколько я вижу, так
- стандартные настройки (без разгона) с AVX оно идет до базовой частоты, а не до турбы. Впрочем, я в этом режиме особо не гонял.
- разгон: можно задать "AVX offset" т.е. понижение множителя (от максимального турбо) если используется AVX

Ну и Агнера Фога читаем: "разогрев" AVX-модуля на Skylake (про Kabu Lake еще не написал) занимает 56 тысяч что ли тактов, дохрена. Остывает (отключается) тоже сильно не сразу, т.е. вообще рекомендуется pre-heat если возможно :)

56 тысяч тактов! Оу.

Читайте Агнера нашего Фога, мудрость великая в его PDF-ках