Доброго времени суток всем.
Недавно состоялся телефонный разговор, с одним товарищем. Он не программист, а занимается 3d графикой. И были у него проблемы с импортом шейдеров в готовые модели на каком-то специфичном движке. Все импортировалось, но жутко тормозило. Пришлось на пальцах ему объяснять что такое есть конвейер и как это все работает( учитывая мои поверхностные знания видео-процессоров ;) )
Как результат этого разговора, меня посетила некоторая идея, которая и вылилась в этот пост.
Сразу оговорюсь, в 99% вашей практики оно вам мало пригодится, но думаю далеко не каждый из нас представляет что такое есть это пресловутый конвейер, почему их ставят несколько и что это дает, а так же можно ли зная о наличии и свойствах конвейера сделать оптимизацию кода. Объяснять буду "на пальцах", кое что будет не совсем(или даже совсем не) соответствовать действительности, но поможет понять суть.
Что такое есть конвейер.
Думаю большинству из нас понятно, что в процессоре все команды выполняются не на одном дыхании, а представляют собой последовательность простейших действий. Например: чтение кода операции, загрузка операндов, выполнение действий, занесение результата. Большинство процессоров имеют схожее разбиение. Таким образом процессор работающий на частоте 1 ГГц реально выполняет команды с частотой 250 МГц. (реально все несколько не так, но мы не об этом) таким образом мы имеем 4 однотипных микродействия для каждой команды. Как сделать чтобы процессор работал быстрее при той же частоте? Правильно, поручить эти четыре действия разным блокам и поставить их в очередь.
Теперь когда у нас считывается код текущей операции, производится получение операндов предыдущей, параллельно выполняется собственно действие той что считывалась два такта назад и занесение результатов вычислений той что читалась четыре такта назад. Все вроде бы классно. Наш теоритический процессор теперь реально работает на весь 1ГГц, но тут есть несколько подводных камней.
1) Что делать если одним из операндов является результат предыдущей операции?
2) Что делать если в результате одной из операций происходит условный переход?
Ведь тогда действия выполненные для трех комманд были произведены в пустую.
В первом случае, можно создать механизм тормозящий очередь операций и мы будем терять один или два такта в зависимости от того какие команды и операнды были использованы. Или можно поменять разбиение на другие подоперации(что сейчас и делается) и тогда наоборот можно выиграть на таких операциях.
Во втором случае изначально исползовался подход: "Прямая работа программы является доминирующей" т.е. продолжали грузить операции следующие за кодом условного перехода, и в 90% случаев теряли три такта. Сейчас используют различные подходы. От простейших, где существует жесткая зависимость код операции - направление обработки, до применения вероятностных алгоритмов учитывающих различные факторы.
В любом случае имеем следующую модель:
1) Наличие ветвлений сводит на нет конвейеры
2) наличие операций типа (2+3)*2+2/4*7..... в зависимости от типа конвейера либо тормозит либо дает выигрыш.
Прим. В принципе в нашей модели процессора мы имеем четырехуровневый конвейер с не очень хорошим( и это мягко сказано ) разбиением на подоперации( именуемые микрокомандами ).
Исходя из этого, лучше:
1) применять процессора с другим разбиением на микрокоманды (очень эффективно для процессоров с небольшой системой команд работает разбиение на две микрокоманды: чтение кода операции/ выполнение операции, но этот метод не применим для процессоров по мощности не уступающих x86, в следствии большого количества команд различной сложности) к стати большинство современных процессоров имеют совсем другое разбиение с глубиной конвейеров от 5 до 18 (а может и больше)
2) На критичных к скорости( это сейчас редко встречается, но все же ) участках, постараться обойтись без условных переходов.
3)В любом случае и на любом конвейере:
1: mov al, 23
2: mov bl, 24
3: mov cl, 25
4: mov dl, 26
5: add al, bl
6: xor cl, dl
7: xor al, cl
будет быстрее чем
1: mov dl, 26
2: mov cl, 25
3: mov bl, 24
4: mov al, 23
5: add al, bl
6: xor al, cl
7: xor al, dl
Попробуйте понять почему.
кроме того, если вы проанализируете сам смысл разбиения на микрокоманды и конвейерной обработки последних, то поймете что большая глубина конвейера не является плюсом, а скорее даже наоборот. Хотя, конечно, все зависит от ситуации ;)
Ну и еще раз повторюсь, в языках высокого уровня а так же в случаях когда вы не программируете шейдеры, какие-то видео-фильтры, компиляторы наконец, для вас это все имеет сугубо познавательный интерес. (В случаях же описанных выше, желательно ознакомиться с документацией по реальным процессорам, под которые все это проектируется, т.к. все что я писал, всего лишь плод моих мыслей помноженных на опыт и знания, а никак не описание реальных процессоров