Почему векторизация в целом быстрее, чем циклы?

Почему на самом низком уровне оборудования, выполняющего операции, и общих базовых операций (т. Е. Вещей, общих для фактических реализаций всех языков программирования при запуске кода) векторизация обычно намного быстрее, чем цикл?

Что делает компьютер во время цикла, чего он не делает при использовании векторизации (я говорю о фактических вычислениях, которые выполняет компьютер, а не о том, что пишет программист), или что он делает по-другому?

Я не мог убедить себя, почему разница должна быть такой значительной. Меня, вероятно, можно было бы убедить, что векторизованный код где-то избавляет от некоторых накладных расходов, связанных с циклами, но компьютер по-прежнему должен выполнять то же количество операций, не так ли? Например, если мы умножаем вектор размера N на скаляр, у нас будет N умножений для выполнения в любом случае, не так ли?


person Ben Sandeen    schedule 29.01.2016    source источник
comment
Оборудование может быть параллельным. Вы можете выполнить xor два 32-битных числа за 1 цикл. Вы можете выполнить xor два 1048576-битных числа за 1 цикл. Просто прожигите еще несколько проводов на микросхеме.   -  person usr    schedule 29.01.2016
comment
В современных коротко-векторных SIMD вы используете векторы внутри цикла для обработки всего массива. Векторные машины Cray старого стиля можно было настроить для большой операции, а затем одна инструкция могла бы загружать / работать / сохранять, но это не так, как работают x86 SSE / ARM NEON / PowerPC AltiVec.   -  person Peter Cordes    schedule 25.10.2017


Ответы (3)


Векторизация (как обычно используется этот термин) относится к операции SIMD (одна инструкция, несколько данных).

По сути, это означает, что одна инструкция выполняет одну и ту же операцию над несколькими операндами параллельно. Например, чтобы умножить вектор размера N на скаляр, назовем M количеством операндов этого размера, с которыми он может работать одновременно. Если это так, то количество инструкций, которые ему нужно выполнить, составляет приблизительно N / M, где (с чисто скалярными операциями) ему придется выполнить N операций.

Например, текущий набор инструкций Intel AVX 2 использует 256-битные регистры. Они могут использоваться для хранения (и обработки) набора из 4 операндов по 64 бита каждый или 8 операндов по 32 бита каждый.

Итак, предполагая, что вы имеете дело с 32-битными действительными числами одинарной точности, это означает, что одна инструкция может выполнять 8 операций (умножения, в вашем случае) одновременно, поэтому (по крайней мере, теоретически) вы можете завершить N умножений, используя только инструкции умножения N / 8. По крайней мере, теоретически это должно позволить операции завершиться примерно в 8 раз быстрее, чем позволяет выполнение одной инструкции за раз.

Конечно, точная выгода зависит от того, сколько операндов вы поддерживаете на инструкцию. Первые попытки Intel поддерживали только 64-битные регистры, поэтому для одновременной работы с 8 элементами эти элементы могли быть только 8 битами каждый. В настоящее время они поддерживают 256-битные регистры, и они объявили о поддержке 512-битных (и, возможно, они даже поставили это в несколько высокопроизводительных процессоров, но не в обычных потребительских процессорах, по крайней мере, пока). Хорошее использование этой возможности тоже может оказаться, мягко говоря, нетривиальным. Планирование инструкций, чтобы у вас было N операндов в наличии и в нужных местах в нужное время, не обязательно является легкой задачей (вообще).

Для сравнения: (теперь уже древний) Cray 1 значительно прибавил в скорости именно таким образом. Его векторный блок работал с наборами из 64 регистров по 64 бита в каждом, поэтому он мог выполнять 64 операции с двойной точностью за такт. В оптимально векторизованном коде она была намного ближе к скорости текущего процессора, чем можно было ожидать, основываясь исключительно на его (гораздо более низкой) тактовой частоте. Однако в полной мере воспользоваться этим было не всегда легко (и до сих пор).

Однако имейте в виду, что векторизация - это не единственный способ, с помощью которого ЦП может выполнять операции параллельно. Также существует возможность параллелизма на уровне команд, который позволяет одному ЦП (или одному ядру ЦП) выполнять более одной инструкции за раз. Большинство современных ЦП включают оборудование для (теоретически) выполнения до 4 инструкций за такт 1, если инструкции представляют собой сочетание загрузки, сохранения и ALU. Они могут регулярно выполнять в среднем около 2 инструкций за такт или больше в хорошо настроенных циклах, когда память не является узким местом.

Затем, конечно, есть многопоточность - выполнение нескольких потоков инструкций на (по крайней мере, логически) отдельных процессорах / ядрах.

Итак, современный ЦП может иметь, скажем, 4 ядра, каждое из которых может выполнять 2 векторного умножения за такт, и каждая из этих инструкций может работать с 8 операндами. Так что, по крайней мере теоретически, он может выполнять 4 * 2 * 8 = 64 операции за такт.

Некоторые инструкции имеют лучшую или худшую пропускную способность. Например, FP добавляет пропускную способность ниже, чем FMA или умножает на Intel до Skylake (1 вектор на такт вместо 2). Но логическая логика, такая как AND или XOR, имеет 3 вектора на тактовую пропускную способность; не требуется много транзисторов для создания исполнительного блока AND / XOR / OR, поэтому процессоры копируют их. Узкие места на общей ширине конвейера (интерфейс, который декодирует и выдает в некорректную часть ядра) являются обычными при использовании инструкций с высокой пропускной способностью, а не узкими местами на конкретном исполнительном блоке.


  1. Но со временем у процессоров появляется больше доступных ресурсов, поэтому это число растет.
