﻿# Опыт команды PVS\-Studio: повышение производительности C\+\+ анализатора на Windows при переходе на Clang

С самого своего начала C\+\+ анализатор PVS\-Studio для Windows \(тогда еще Viva64 версии 1\.00 в 2006 году\) собирался компилятором MSVC\. С выходом новых релизов C\+\+ ядро анализатора научилось работать на Linux и macOS, и структура проекта была переведена на использование CMake\. Но под Windows сборка по\-прежнему происходила с помощью компилятора MSVC\. 29 апреля 2019 года разработчики Visual Studio объявили о включении в свою среду разработки набора утилит LLVM и компилятора Clang\. И сейчас у нас наконец дошли руки, чтобы попробовать его в действии\.

![0830_Clang-build_ru/image1.png](https://import.viva64.com/docx/blog/0830_Clang-build_ru/image1.png)

## Тестирование производительности

В качестве бенчмарка воспользуемся нашей утилитой для регрессионного тестирования анализатора под названием SelfTester\. Суть её работы заключается в анализе набора разных проектов и сравнении результатов анализа с эталонными\. Например, если при каких\-то правках в ядре анализатора появились ложные предупреждения или пропали правильные, значит появилась регрессия, которую надо исправить\. Более подробно про SelfTester можно прочитать в статье "[Лучшее – враг хорошего](https://pvs-studio.ru/ru/blog/posts/csharp/0667/)"\.

Среди тестовой базы – достаточно разнообразные по объёму кода проекты\. Как правило, если рабочий компьютер или тестовый сервер не нагружен, то время тестирования SelfTester'ом на одной и той же версии ядра варьируется в пределах погрешности\. В случае если производительность анализатора не уберегли, это значительно скажется на общем времени тестирования\.

После того как сборка C\+\+ ядра перешла на Clang, SelfTester стал проходить на 11 минут быстрее\.

![0830_Clang-build_ru/image2.png](https://import.viva64.com/docx/blog/0830_Clang-build_ru/image2.png)

Выигрыш по производительности в 13% — это довольно заметно, учитывая, что достаточно просто поменять компилятор, не так ли?

Минусы тоже есть, но незначительные\. Сборка дистрибутива замедлилась на 8 минут, а размер исполняемого файла подрос на 1,6 Мбайт \(из них \~500 Кбайт из\-за статической линковки рантайма\)\.

![0830_Clang-build_ru/image3.png](https://import.viva64.com/docx/blog/0830_Clang-build_ru/image3.png)

Видимо, производительность достигается более долгим этапом LTO \(большую часть сборки занимает именно линковка\) и более агрессивным раскручиванием циклов и встраиванием функций\.

Далее хочется поделиться подводными камнями, возникшими в процессе перехода\.

## Генерация сборки под Clang

Скрипты CMake позволяют нам собирать свой код всеми мейнстримными компиляторами под нужные операционные системы\.

Прежде всего необходимо установить компоненты компилятора Clang через Visual Studio Installer\.

![0830_Clang-build_ru/image4.png](https://import.viva64.com/docx/blog/0830_Clang-build_ru/image4.png)

Clang\-cl — это так называемый "драйвер", который позволяет использовать clang с параметрами от cl\.exe\. Таким образом, он должен прозрачно взаимодействовать с MSBuild, практически как родной компилятор\.

Также можно воспользоваться официальными сборками от проекта LLVM, которые можно найти на их репозитории [GitHub](https://github.com/llvm/llvm-project/releases)\. Однако для них нужно установить дополнительный плагин, чтобы Visual Studio смогла найти компиляторы\. Имя toolset'а будет _llvm_, а не _clangcl_, как показано дальше в примерах\.

Указываем toolchain в команде генерации solution для Visual Studio:

```cpp
cmake -G "Visual Studio 16 2019" -Tclangcl <src>
```

Либо используем GUI:

![0830_Clang-build_ru/image5.png](https://import.viva64.com/docx/blog/0830_Clang-build_ru/image5.png)

Открываем получившийся проект, собираем\. И, конечно же, получаем пачку ошибок\.

![0830_Clang-build_ru/image7.png](https://import.viva64.com/docx/blog/0830_Clang-build_ru/image7.png)

## Чиним сборку

Хоть сlang\-cl внешне и ведет себя как CL, под капотом это совсем другой компилятор, со своими приколами\.

Мы стараемся не игнорировать предупреждения компиляторов, поэтому используем флаги /W4 и /WX\. Однако Clang может генерировать дополнительные предупреждения, которые сейчас мешают сборке\. Пока выключим их:

```cpp
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  ....

  if (WIN32)
    add_compile_options(-Wno-error=deprecated-declarations
                        -Wno-error=reorder-ctor
                        -Wno-error=format-security
                        -Wno-error=macro-redefined
                        -Wno-error=bitwise-op-parentheses
                        -Wno-error=missing-field-initializers
                        -Wno-error=overloaded-virtual
                        -Wno-error=invalid-source-encoding
                        -Wno-error=multichar
                        -Wno-unused-local-typedef
                        -Wno-c++11-narrowing)
  ....
  endif()
endif()
```

![0830_Clang-build_ru/image8.png](https://import.viva64.com/docx/blog/0830_Clang-build_ru/image8.png)

Немного получше\.

Компиляторы GCC и Clang имеют встроенную поддержку типа _int128_, в отличие от MSVC под Windows\. Поэтому в своё время была написана обертка с реализацией _Int128_ для Windows \(на ассемблерных вставках и обернутая ifdef'ами, в лучших традициях C/C\+\+\)\. Поправим определения для препроцессора, заменив:

```cpp
if (MSVC)
  set(DEFAULT_INT128_ASM ON)
else ()
  set(DEFAULT_INT128_ASM OFF)
endif ()
```

на

```cpp
if (MSVC AND NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  set(DEFAULT_INT128_ASM ON)
else ()
  set(DEFAULT_INT128_ASM OFF)
endif ()
```

![0830_Clang-build_ru/image9.png](https://import.viva64.com/docx/blog/0830_Clang-build_ru/image9.png)

Обычно библиотеку с builtin'ами линкеру \(lld\) передает драйвер компилятора, будь то clang\.exe или clang\-cl\.exe\. Но в данном случае линкером заправляет MSBuild напрямую, который не знает, что нужно её использовать\. Соответственно, драйвер никак не может передать флаги линкеру, поэтому приходится разбираться самим\.

```cpp
if (CMAKE_GENERATOR MATCHES "Visual Studio")

  link_libraries("$(LLVMInstallDir)\\lib\\clang\\\
${CMAKE_CXX_COMPILER_VERSION}\\lib\\windows\\\
clang_rt.builtins-x86_64.lib")

else()
  link_libraries(clang_rt.builtins-x86_64)
endif()
```

![0830_Clang-build_ru/image11.png](https://import.viva64.com/docx/blog/0830_Clang-build_ru/image11.png)

Ура\! Сборка заработала\. Однако дальше при запуске тестов нас ждала куча ошибок сегментации:

![0830_Clang-build_ru/image12.png](https://import.viva64.com/docx/blog/0830_Clang-build_ru/image12.png)

Отладчик при этом показывает какое\-то странное значение в _IntegerInterval_, а на самом деле проблема находится немного дальше:

![0830_Clang-build_ru/image14.png](https://import.viva64.com/docx/blog/0830_Clang-build_ru/image14.png)

В разных структурах для Dataflow\-механизма активно применяется ранее упомянутый тип _Int128_, а для работы с ним используются SIMD\-инструкции\. И падение вызвано невыровненным адресом:

![0830_Clang-build_ru/image16.png](https://import.viva64.com/docx/blog/0830_Clang-build_ru/image16.png)

Инструкция MOVAPS перемещает из памяти набор чисел с плавающей запятой в регистры для SIMD\-операций\. Адрес при этом обязан быть выровнен, в его конце должен стоять 0, а оказалась 8\. Придется помочь компилятору, задав правильное выравнивание:

```cpp
class alignas(16) Int128
```

![0830_Clang-build_ru/image18.png](https://import.viva64.com/docx/blog/0830_Clang-build_ru/image18.png)

Порядок\.

Последняя проблема вылезла из\-за Docker\-контейнеров:

![0830_Clang-build_ru/image19.png](https://import.viva64.com/docx/blog/0830_Clang-build_ru/image19.png)

Сборка под MSVC всегда делалась со статической линковкой рантайма, а для экспериментов с Clang рантайм переключили на динамический\. Оказалось, что в образах с Windows по умолчанию не установлены Microsoft Visual C\+\+ Redistributable\. Решили вернуть статическую линковку, чтобы у пользователей не возникало таких же неприятностей\.

## Заключение

Несмотря на то, что пришлось немного повозиться с подготовкой проекта, мы остались довольны ростом производительности анализатора более чем на 10%\.

Последующие релизы PVS\-Studio на Windows будут собираться с помощью компилятора Clang\.