Новые «плюшки» компилятора – безопасней, быстрее, совершеннее

в 10:37, , рубрики: c++, fortran, intel компиляторы оптимизация, Блог компании Intel, Программирование, метки:

Новые «плюшки» компилятора – безопасней, быстрее, совершеннее - 1
Как говорилось во всеми нами любимом фильме: «Налетай, торопись, покупай живопись». Последняя, конечно, тут ни при чем, а вот «налетать» на новую Бета версию компилятора уже пора. Сегодня я расскажу о том, что нового появилось в пакете Intel Parallel Studio XE 2018 Beta, и в частности, в компиляторной её составляющей. А там действительно много что добавилось, ведь стандарты не стоят на месте — C++14, C++17, Fortran 2008, 2015, OpenMP 4.5 и 5.0, а компилятор должен не только их поддерживать, но и генерировать совершенный, производительный и безопасный код. Кроме этого, новые наборы инструкций AVX512, позволяющие «снимать сливки» с последних процессоров Skylake и KNL, всё больше входят в арсенал современных компиляторов. Но а самое вкусное — новые ключи, которые позволяют получить ещё больше производительности «не напрягаясь». Итак, поехали!

Сразу скажу, что качать Beta версию можно здесь:

Intel Parallel Studio XE 2018 Pre-Beta survey

Всё, о чем я буду говорить, включено в эту версию компиляторов и Update 1, который очень скоро будет доступен. Что потом окажется в финальном продукте – вопрос сложный, но, «вангую», почти всё. Что же мы имеем в новой версии 18.0 Beta?

Безопасность кода

В Microsoft изо всех сил стараются противостоять хакерам и придумывают всё новые и новые технологии. Для начала, они сделали в своём компиляторе С++ дефолтным максимальный уровень защиты стэка через опцию /GS:strong, которая позволяет бороться с переполнением буфера. При этом делается это в ущерб производительности, но безопасность превыше всего. Так как Intel под Windows пытается быть полностью совместимым с компилятором от Microsoft, то начиная с новой версии мы тоже включаем /GS:strong по умолчанию. Ограничить её действие и немного улучшить производительность можно с помощью /GS:partial.

Кроме этого, идёт разработка новой технологии CET (Control-Flow Enforcement Technology), позволяющей бороться с атаками методом возвратно-ориентированного программирования (ROP и JOP-атаками). Одна из идей защиты состоит в том, что появляется ещё один защищенный shadow стэк, в который будет записываться/дублироваться адрес возврата. Когда мы доходим до возврата из функции, происходит проверка корректности возвращаемого процедурой адреса и того адреса, который мы положили в shadow стэк. Кроме этого, добавляется новая инструкция ENDBRANCH для того, чтобы обозначать области в программе, на которые можно делать непрямые переходы через call/jmp.

Реализован конечный автомат, и как только процессор обрабатывает одну из инструкций call/jmp, он переходит из состояния IDLE в WAIT_FOR_ENDBRANCH. Собственно, в этом состоянии следующая инструкция для выполнения должна быть ENDBRANCH. Гораздо больше деталей написано в указанной выше статье, а в компиляторах Intel для С/С++ и Fortran появилась поддержка CET через опцию -cf-protection. По умолчанию, она не включена и, естественно, может влиять на производительность при использовании.

Кто готов протестировать новую защиту? Важное замечание заключается в том, что для полноценной работы CET необходима поддержка ОС и RTL, а её пока нет.

Производительность

Теперь поговорим о новых опциях компиляторов, которые позволят сделать ваши приложения ещё быстрее и производительнее.

Есть такая компиляторная оптимизация, которая называется function splitting (разделение функций). Для того чтобы понять, зачем она нужна, стоить вспомнить про встраивание кода и то, что одним из эффектов является увеличение его размера. Поэтому inlining не имеет смысл при больших размерах самой функции, которую мы хотим встроить в место вызова. Именно в этих случаях разбиение функции и частичный inlining нам поможет не допустить чрезмерного увеличения размера кода, сохранив её плюсы. В итоге наша функция будет разбиваться на две части, одна из которых (hot) будет встроена, а другая (cold) – нет.

На самом деле, эта оптимизация уже долгое время присутствует в 32-битных компиляторах Intel для Windows, при использовании Profile-Guided Optimization (PGO). Вот, кстати, интересный пост про эту оптимизацию в gcc. Идея проста – скомпилировать наше приложение, сделав инструментацию, затем запустить его и собрать данные о том, как оно выполнялось (профиль), и, уже учитывая эти данные из рантайма, пересобрать код ещё раз, применив полученные знания для более мощной оптимизации.

Теперь понятно, почему с PGO было возможно использовать function splitting, ведь мы хорошо знаем для каждой функции, какая её часть сколько времени выполнялась и что нужно инлайнить.

Сейчас мы уже позволяем разработчикам контролировать эту оптимизацию «ручками», добавляя ключ -ffnsplit[=n] (Linux) или -Qfnsplit[=n] (Windows), который говорит компилятору, что нужно выполнять разбиение функции с вероятностью выполнения блоков равным n или меньше. При этом не важно, включено PGO или нет, но нам обязательно указывать этот параметр n. Если его не указать, то данная оптимизация будет выполняться только при наличии динамической информации от PGO. Значения n могут быть от 0 до 100, но наиболее интересные для нас находятся в первой половине. Скажем, при PGO и 32 битном компиляторе на Windows использовалось значение 5, означающее, что если вероятность выполнения менее 5%, то этот блок не будет инлайнииться.

