Хочу написать статью об оптимизации игр для разных платформ

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

4747

Комментарий недоступен

11
Ответить

1/2

Условно, почему почти одна и та же картинка может идти в 30 фпс FHD, а после оптимизации в 2k 60 фпс.

Как правило вот такой разлёт говорит о переходе от "тестовой" (в лоб, чтобы просто посмотреть на результат) версии к нормальной.

SIMD (avx это лишь "один из") концептуально очень просто понять. Оставим, для простоты понимания, в стороне вопросы работы с памятью и размеры вообще, сосредоточимся именно на вычислениях.

Есть у нас очень простой процессор, который имеет одно ядро и 1 очень простой исполнительный блок для целочисленной арифметики. Каждый блок выполняет только сложение/вычитание строго за 1 такт. Ещё есть один порт (execution port, не приходит на ум как его по-русски нормально обозвать) - блок, который отвечает за распределение задач между исполнительными блоками (вычисления, чтение, запись), который может распределить одну задачу за 1 такт. И есть декодер инструкций, который за 1 такт выясняет, что же именно нужно сделать и отправляет эту информацию на порт. Пришла инструкция, мы её декодировали, в регистрах (обычных) уже лежат нужные данные, нужно только сложить и сохранить в третьем регистре. Супер, порт смотрит на инструкцию, отправляет её исполнительному блоку, тот считает, сохраняет результат. Всё хорошо, всё работает, но хочется большего.

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

Улучшаем наш процессор: теперь у нас три блока. Можно на такте t0 первому блоку отправить деление, пока он работает второму на такте t1 - умножение, третьему на такте t2 - сложение. Вуаля, теперь простоя куда меньше и за 3 фактических такта мы и сложили, и умножили и поделили (попутно изобрели примитивный параллелизм на уровне инструкций: ядро процессора одно, а фактически выполнение идёт параллельно). Но это всё ещё такое себе, потому что если нам нужно только складывать, то простой никуда не денется.

Улучшаем наш процессор: теперь у нас есть два порта. Количественное улучшение, некоторый простой всё ещё никуда не делся.

Надо улучшать дальше, но возникает дилемма: если мы хотим выполнять больше вычислений, нам нужно либо больше вычислительных блоков, либо более качественные блоки (современные цпу, например, могут обычное целочисленное сложение/вычитание и умножение выполнить за одно и тоже время, если без блекджека и шлюх). Допустим, на данный момент мы не можем качественно улучшить блоки, значит нужно увеличить их количество. Но тогда мы возвращаемся к старой проблеме: простой. Хорошо, тогда увеличим количество портов. Тут можно подумать: почему бы просто не сделать N блоков и N же портов? Ответ простой: потому что выполняются инструкции разной сложности, и если мы уравним количество блоков и портов, то простаивать начнут эти самые порты, вместо блоков, а кремний на них потрачен, а площадь чипа не бесконечная. Получается, баш на баш с выбором когда простаивать: при большом числе сложных операций, или при большом числе простых.

И тут мы приходим к SIMD: если у нас есть куча данных, над которыми нужно произвести одну и ту же операцию, то, может, как-то это сгруппировать? Добавим несколько хитрых регистров, в который вмещается два числа. Введём, также, ограничение, что над всеми элементами такого регистра можно производить только какую-то одну инструкцию на выбор. Добавим, также, специальные инструкции, в каком-то роде "макро"-инструкции специально для работы с такими регистрами. Теперь, когда в порт приходит инструкция "сложить" для таких вот хитрых регистров, он не обрабатывает n случаев для n операций, он сразу может отправить одну и ту же команду на нужное число блоков. Т.е. декодер за 1 цикл выяснил, что нужно сделать, а порт за 1 цикл нагрузил сложением сразу 2 вычислительных блока. Счастье и радость.

Дальше идёт рост вширь: шире "хитрые" регистры, больше типов данных, которые туда можно засунуть, больше типов операций, которые можно выполнять.

4
Ответить

Про всё остальное расскажу в статье
Про всякие процессорные технологии я не очень знаком, могу сильно ошибаться. Выглядит как расширенный набор инструкций (если инструкция целиком реализована в процессоре, она работает быстрее, чем вызывать её части по отдельности) и более длинные регистры (позволяют хранить непосредственно в процессоре больше данных, а значит нужно меньше обращаться к кэшу и ждать ответа). Ну и какие-то мелкие оптимизации, которые ускоряют работу проца на 1-5%

3
Ответить