Array Building Blocks: два блока компиляции

Во втором посте по Array Building Blocks (первый мой пост здесь) я планировал рассказать о блюде под названием ArBB runtime и разобрать пару примеров кода: более простой, но медленный и более сложный, но быстрый. Но думается, что этот пирог лучше есть по частям. Остановимся пока на описании runtime и выясним, что означает двухфазная компиляция в ArBB.

Сразу надо признать, что ArBB runtime в значительной степени - это черный ящик, о его внутренней структуре мы знаем немного. Оно и понятно: повара не любят раскрывать рецепты своих блюд. Начнем с общеизвестной информации, не связанной непосредственно с ArBB. Когда приложение скомпилировано, динамически слинковано, находится на исполнении и встречается вызов библиотечной функции, начинает исполняться код самой библиотеки. Таким образом, если в приложении встречается вызов arbb::call(), ArBB берет на себя контроль над вычислениями, а по завершении контроль возвращается к самому приложению.

Дальше идет специфика. «Фишка» ArBB наличии собственной среды исполнения, которая осуществляет динамическую компиляцию с оптимизацией под текущую платформу во время исполнения. Таким образом, первая фаза компиляции - это собственно компиляция С++ кода в исполняемый файл, а вторая - во время работы приложения. Для этого в составе ArBB runtime есть JIT (Just In Time) компилятор, генерирующий бинарный код для исполнения конкретной операции с учетом количества ядер, поддерживаемых процессором SIMD инструкций (SSE, AVX) и т.д. Для чего все это? Проблема в том, что если весь бинарный код будет готов еще до исполнения, то он не может быть оптимален сразу для разного железа. Будет неправдой сказать, что это не пытались обойти без динамической генерации кода. Например, можно положить в библиотеку несколько заранее подготовленных вариантов бинарного кода для одной и той же операции. Когда идет обращение к библиотеке, специальный модуль (Dispatcher) принимает решение, какой вариант лучше всего подойдет для текущей машины. У каждого из подходов есть свои недостатки, обсуждение которых я оставлю за рамками этого поста. Но нужно сказать, что подход с JIT-ом представляется более тяжеловесным с точки зрения накладных расходов, но более легким с точки зрения объема библиотеки и более гибким с точки переносимости и количества поддерживаемого железа.

Теперь нюанс. Дело в том, что JIT генерирует код не всегда. Согласно опубликованной информации, в зависимости от уровня оптимизации ArBB предлагает два варианта. В неоптимизированном случае просто исполняется код, «зашитый» в библиотеку изначально. Случай с оптимизацией более интересен. Здесь JIT компилятор генерирует код требуемой операции на лету, и этот код будет оптимизирован под платфому, на которой идет исполнение. После этого сгенерированный код исполняется. Кроме того, ArBB runtime записывает этот код, чтобы не генерировать его снова, если этот же вызов будет выполнен в приложении еще раз.

Здесь становятся видны сходство и разница между JIT из ArBB и JIT из .NET. И тот и другой получают на вход некую конструкцию (ArBB вызов или IMSL), генерируют бинарный код, и записывают его на случай, если в приложении потребуется выполнить его снова. ArBB JIT генерирует оптимизированный код, чтобы приложение быстро работало там, где оно реально выполняется. Сами приложения, использующие ArBB (насколько я понимаю) - это обычный бинарный код с библиотечными вызовами. .NET же предлагает дополнительный уровень абстракции на уровне приложения (IMSL), в часности, предлагая независимость от языка программирования.

For more complete information about compiler optimizations, see our Optimization Notice.