Введение

Цепочка обработки данных

В вычислительной технике конвейер, также известный как конвейер данных, представляет собой набор элементов обработки данных, соединенных последовательно, где выход одного элемента является входом следующего. Элементы конвейера часто выполняются параллельно или с разделением по времени. Между элементами часто вставляется некоторый объем буферной памяти. Компьютерные конвейеры включают в себя:

Конвейеры инструкций, такие как классический конвейер RISC, которые используются в центральных процессорах (CPU) и других микропроцессорах для обеспечения перекрывающегося выполнения нескольких инструкций с использованием одной и той же схемы. Схема обычно разделена на этапы, и каждый этап обрабатывает определенную часть одной инструкции за раз, передавая частичные результаты на следующий этап. Примеры этапов: декодирование инструкций, арифметические/логические операции и выборка из регистров. Они связаны с технологиями суперскалярного исполнения, пересылки операндов, спекулятивного исполнения и исполнения вне порядка.

Графические конвейеры, которые можно найти в большинстве графических процессоров (GPU), состоящие из нескольких арифметических устройств или полноценных процессоров, реализующих различные этапы обычных операций рендеринга (перспективная проекция, отсечение, вычисление цвета и освещения, рендеринг и т. д.).

Программные конвейеры, состоящие из последовательности вычислительных процессов (команд, запусков программ, задач, потоков, процедур и т. д.), концептуально выполняемых параллельно, при этом выходной поток одного процесса автоматически подается в качестве входного потока следующего. Системный вызов pipe в Unix является классическим примером этой концепции.

HTTP-пиплайнинг – техника отправки нескольких HTTP-запросов через одно и то же TCP-соединение, без ожидания завершения предыдущего запроса перед отправкой нового. Некоторые операционные системы могут предоставлять синтаксис, подобный UNIX, для объединения нескольких запусков программ в конвейер, но реализовывать его как простое последовательное выполнение, а не истинный конвейерный режим – то есть, ожидая завершения каждой программы перед запуском следующей.

Концепция и мотивация

Трубопровод – это концепция, широко используемая в повседневной жизни. Например, на конвейере автомобильного завода каждая конкретная задача – такая как установка двигателя, установка капота и установка колес – часто выполняется отдельной рабочей станцией. Станции выполняют свои задачи параллельно, каждая на отдельном автомобиле. Как только для автомобиля выполнена одна задача, он перемещается на следующую станцию. Изменения во времени, необходимом для выполнения задач, могут быть компенсированы "буферизацией" (удержанием одного или нескольких автомобилей в пространстве между станциями) и/или "остановкой" (временной остановкой предыдущих станций), пока следующая станция не станет свободной. Предположим, что для сборки одного автомобиля требуется три задачи, занимающие соответственно 20, 10 и 15 минут. Если бы все три задачи выполняла одна станция, завод выпускал бы один автомобиль каждые 45 минут. Используя конвейер из трех станций, завод выпустил бы первый автомобиль через 45 минут, а затем новый каждые 20 минут. Как показывает этот пример, конвейер не уменьшает задержку, то есть общее время, необходимое для прохождения одного элемента через всю систему. Однако он увеличивает пропускную способность системы, то есть скорость обработки новых элементов после первого.

Балансирование этапов

Поскольку пропускная способность конвейера не может быть выше, чем у его самого медленного звена, разработчику следует стремиться к такому распределению работы и ресурсов между этапами, чтобы все они выполняли свои задачи за одинаковое время. В примере со сборкой автомобиля, если бы каждая из трех задач занимала 15 минут вместо 20, 10 и 15 минут, задержка осталась бы прежней – 45 минут, но новый автомобиль завершался бы каждые 15 минут, а не 20.

Буферная система

В идеальных условиях, если все обрабатывающие элементы синхронизированы и требуют одинакового времени для обработки, то каждый элемент может получать данные сразу после их выдачи предыдущим элементом, за один тактовый цикл. Таким образом, данные будут проходить по конвейеру с постоянной скоростью, подобно волнам в водном канале. В таких "волновых конвейерах" не требуется синхронизация или буферизация между этапами, кроме необходимой памяти для хранения данных. В более общем случае, буферизация между этапами конвейера необходима, когда время обработки нерегулярно или когда элементы могут создаваться или удаляться в процессе обработки. Например, в графическом конвейере, обрабатывающем треугольники для отображения на экране, элемент, проверяющий видимость каждого треугольника, может отбрасывать невидимые треугольники или генерировать два или более треугольных фрагмента, если они частично скрыты. Буферизация также необходима для компенсации неравномерности скорости, с которой приложение передает данные на первый этап и потребляет результаты с последнего. Буфер между двумя этапами может быть реализован как простой аппаратный регистр с соответствующей логикой синхронизации и сигнализации. Когда этап А сохраняет данные в регистре, он отправляет сигнал "данные доступны" следующему этапу B. После использования данных, B отправляет сигнал "данные получены" этапу A. Этап А приостанавливается, ожидая этот сигнал, прежде чем сохранять следующие данные в регистре. Этап B приостанавливается, ожидая сигнала "данные доступны", если он готов к обработке следующего элемента, но этап A еще не предоставил его. Если время обработки элемента переменное, весь конвейер может часто останавливаться, ожидая, пока этот элемент и все предыдущие обработают данные в своих входных буферах. Частоту таких остановок можно снизить, предоставив в буфере ввода этапа место для нескольких элементов. Такой буфер с несколькими элементами обычно реализуется в виде очереди FIFO (первый пришел – первый ушел). Предыдущий этап все еще может быть остановлен при заполнении очереди, но частота таких событий уменьшится с увеличением количества слотов буфера. Теория очередей позволяет определить необходимое количество слотов буфера в зависимости от изменчивости времени обработки и требуемой производительности.

