На прошлой неделе вышла новая версия компилятора С/С++ от Intel — 16.0 aka Parallel Studio XE Composer Edition for C++. Существенно расширилась поддержка новых стандартов (C11, C++14, OpenMP 4.1), возможности по работе с Xeon Phi, вышли новые версии библиотек и ещё много всего «вкусного». Давайте более подробно посмотрим на то, что появилось в последнем релизе. Поехали!
- Добавлена поддержка SIMD операторов для работы с целочисленными типами SSE на Линуксе. Теперь следующие операторы работают: + — * / & | ^ += -= *= /= &= |= ^= == != > < >= <=
Простой пример, который раньше не компилировался (не забываем инклудить immintrin.h):
__m128i x,y,z; x = y + z;
Стоит отметить, что работает это только на Линуксе. Компилятор на Windows всё так же ругается: operation not supported for these simd operands
Кроме того, поддерживаются только 128 и 256 битные SIMD типы, и только форма с двумя операндами, как в примере. Сами операнды должны быть одного типа, например, типы SSE от Intel не могут использоваться вместе с GNU типами, объявленными с атрибутом vector_size.
- Существенно расширилась поддержка стандартов. Пожалуй, это направление развития компилятора можно так же отнести к одной из самых главных в новой версии.
Так, если в версии 15 из стандарта C11 (для языка С, не путать с С++11) поддерживались только бинарные литералы (начинаются с префикса 0b или 0B), то сейчас есть практически всё. Последний общедоступный драфт стандарта можно найти здесь в свободном доступе. Хорошей обзорной статьи на русском про С11 я не нашёл, поэтому про каждую возможность языка напишу более детально. Не забываем, что при компиляции нужно указывать ключ /Qstd=c11 на Windows и -std=c11 на Linux и Mac OS X для того, чтобы всё это заработало:
- Новые ключевые слова (как в C++11) для выравнивания данных _Alignas и _Alignof, позволяющие уйти от компиляторозависимых решений:
// массив cacheline выравнен по 64 байта _Alignas(64) char cacheline[64]; printf("Alignment of char = %d\n", _Alignof(char));
Про необходимость и значимость выравнивания данных я уже писал ранее.
- Выражения, не зависящие от типа, с использованием ключевого слова _Generic. Это своего рода «шаблоны» из С++. Например, следующий макрос для извлечения квадратного корня sqrt(x) транслируется в sqrtl(x), sqrt(x) или sqrtf(x) в зависимости от типа параметра x:
#define sqrt(x) _Generic((x), long double: sqrtl, default: sqrt, float: sqrtf)(x)
А ведь раньше приходилось хорошо потрудиться, чтобы реализовать это ручками самому!
- Спецификатор функции _Noreturn позволяет объявлять функции, которые никогда не возвращаются в вызывающий код. Это позволяет избегать предупреждений от компилятора для функций, у которых нет return’а, а так же включать ряд оптимизаций, которые можно выполнять только над «невозвратными» функциями.
_Noreturn void func (); // func never returns
- Новое ключевое слово _Static_assert, позволяющее выдавать ошибку компиляции в случае, если выражение равно нулю. Простой пример:
// ошибка во время компиляции static_assert(sizeof(int) < sizeof(char), "app requires sizeof(char) to be less than char"); error: static assertion failed with "app requires sizeof(char) to be less than char"
В отличие от директив #if и #error позволяет отлавливать ошибки, которые трудно найти во время препроцессинга.
- Анонимные структуры и объединения. Это некоммерческое общество анонимных алкоголиков… Шучу, просто проверил вашу концентрацию. Они используются для вложения структур и объединений. Например:
struct T // C11 { int m; union //анонимное объединение { char * index; int key; }; }; struct T t; t.key=1300; //прямой доступ к члену объединения key
- Особенность стандарта С11 в том, что он стандартизирует многопоточность в языке С. Конечно же, разработчики уже давным-давно используют блага параллельности в С, но, тем не менее, через библиотеки и другие расширения языка. Сейчас же это прописано в стандарте.
Так, одно из новых ключевых слов, поддерживаемое компилятором Intel, _Thread_local. С его помощью можно указать, что переменная не является общей для потоков и каждый из них получает свою локальную копию.
- Новые ключевые слова (как в C++11) для выравнивания данных _Alignas и _Alignof, позволяющие уйти от компиляторозависимых решений:
- По стандартам С и С++, компилятор не обязан соблюдать приоритет для вычислений выражений в скобках. Например, далеко не факт, что сложение B и C в выражении A+(B+C) будет выполнено в первую очередь, что приводит к различиям в численных результатах. Наконец появилась опция компилятора, которая отключает оптимизацию, меняющую порядок суммирования (реассоциацию) для типов с плавающей точкой. Теперь, если используются опции -fprotect-parens (Linux* OS и OS X*) или /Qprotect-parens (Windows*), порядок выполнения операций будет определяться расставленными скобками. Использование данной опции может несколько замедлить выполнение кода. По умолчанию, компилятор не включает данную опцию.
- Полностью реализовав поддержку С++11 в версии 15.0, разработчики компилятора вплотную занялись следующим – С++14, который сейчас будет поддерживаться более чем на половину. Аналогично С11, есть страничка, на которой отслеживается поддержка различных возможностей стандарта в разных версиях компилятора. Включить поддержку С++14 можно опцией /Qstd=c++14 на Windows и -std=c++14 на Linux и Mac OS X.
Итак, что же теперь поддерживается, начиная с нового релиза:
- Обобщённые лямбда-функции
- Захват выражений для лямбда-функций
- Разделители разрядов
- Атрибут [[deprecated]]
- Вывод типа возвращаемого значения для функций
- Агрегатная инициализация классов с инициализаторами полей
Более детальное описание этих возможностей стандарта можно без труда найти в сети, например, на Вики, или в отличных постах на Хабре (Ч1 и Ч2).
Так же появилась поддержка макросов (Feature Test macros), позволяющих определять поддержку той или иной возможности стандарта компилятором, или наличия хедера. Этот функционал пока ещё не в стандарте С++14, а только в TR, но обещает быть в скором времени. Простой пример:
#if __cpp_binary_literals int const packed_zero_to_three = 0b00011011; #else int const packed_zero_to_three = 0x1B; #endif
Теперь мы очень легко можем определить, поддерживаются ли компилятором бинарные литералы. Более подробно (например, найти табличку с именами макросов __cpp_binary_literals, __cpp_digit_separators и т.д.) ознакомиться с этой полезной возможностью можно здесь.
- Добавлена очень полезная директива pragma block_loop, позволяющая контролировать оптимизацию с разделением циклов на блоки (loop blocking), о которой я подробно писал в этом посте.
- Поддержка очередной версии стандарта OpenMP 4.1 (Technical Report 3), в основном расширяет возможности по работе с оффлодом (выгрузке) вычислений на сопроцессор Xeon Phi и другие возможные ускорители:
- Появилась новая директива omp target enter data для того, чтобы мапить переменные на сопроцессор (возможно задавать to и alloc для опции map). Если директива omp target мапила переменные и выполняла код на устройстве, то omp target data только занимается данными. Соответственно, теперь имеется и директива omp target exit data для unmap’a переменных (возможно задавать from, release и delete для опции map).
- Улучшены возможности по асинхронному выполнению кода. target регион теперь является задачей (task), поэтому возможен асинхронный оффлод с помощью существующей модели работы с задачами и опции nowait для директивы omp task.
- Опция depend для директивы omp task, позволяющая реализовать выгрузку с зависимостями
- Новые модификаторы always и delete для опции map
Кроме того, что в рамках OpenMP 4.1 существенно расширяются возможности по работе с ускорителями, так же доработана специфичная для компилятора Intel реализация работы с сопроцессорами:
- Если раньше было невозможно передавать через указатель поле объекта для выгрузки на сопроцессор в виде ptr->field, то теперь это ограничение убрано. При этом появилась возможность передать структуры, поля которой являются указателями. Сами структуры в этом случае передаются побитово, указатели копируются, а вот поля структур, на которые они указывают, нет.
- Стало возможным выделять память только на сопроцессоре, не выделяя память на хосте, с помощью модификаторов targetptr и preallocated.
- Появилось понятие stream (и новая опция stream для директивы pragma offload) – логической очереди для выгрузок. С её помощью теперь можно выгрузить несколько независимых вычислений на Xeon Phi из одного CPU потока. Порядок работы такой:
Сначала, создаем стрим с помощью API функции _Offload_stream_create:
OFFLOAD_STREAM* handle = _Offload_stream_create( int device, // Intel® MIC Architecture device number int number_of_cpus); // Threads allocated to the stream
И оффлодим в стрим с помощью директивы offload и опции stream, указывая при этом сигнальное значение для идентификации выгрузки. Это поможет определить, выполнен ли тот или иной оффлод:
// Issue offload to a stream and identify with signal value s1 #pragma offload … stream(handle) signal(s1) { … } … // Issue offload to a stream and identify with signal value s2 #pragma offload … stream(handle) signal(s2) { … } … // Check if offload with signal value s1 has completed if (_Offload_signaled(s1)) …
Гораздо больше деталей можно найти в нашей документации, которая была ощутимо расширены с выходом новой версии.
Кроме всего перечисленного, естественно вышли и новые версии всех библиотек (Intel IPP, TBB, MKL), где тоже можно найти много всего интересного. Кроме того, в дополнение к известным «трехбуквенным», добавилась новая библиотека Intel DAAL, о которой я уже рассказывал в отдельном посте.Мой список улучшений и дополнений не является самым полным, но я постарался рассказать о наиболее значимых вещах. За бортом обзора остались изменения в Intel Cilk™ Plus, новые листинги с аннотациями от компилятора, улучшения скорости компиляции и ещё много мелких доработок. Пробуем самую свежую версию, которая всё так же доступна с пробной лицензией на 30 дней (без каких-либо функциональных ограничений и с полной поддержкой) и делимся с нами вашим опытом!
QtRoS
А кто использует компилятор от Intel?
Это не сарказм или «наезд», просто интересен сегмент.
FoxCanFly
Intel, очевидно
mezastel
Я использую. Но это специфичное удовольствие. В основном потому что компилятор ломается без причины и поддерживает новые фичи С++ одним из последних.
ivorobts
Если пробовать новые фичи С++ -то да, будут вылазить проблемы, как и с любым новым кодом — багфикс ещё никто не отменял.
mezastel
Да, но тут компилятор просто глухо ломается, «ошибка номер 3», без документации. Приходится писать в Intel напрямую.
OlegMax
Когда уже они перейдут на Clang frontend? Сильная сторона Intel — оптимизация, зачем тратить ресурсы на повторение сделанного другими?
Kepp
В процессе. Насколько я знаю, команду под этот проект сформировали около года назад.
Причина в том, что текущий компилятор настолько прекрасен, что что-то новое туда добавить (С++11,14,17) совсем непросто.
ivorobts
В первую очередь тем, кто заботится о производительности своего приложения и хочет использовать максимум от своего «железа».
В частности, в HPC он очень востребован. А ещё тем, кто хочет получить хорошую поддержку, ибо баги есть в любом компиляторе, весь вопрос как быстро их решат для вас.
AxisPod
Ну исходя из того, что фичи новых стандартов добавляют очень медленно, есть очень не маленькие шансы, что в других компиляторах баги уже пофиксят, а в интеловском фичи с багами только появятся.
ivorobts
Я бы не сказал, что компилятор от Интел в плане поддержки стандартов проигрывает. Скажем, тот же С++11 полностью поддерживается у Microsoft только с VS2015, а в Интеловском с прошлой версии 15.0, что даже чуть раньше. На Windows для Интела ориентир в сторону Visual C++, на Линуксе gcc. GNUшному конечно, проигрывает в каких то фичах, но зато OpenMP лучше поддерживает(4.0 раньше всех поддерживался). В целом не всё так однозначно.
pak63
Я использую, хорошие возмножности для оптимизации кода. Советую вам попробовать PGO для своих задач, если используете VS2013 и выше, то там это сделано очень удобно. Компилятор интегрируются в VS, а для PGO сделано отдельное окно. Однако, сам был удивлен падению компилятора на конструкции copyin в OpenMP.