Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Simd

216 views

Published on

Технология векторизации кода. Описание использования в языках C/C++ и C#.

Published in: Education
  • Be the first to comment

  • Be the first to like this

Simd

  1. 1. Производительность Вычислительная мощность компьютера (производительность компьютера)  —  это  количественная  характеристика  скорости  выполнения  определённых операций на компьютере. Технически  современный микропроцессор выполнен  в  виде  одной  сверхбольшой  интегральной  схемы,  состоящей  из  нескольких  миллиардов  элементов  —  это  одна  из  самых  сложных  конструкций,  созданных человеком.  1
  2. 2. Закон Мура Гордон Мур,  один  из  основателей  компании  Intel,  сформулировал  получивший  его  имя  закон  в  1965  году.  Описание  закономерности  звучит  так:  «Количество  транзисторов,  размещаемых  на  кристалле  интегральной  схемы,  удваивается каждые  12 месяцев». В  1975 году Мур скорректировал свое  предсказание, увеличив этот срок до  двух лет. 2
  3. 3. Закон Мура? 3
  4. 4. Совершенствование архитектуры Совершенствование процесса выполнения инструкций:   Совершенствование архитектуры набора команд: RISC, CISC, MISC, VLIW   Параллелизм уровня инструкций: конвейерная архитектура, суперскалярная обработка   Параллелизм уровня потоков: многопроцессорные системы, многопоточность, многоядерные  процессоры   Параллелизм данных: векторная обработка данных (векторные процессоры/инструкции) 4
  5. 5. Векторная обработка данных  Векторный процессор (vector processor) – процессор,  поддерживающий на уровне системы команд операции для работы с  одномерными массивами (векторами). 5
  6. 6. Векторный VS Скалярный Скалярный процессор for i = 1 to 10 do IF – Instruction Fetch(next) ID – Instruction Decode Load Operand1 Load Operand2 Add Operand1 Operand2 Store Result end for Векторный процессор IF – Instruction Fetch(next) ID – Instruction Decode Load Operand1 Load Operand2 Add Operand1 Operand2 Store Result 6 Меньше преобразований адресов Меньше IF, ID Меньше конфликтов конвейера, ошибок предсказания  переходов Эффективнее доступ к памяти (2 выборки vs 20) Операция над операндами выполняется параллельно Уменьшился размер кода
  7. 7. Что такое векторные инструкции SIMD (single instruction, multiple data — одиночный поток команд, множественный поток данных) — принцип компьютерных  вычислений, позволяющий обеспечить параллелизм на уровне  данных. 7  Одна инструкция, много данных  Каждая операция применяется к  N значений в одном (большом)  регистре фиксированного  размера (128, 256, 512 бит)  Может быть до N раз быстрее  обычных АЛУ
  8. 8. SIMD-инструкции Intel x86  MMX 64 bits float, double  SSE 128 bits float  SSE2 128 bits int8, int16, int32, int64, double  SSE3, SSSE3  SSE4a (AMD)  SSE4.1, SSE4.2  AVX 256 bits float, double  AVX2 256 bits int8, int16, int32, int64  FMA3  FMA4, XOP (AMD)  MIC 512 bits float, double, int32, int64 8 PowerPC  AltiVec 128 bits int8, int16, int32, int64, float  Cell SPU & VSX, 128 bits int8, int16, int32, int64, float, double  QPX 512 bits double ARM  VFP 64 bits float, double  NEON 64 bits & 128 bits float, int8, int16, int32, int64
  9. 9. MMX 1997, Intel Pentium MMX  MMX – набор SIMD-инструкции для обработки целочисленных  векторов длиной 64 бит  8 виртуальных регистров mm0 – mm7  Типы векторов:  9  mm# quadword  8 x char char char char char char char char char  4 x short int short int short int short int short int  2 x int int int
  10. 10. SSE 1999, Intel Pentium III  8 (16 для x64) векторных регистров шириной 128 бит: xmm0 – xmm7  32-битный (в x86-64 — 64) регистр флагов (MXCSR)  Инструкции над вещественными числами одинарной точности  Инструкции явной предвыборки данных, контроля кэширования  данных  70 инструкций: команды пересылки, арифметические команды, команды сравнения, преобразования типов, побитовые операции  Типы векторов:   4 x float 10
  11. 11. SSE2 2001, Pentium 4, IA32, x86-64 (Intel 64, 2004)  16 векторных регистров шириной 128 бит: xmm0 – xmm15  Добавлено 144 инструкции к 70 инструкциям SSE  Продолжение SSE работает с вещественными числами  SSE2 включает в себя ряд команд управления кэшем  Типы векторов:  16 x char  8 x short int  4 x float или int  2 x double  1 x 128-bit int 11
  12. 12. SSE3/4 Intel SSE3: 2003, Pentium 4 Prescott, IA32, x86-64 (Intel 64, 2004)  Добавлено 13 новых инструкции к инструкциям SSE2  Возможность горизонтальной работы с регистрами – команды сложения и вычитания нескольких значений, хранящихся в одном регистре 12 Intel SSE4: 2006, Intel Core, AMD Bulldozer  Добавлено 54 новых инструкций  SSE 4.1 – 47  SSE 4.2 – 7
  13. 13. AVX 2008, Intel Sandy Bridge (2011), AMD Bulldozer (2011)  Добавлено 13 новых инструкции к инструкциям SSE2  Размер векторов увеличен до 256 бит  Векторные регистры переименованы: ymm0 – ymm15  Регистры xmm# – это младшие 128 бит регистров ymm#  Трехоперандный синтаксис AVX-инструкций: С = A + B  Использование ymm регистров требует поддержки со стороны операционной системы (для сохранения регистров при переключении контекстов)  Linux ядра >= 2.6.30  Apple OS X 10.6.8  Windows 7 SP 1  Поддержка компиляторами:  GCC 4.6  Intel C++ Compiler 11.1  Microsoft Visual Studio 2010  Open64 4.5.1 13
  14. 14. Эволюция SIMD 14
  15. 15. Эволюция SIMD 15 MMX (1997) MM0-MM7, 64-битные регистры SSE (1999) XMM0-XMM7, 128-битные регистры SSE2 (2001) XMM0- XMM15, 128- битные регистры SSE3 (2004) SSE4 (2007) AVX (2011) YMM0-YMM15, 256-битные регистры AVX2 (2014) AVX512 (2015) ZMM0-ZMM31, 512-битные регистры
  16. 16. Инструкции SSE/AVX 16
  17. 17. Скалярные SSE/AVX-инструкции 17 Скалярные SSE-инструкции (scalar instruction) – в операции участвуют только младшие элементы данных (скаляры) в векторных регистрах/памяти
  18. 18. Векторные SSE/AVX-инструкции 18 SSE-инструкция над упакованными векторами (packed instruction) – в операции участвуют все элементы векторных регистров/памяти
  19. 19. Инструкции 19  Операции копирования данных (mem-reg/reg-mem/reg-reg)  Scalar: MOVSS  Packed: MOVAPS, MOVUPS, MOVLPS, MOVHPS,MOVLHPS, MOVHLPS  Арифметические операции  Scalar: ADDSS, SUBSS, MULSS, DIVSS, RCPSS, SQRTSS, MAXSS, MINSS, RSQRTSS  Packed: ADDPS, SUBPS, MULPS, DIVPS, RCPPS, SQRTPS, MAXPS, MINPS, RSQRTPS  Операции сравнения  Scalar: CMPSS, COMISS, UCOMISS  Pacled: CMPPS  Поразрядные логические операции  Packed: ANDPS, ORPS, XORPS, ANDNPS  ...... Математика Скалярный оператор Векторный оператор X = X + Y addss addps X = X – Y subss subps X = X × Y mulss mulps X = X / Y divss divps X = rcpss rcpps X = sqrtss sqrtps X = rsqrtss rsqrtps X = max(X, Y) maxss maxps X = min(X, Y) minss minps Математика Скалярный оператор Векторный оператор X = X + Y addss addps X = X – Y subss subps X = X × Y mulss mulps X = X / Y divss divps rcpss rcpps sqrtss sqrtps rsqrtss rsqrtps X = max(X, Y) maxss maxps X = min(X, Y) minss minps
  20. 20. Где искать? 20 https://software.intel.com/sites/landingpage/IntrinsicsGuide/ http://x86.renejeschke.de/
  21. 21. Как использовать? 21 [BITS 64] DEFAULT REL GLOBAL _main EXTERN ExitProcess EXTERN GetStdHandle EXTERN WriteFile SECTION .text _main: ;align stack to 16 bytes for Win64 calls and rsp, -10h ;give room to Win64 API ;calls that don't take stack params sub rsp, 020h mov rcx, -0Bh ;STD_OUTPUT_HANDLE call GetStdHandle movdqu xmm0, [arr] movdqu xmm1, [message] paddb xmm0, xmm1 movdqa [message], xmm0 mov rcx, rax mov rdx, message mov r8, msglen xor r9, r9 push r9 sub rsp, 20h ;Give Win64 API calls room call WriteFile add rsp, 28h ;Restore Stack Pointer mov rcx, 0 call ExitProcess xor rax, rax ret SECTION .data arr db 50,68,62,-9,36,26,30,21,-15,58,68,-15,52,64,64,61 message db '////////////////', 10 msglen equ $-message
  22. 22. Компилируем и запускаем 22
  23. 23. Компилируем и запускаем 23 Сложно? Да!
  24. 24. Как проще? 24 Ассемблер Ассемблерные вставки Встроенные функции компилятора (Intrinsic) Специальные классы Автоматическая векторизация компилятора Сложно Лучшая управляемость (полный контроль у разработчика) Легко (нет контроля у разработчика, надеемся на компилятор)
  25. 25. SIMD в высокоуровневых языках 25
  26. 26. Intrinsic в C++ 26  Intrinsics – набор встроенных функций и типов данных, поддерживаемых компилятором, для предоставления высокоуровневого доступа к SSE-инструкциям  Компилятор самостоятельно распределяет XMM/YMM регистры, принимает решение о способе загрузки данных из памяти (проверяет выравнен адрес или нет) и т.п.  Заголовочные файлы #include <mmintrin.h> /* MMX */ #include <xmmintrin.h> /* SSE, нужен также mmintrin.h */ #include <emmintrin.h> /* SSE2, нужен также xmmintrin.h */ #include <pmmintrin.h> /* SSE3, нужен также emmintrin.h */ #include <smmintrin.h> /* SSE4.1 */ #include <nmmintrin.h> /* SSE4.2 */ #include <immintrin.h> /* AVX */ __m128 float[4] __m128d double[2] __m128i char[16], short int[8], int[4], uint64_t [2]  Типы встроенных данных
  27. 27. Intrinsic в деле 27 #include "iostream" #include "xmmintrin.h" int main() { const auto N = 8; alignas(16) float a[] = { 41982.0, 81.5091, 3.14, 42.666, 54776.45, 342.4556, 6756.2344, 4563.789 }; alignas(16) float b[] = { 85989.111, 156.5091, 3.14, 42.666, 1006.45, 9999.4546, 0.2344, 7893.789 }; __m128* a_simd = reinterpret_cast<__m128*>(a); __m128* b_simd = reinterpret_cast<__m128*>(b); auto size = sizeof(float); void *ptr = _aligned_malloc(N * size, 32); float* c = reinterpret_cast<float*>(ptr); for (size_t i = 0; i < N/2; i++, a_simd++, b_simd++, c += 4) _mm_store_ps(c, _mm_add_ps(*a_simd, *b_simd)); c -= N; std::cout.precision(10); for (size_t i = 0; i < N; i++) std::cout << c[i] << std::endl; _aligned_free(ptr); system("PAUSE"); return 0; }
  28. 28. SIMD в C# 28 На данный момент поддержка этой технологии в .NET представлена в пространстве имен System.Numerics.Vectors и представляет собой библиотеку векторных типов, которые могут использовать преимущества аппаратного ускорения SIMD. Она содержит следующие типы:  Vector — коллекцию статических удобных методов для работы с универсальными векторами  Matrix3x2 — представляет матрицу 3х2  Matrix4х4 — представляет матрицу 4х4  Plane — представляет трехмерную плоскость  Quaternion — представляет вектор, используемый для кодирования трехмерных физических поворотов  Vector<(Of <(<'T>)>)> представляет вектор указанного числового типа, который подходит для низкоуровневой оптимизации параллельных алгоритмов  Vector2 — представляет вектор с двумя значениями одинарной точности с плавающей запятой  Vector3 — представляет вектор с тремя значениями одинарной точности с плавающей запятой  Vector4 — представляет вектор с четырьмя значениями одинарной точности с плавающей запятой
  29. 29. System.Numerics.Vectors 29 using System; using System.Numerics; static void Main(string[] args) { const Int32 N = 8; Single[] a = { 41982.0F, 81.5091F, 3.14F, 42.666F, 54776.45F, 342.4556F, 6756.2344F, 4563.789F }; Single[] b = { 85989.111F, 156.5091F, 3.14F, 42.666F, 1006.45F, 9999.4546F, 0.2344F, 7893.789F }; Single[] c = new Single[N]; for (int i = 0; i < N; i += Vector<Single>.Count) { var aSimd = new Vector<Single>(a, i); var bSimd = new Vector<Single>(b, i); Vector<Single> cSimd = aSimd + bSimd; cSimd.CopyTo(c, i); } for (int i = 0; i < a.Length; i++) Console.WriteLine(c[i]); Console.ReadKey(); }
  30. 30. Простой пример 30 [Benchmark(Description = "VectorSum")] public int VectorSum() { var vSize = Vector<int>.Count; for (int i = 0; i < c.Length; i += vSize) { var aa = new Vector<int>(a, i); var bb = new Vector<int>(b, i); aa += bb; aa.CopyTo(c, i); } return c[0]; } [Benchmark(Description = "SimpleSum", Baseline = true)] public int SimpleSum() { for (int i = 0; i < c.Length; i++) c[i] = a[i] + b[i]; return c[0]; } Method Platform Jit Median StdDev Scaled SimpleSum X64 LegacyJit 2.3991 us 0.0818 us 1 SimpleSum X64 RyuJit 4.9439 us 0.4746 us 2.06 SimpleSum X86 LegacyJit 4.0082 us 0.3158 us 1.66 VectorSum X64 LegacyJit 51.9921 us 4.5165 us 21.67https://github.com/PerfDotNet/BenchmarkDotNet/ - benchmark for .NET
  31. 31. Почему так???? 31 var aa = new Vector<int>(a, i); 014F35A6 mov edx,dword ptr [esi+4] 014F35A9 push edi 014F35AA lea ecx,[ebp-1Ch] 014F35AD call dword ptr ds:[552419Ch] var bb = new Vector<int>(b, i); 014F35B3 mov edx,dword ptr [esi+8] 014F35B6 push edi 014F35B7 lea ecx,[ebp-2Ch] 014F35BA call dword ptr ds:[552419Ch] aa += bb; 014F35C0 lea eax,[ebp-1Ch] 014F35C3 sub esp,10h 014F35C6 movq xmm0,mmword ptr [eax] 014F35CA movq mmword ptr [esp],xmm0 014F35CF movq xmm0,mmword ptr [eax+8] 014F35D4 movq mmword ptr [esp+8],xmm0 014F35DA lea eax,[ebp-2Ch] 014F35DD sub esp,10h 014F35E0 movq xmm0,mmword ptr [eax] 014F35E4 movq mmword ptr [esp],xmm0 014F35E9 movq xmm0,mmword ptr [eax+8] 014F35EE movq mmword ptr [esp+8],xmm0 014F35F4 lea ecx,[ebp-1Ch] 014F35F7 call dword ptr ds:[55241BCh] aa.CopyTo(c, i); 014F35FD mov edx,dword ptr [esi+0Ch] 014F3600 push edi 014F3601 lea ecx,[ebp-1Ch] 014F3604 call dword ptr ds:[55241B0h] var aa = new Vector<int>(a, i); 00007FFD3BDB5370 mov rdx,qword ptr [rcx+8] 00007FFD3BDB5374 mov r8d,dword ptr [rdx+8] 00007FFD3BDB5378 cmp eax,r8d 00007FFD3BDB537B jae 00007FFD3BDB53EE 00007FFD3BDB537D mov r8d,dword ptr [rdx+8] 00007FFD3BDB5381 lea r9d,[rax+7] 00007FFD3BDB5385 cmp r9d,r8d 00007FFD3BDB5388 jae 00007FFD3BDB53EE 00007FFD3BDB538A vmovupd ymm0,ymmword ptr [rdx+rax*4+10h] var bb = new Vector<int>(b, i); 00007FFD3BDB5391 mov rdx,qword ptr [rcx+10h] var bb = new Vector<int>(b, i); 00007FFD3BDB5395 mov r8d,dword ptr [rdx+8] 00007FFD3BDB5399 cmp eax,r8d 00007FFD3BDB539C jae 00007FFD3BDB53EE 00007FFD3BDB539E mov r8d,dword ptr [rdx+8] 00007FFD3BDB53A2 cmp r9d,r8d 00007FFD3BDB53A5 jae 00007FFD3BDB53EE 00007FFD3BDB53A7 vmovupd ymm1,ymmword ptr [rdx+rax*4+10h] aa += bb; 00007FFD3BDB53AE vpaddd ymm0,ymm0,ymm1 aa.CopyTo(c, i); 00007FFD3BDB53B3 mov rdx,qword ptr [rcx+18h] 00007FFD3BDB53B7 mov r8d,dword ptr [rdx+8] 00007FFD3BDB53BB cmp eax,r8d 00007FFD3BDB53BE jae 00007FFD3BDB53F3 00007FFD3BDB53C0 mov r8d,dword ptr [rdx+8] 00007FFD3BDB53C4 cmp r9d,r8d 00007FFD3BDB53C7 jae 00007FFD3BDB53F8 00007FFD3BDB53C9 vmovupd ymmword ptr [rdx+rax*4+10h],ymm0 x64 x86
  32. 32. А на практике ??? 32 Свёртка (в случае изображения) — это операция вычисления нового значения заданного пикселя, при которой учитываются значения окружающих его соседних пикселей.
  33. 33. Пример использования свертки 33  Фильтрация Свёртка — очень полезная и распространённая операция, лежащая в основе различных фильтров (размытие, повышение резкости, нахождение краёв, подавление шумов). Фильтр улучшения четкостиФильтр размытие
  34. 34. Пример использования свертки 34  Сверточные нейронные сети Имеется матрица на входе (картинка) и есть ядро свертки которое состоит из весов (эти веса в процессе обучения сверточной нейронной сети настраиваются). Ядро построено таким образом, что графически кодирует какой-либо один признак, например, наличие наклонной линии под определенным углом. Тогда следующий слой, получившийся в результате операции свёртки такой матрицей весов, показывает наличие данной наклонной линии в обрабатываемом слое и её координаты, формируя так называемую карту признаков (англ. feature map).
  35. 35. Реализация операции свертки 35 unsafe public static int[][] Convolution(int[][] img, int[,] filter) { .... var vSize = Vector<int>.Count; for (int i = 0; i < resHeight; ++i) { res[i] = new int[resWidth]; fixed (int* resP = res[i], filterP = filter) { for (int j = 0; j < resWidth; j += vSize) { var kernel = Vector<int>.Zero; for (int fi = 0, imgI = i, filterI = 0; fi < filterHeight; ++fi, ++imgI, filterI += filterWidth) for (int fj = 0; fj < filterWidth; ++fj) { var imgV = new Vector<int>(img[imgI], j + fj); var filterV = new Vector<int>(filterP[filterI + fj]); kernel += imgV * filterV; } kernel.CopyTo(res[i], j); } } } return res; } public static int[][] Convolution(int[][] img, int[,] filter) { .... for (int i = 0; i < resHeight; ++i) { res[i] = new int[resWidth]; for (int j = 0; j < resWidth; ++j) { var kernel = 0; for (int fi = 0, imgI = i; fi < filterHeight; ++fi, ++imgI) for (int fj = 0; fj < filterWidth; ++fj) kernel += img[imgI][j+fj] * filter[fi, fj]; } res[i][j] = kernel; } } return res; } 4 строки – unsafe оптимизация работы с массивом 6 строк – векторизация
  36. 36. Benchmark 36 Method Platform Jit Median Scaled SimpleJagged X86 LegacyJit 93.9177 ms 1 VectorJagged X86 LegacyJit 251.0950 ms 2.67 SimpleJagged X64 LegacyJit 103.8635 ms 1 VectorJagged X64 LegacyJit 341.3388 ms 3.28 SimpleJagged X64 RyuJit 96.0608 ms 1 VectorJagged X64 RyuJit 16.5649 ms 0.17
  37. 37. В итоге 37 Что мы имеем:  Во многих случаях векторизация дает увеличение производительности (размер вектора, реализация)  Сложные алгоритмы потребуют изобретательность, но без этого никуда  System.Numerics.Vectors в настоящее время обхватывает только часть simd-инструкций. Для более серьезного подхода потребуется С++  Есть множество других способов помимо векторизации: правильное использование кэша, многопоточность, грамотная работа с памятью(чтобы сборщик мусора не потел) и т.д. SIMD инструкции это один из способов выжать производительность, но не единственный. Источник параллелизма Ускорение Усилие программиста Популярность Множество ядер 2х-128х Умеренное Высокая Множество машин 1х-Бесконечность Умеренно-Высокое Высокая Векторизация 2х-8х Умеренно-Высокое Низкая Графические адаптеры 128х-2048х Высокое Низкая
  38. 38. Спасибо за внимание =) Вопросы?

×