Нелинейные трубопроводы

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

Зависимости между позициями

В некоторых случаях обработка элемента Y на этапе А может зависеть от результатов или эффекта обработки предыдущего элемента X на какой-либо более поздней стадии B конвейера. В этом случае этап А не может правильно обработать элемент Y, пока элемент X не завершит обработку на этапе B. Эта ситуация очень часто встречается в конвейерах команд. Например, предположим, что Y — это арифметическая инструкция, которая считывает содержимое регистра, который, как предполагалось, был изменен более ранней инструкцией X. Пусть A будет этапом, который извлекает операнды инструкции, а B — этапом, который записывает результат в указанный регистр. Если этап А пытается обработать инструкцию Y до того, как инструкция X достигнет этапа B, регистр может все еще содержать старое значение, и результат Y будет некорректным. Для правильной обработки таких конфликтов конвейер должен быть оснащен дополнительными схемами или логикой, которые обнаруживают их и принимают соответствующие меры. Стратегии для этого включают:
Задержка: Каждый затронутый этап, такой как A, останавливается до тех пор, пока зависимость не будет устранена, то есть до тех пор, пока не будет доступна необходимая информация и/или не будет достигнуто требуемое состояние. Переупорядочение элементов: вместо задержки этап А может отложить элемент Y и искать любой последующий элемент Z во входном потоке, который не имеет никаких зависимостей от предыдущих элементов. В конвейерах команд эта техника называется внеочередным выполнением. Предсказание и откат: одним из важных примеров зависимости между элементами является обработка условного перехода инструкции X конвейером команд. Первая стадия A конвейера, которая извлекает следующую инструкцию Y для выполнения, не может выполнить свою задачу, пока X не извлечет свой операнд и не определит, следует ли выполнять переход или нет. Это может занять много тактов, поскольку операнд X может, в свою очередь, зависеть от предыдущих инструкций, которые извлекают данные из основной памяти. Вместо того чтобы останавливаться в ожидании завершения X, этап А может предсказать, будет ли выполнен переход, и извлечь следующую инструкцию Y на основе этого предсказания. Если предсказание окажется неверным (что, будем надеяться, произойдет редко), системе придется откатиться и возобновить работу с правильным выбором. А именно, все изменения, внесенные в состояние машины на этапе А и последующих этапах на основе этого предсказания, должны быть отменены, инструкции, следующие за X, уже находящиеся в конвейере, должны быть сброшены, и этап А должен перезапуститься с правильным указателем инструкции. Эта стратегия предсказания переходов является частным случаем спекулятивного выполнения.

Типичные реализации программного обеспечения

Для эффективной реализации конвейеров данных необходима стратегия планирования задач ЦП для распределения работы между доступными ядрами процессора, а также использование структур данных, с которыми будут оперировать этапы конвейера. Например, производные UNIX могут конвейеризировать команды, соединяющие стандартный ввод-вывод различных процессов, используя каналы, реализованные операционной системой. Подходы нижнего уровня могут полагаться на потоки, предоставляемые операционной системой, для планирования работы на этапах: жизнеспособны и существуют как реализации на основе пула потоков, так и реализации с одним потоком на этап. Существуют и другие стратегии, основанные на кооперативной многозадачности, которые не требуют нескольких потоков выполнения и, следовательно, дополнительных ядер ЦП, например, использование циклического планировщика с фреймворком на основе корутин. В этом контексте каждый этап может быть реализован с собственной корутиной, передавая управление обратно планировщику после завершения своей задачи. Такой подход может потребовать тщательного контроля над этапами конвейера, чтобы избежать превышения выделенного времени.

Затраты и недостатки

Система с конвейерной обработкой обычно требует больше ресурсов (схемные элементы, процессорные устройства, компьютерная память и т. д.), чем система, выполняющая обработку по одному блоку за раз, поскольку её этапы не могут совместно использовать эти ресурсы, а также из-за необходимости буферизации и дополнительной логики синхронизации между элементами. Более того, передача данных между отдельными процессорными элементами может увеличить задержку, особенно в длинных конвейерах. Дополнительная сложность, связанная с конвейерной обработкой, может быть значительной, если существуют зависимости между обработкой различных элементов, особенно при использовании стратегии предсказания и отката для их обработки. Фактически, стоимость реализации такой стратегии для сложных наборов команд послужила стимулом для радикальных предложений по упрощению компьютерной архитектуры, таких как RISC и VLIW. Компиляторы также были нагружены задачей перестановки машинных инструкций для повышения эффективности работы конвейера команд.

Новые технологии

Это правда, что в последние годы требования к приложениям и их базовому оборудованию значительно возросли. Например, создание конвейеров с приложениями, обрабатывающими данные построчно на одном узле, больше не представляется возможным из-за объема и разнообразия больших данных. Однако с появлением движков для анализа данных, таких как Hadoop или, более недавно, Apache Spark, стало возможным распределять большие наборы данных между множеством вычислительных узлов, что позволило приложениям достичь эффективности, в несколько сотен раз превышающей прежние возможности. В результате, сегодня даже компьютер среднего уровня, использующий распределенные вычисления, способен создавать и запускать конвейеры обработки больших данных.