Если мы заговорили про PGO, то обязательно стоит сказать, что и здесь в новой версии Студии произошли приятные изменения. Раньше эта оптимизация работала только с инструментацией, но теперь возможна работа с использованием сэмплирования из профилировщика VTune. К реализации такой фичи подтолкнула невозможность применения традиционного PGO на real time и embedded системах, где имеются ограничения на размер данных и кода, а инструментация могла его существенно увеличить. Кроме этого, на подобных системах невозможно выполнять I/O операции. Аппаратное сэмлирование из VTune позволяет существенно снизить накладные расходы при выполнении приложения, при этом не происходит увеличения использования памяти. Этот способ даёт статистические данные (при инструментации они точные), но при этом применим на системах, где традиционный PGO «буксует».

Схему работы с новым режимом PGO можно представить в виде диаграммы:

Новые «плюшки» компилятора – безопасней, быстрее, совершеннее - 2

Как и прежде, нам нужно скомпилировать наш код для последующего сбора статистических данных. Только теперь это делается с помощью опции -prof-gen-sampling (Linux) или /Qprof-gen-samplig (Windows).

На выходе мы получим бинарники с расширенной дебаг информацией (что увеличит размер на допустимые 5-10%), но без инструментации. А далее нам понадобится специальный скрипт из VTune для запуска приложения и генерации профиля. После чего (если не нужно мержить несколько профилей), просто пересобираем наш код с полученными данными с ключиком -prof-use-sampling (Linux) или /Qprof-use-sampling (Windows). Для работы с этими опциями нам понадобится VTune, поэтому нужна установка не только компилятора, но и профилировщика. В Beta пакете имеется и то, и другое.

Теперь поговорим о работе с математическими функциями из библиотеки SVML (Short Vector Math Library), предоставляющей векторные аналоги скалярных математических функций.

Сразу несколько изменений коснулись SVML с выходом новой версии. Для того, чтобы убрать накладные расходы при динамическом диспатчинге, теперь на этапе компиляции будет генерироваться прямой вызов необходимой функции, используя заданные значения ключа -x. До этого мы в рантайме проверяли, какой у нас процессор, и вызывали нужную версию функции. И хотя overhead не является большим для обычных функций, при интенсивной работе с математическими функциями (например, экспонентой), он может составлять весомые 10%. Особенно востребованным это будет при вычислениях в финансовом сегменте приложений.

Тем не менее, если нам понадобиться вернуть «старое» поведение компилятора, то нам поможет опция -fimf-force-dynamic-target (Linux) или /Qimf-force-dynamic-target (Windows).

Из той же финансовой области пришло и другое изменение. При работе с математикой важна не только производительность, но и воспроизодимость результатов. Я уже писал о замечательных опциях, позволяющих заботиться об этом -fp-model (Linux) и /fp (Windows). Так вот задавая модель работы с числами с плавающей точкой как precise (-fp-model precise (Linux) или /fp:precise (Windows)), мы лишали себя удовльствия использовать векторные математические функции из SVML, что, конечно, отрицательно сказывалось на производительности, но весьма положительно на воспроизводимость результатов. Теперь разработчики позаботились о том, чтобы производительность не влияла на стабильность численных результатов. С помощью ключика -fimf-use-svml (Linux) или /Qimf-use-svml (Windows) можно сказать компилятору использовать скалярные функции из SVML вместо их вызовов из стандартной библиотеки LIBM. А так как они позаботились о том, чтобы скалярные и векторные версии SVML давали одинаковые результаты, то теперь и при использовании precise модели можно использовать векторные математические функции.

При работе с различными буферами используется большое количество функций, например memcpy, memset и т.д. При наличии их вызовов компилятор использует свою внутреннюю логику и может пойти различными путями: вызывать соответствующую библиотечную функцию, генерировать rep инструкции или развернуть операции в цикл при условии, что он знает размер во время компиляции. Так получилось, что он не всегда правильно угадывает нужный подход, поэтому теперь имеется опция -mstringop-strategy (Linux) или /Qstringop-strategy (Windows), с помощью которой можно сказать компилятору, что делать с такими функциям, работающими с буферами/строками (strings, отсюда и название ключика). Можно указать, соответственно, libcall, rep или const_size_loop в качестве аргумента для опции. Например при компиляции с ключом -Os (заботимся о размере наших бинарников), будет неявно использоваться опция -mstringop-strategy=rep.

Для более производительного кода на системах, поддерживающих AVX-512, появилась опция -opt-assume-safe-padding (Linux) или /Qopt-assume-safe-padding (Windows).

Она позволяет компилятору предполагать, что он может безопасно обращаться к 64 байтам после каждого массива или переменной, выделенной приложением. Ранее данная опция была доступна для KNC, теперь же её можно использовать и для последних архитектур с поддержкой AVX-512. В определённых случаях подобная «вольность» позволит компилятору сгенерировать немаскированные операции загрузки вместо маскированных, например, при использовании G2S (gather to shuffle) оптимизации. Но важно выравнивать данные по 64 байта.

Заключение

Это, пожалуй, наиболее важные из новых «волшебных» опций, которые появились в последней версии компилятора. Но кроме всего этого, была добавлена поддержка почти всего стандарта OpenMP 4.5 (нет только user defined recutions), а так же часть нового поколения OpenMP 5.0 (например, редукции в task’ах).

Стандарты С++11 и С++14 полностью поддерживаются ещё с версии 17.0, а вот полная поддержка Fortran 2008 появилась только сейчас. Да и последний стандарт С++17 будет поддерживаться гораздо в большем объёме, и это учитывая, что он ещё окончательно не принят.

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

Автор: ivorobts

Источник

Поделиться

* - обязательные к заполнению поля