person Jerry Coffin    schedule 29.01.2016
comment
В моем вводном курсе компьютерных систем (и в нашем курсе параллельного программирования) мы рассматривали процессор (или одно ядро ​​многоядерного процессора) как систему типа «черный ящик», которая может выполнять ТОЛЬКО действия последовательно; никакие вычисления не могли выполняться одновременно. Это неправильно? Или у ядра есть свои собственные подпроцессоры, каждый из которых может выполнять простые вычисления? - person Ben Sandeen; 29.01.2016
comment
Да, по отношению к современному (достаточно высококлассному) процессору это неверно. Стандартные настольные / серверные процессоры десятилетиями поддерживают различные типы параллелизма. Чисто последовательный был бы (например) 486, но уже не был верен оригинальному Pentium. На мэйнфреймах то же самое произошло еще раньше (например, CDC 6500 имел архитектуру, аналогичную Pentium, а 6600 - Pentium Pro). Они были выпущены примерно в 1964 году. - person Jerry Coffin; 29.01.2016
comment
Большинство современных процессоров имеют ширину конвейера 4 мупа (Intel с Core2, AMD с Bulldozer). Это дает вам 4 инструкции за такт, если у вас есть несколько инструкций ALU для загрузки, сохранения и однократного выполнения. (Пары инструкций сравнения + ветвления могут объединяться в 1 муп, поэтому истинный максимальный IPC Haswell составляет 6 инструкций за такт, но гораздо более реалистично сказать 4) конвейер Ryzen имеет ширину 6, но однокомпонентные инструкции могут запускать только 5 команд за такт. Часы. (Векторы AVX / AVX2 256b декодируются до 2 мопов и могут хорошо заполнить канал.) Core2 вряд ли будет выполнять 4 IPC, за исключением специально созданных циклов, но это реалистично для SKL. - person Peter Cordes; 25.10.2017
comment
Команда load + ALU, такая как vfmadd132ps ymm0, ymm1, [rdi], может быть объединена в один uop, поэтому вы можете иногда насыщать векторные ALU и сжимать нагрузки, чтобы предоставить им новые данные, не создавая узких мест во внешнем интерфейсе. Например, мне удалось построить цикл, который запускает 7 мопов с незанятыми доменами за такт на Skylake (2 микроперемещенных нагрузки + ALU, 1 хранилище (которое представляет собой 2 микропредложения микроплавленных в 1 на Intel) и одно сравнение + ветвь . agner.org/optimize/blog/read.php?i = 415 # 857. - person Peter Cordes; 25.10.2017

Векторизация имеет два основных преимущества.

  1. Основное преимущество заключается в том, что оборудование, разработанное для поддержки векторных инструкций, обычно имеет оборудование, способное выполнять несколько операций ALU параллельно при использовании векторных инструкций. Например, если вы попросите его выполнить 16 сложений с помощью 16-элементной векторной инструкции, у него может быть 16 сумматоров, которые могут выполнять все добавления одновременно, параллельно. Единственный способ получить доступ ко всем этим сумматорам 1 - это векторизация. С помощью скалярных инструкций вы просто получите 1 одинокий сумматор.

  2. Обычно использование векторных инструкций позволяет сэкономить на накладных расходах. Вы загружаете и храните данные большими порциями (до 512 бит за раз на некоторых последних процессорах Intel), и каждая итерация цикла выполняет больше работы, поэтому накладные расходы цикла обычно ниже в относительном смысле 2, и вам нужно меньше инструкций для выполнения той же работы, чтобы снизить нагрузку на процессор и т. д.

Наконец, ваша дихотомия между циклами и векторизацией является странной. Когда вы берете не-векторный код и векторизуете его, вы обычно получаете цикл, если он был раньше, или нет, если его не было. Сравнение действительно между скалярными (не-векторными) инструкциями и векторными инструкциями.


1 Или по крайней мере 15 из 16, возможно, один также используется для выполнения скалярных операций.

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

person BeeOnRope    schedule 25.10.2017

Векторизация - это разновидность параллельной обработки. Это позволяет выделить больше компьютерного оборудования для выполнения вычислений, поэтому вычисления будут выполняться быстрее.

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

Для векторизации используется специальное оборудование. В отличие от многоядерного ЦП, для которого каждый из блоков параллельной обработки является полнофункциональным ядром ЦП, блоки векторной обработки могут выполнять только простые операции, и все блоки выполняют одну и ту же операцию одновременно, работая с последовательностью значений данных ( вектор) одновременно.

person Raedwald    schedule 29.01.2016
comment
Значит, векторизованный код имеет кардинально иную реализацию? На самом деле он просто распределяет операции между большим количеством ядер? Если да, означает ли это, что одноядерный ЦП не увидит никаких преимуществ от векторизации, или есть ли вспомогательные аппаратные блоки (за отсутствием лучшего слова) в каждом ядре, которые по-прежнему помогли бы ускорить процесс? - person Ben Sandeen; 29.01.2016