В августе мы, команда C3D Labs (АСКОН), впервые выпустили версию геометрического ядра C3D для отечественной операционной системы @AstraLinux, пополнив список поддерживаемых дистрибутивов Линукс. На данный момент ядро геометрического моделирования C3D может быть использовано в разработке ПО на широком спектре операционных систем: кроме Windows — это MacOS, IOS, FreeBSD и несколько Linux-дистрибутивов. Также SDK ядра предоставляет большое разнообразие компиляторов: MSVC 2012 — 2019, GCC 4.8 — 7.2, Clang 6.0 — 10.0.

Так было не всегда. В 2012 году, когда ядро C3D выделилось из состава САПР КОМПАС-3D как отдельный продукт, оно работало только для нескольких версий компилятора MSVC и, разумеется, только под ОС Windows. Но ядро развивалось, со временем к нему стали предъявляться требования и пожелания, которые мы не могли игнорировать, если хотели иметь действительно лучший продукт в своем классе. Ниже рассказ о том, как мы портировали ядро на различные ОС и платформы.

Рабочее окружение

Первоначально сборку и отладку библиотеки на другой операционной системе мы налаживали по-честному, не используя средств кроссплатформенной компиляции, эмуляции или других хитростей: брали целевую платформу, настраивали рабочее окружение, устанавливали средства сборки, зависимости. В первую очередь это было важно для проверки корректности работы именно в той среде, для которой создана сборка.

Первые препятствия возникают уже на этапе подготовки: например, нужной версии компилятора нет в поставке операционной системы, версия CMake может быть ниже требуемой, некорректно определяются пути к зависимостям и т.д. К счастью, здесь нет каких-то подводных камней, но приходится повозиться.

Далее — генерация проекта с помощью CMake. Здесь начинается следующий блок проблем, связанный с тем, что в некоторых окружениях требуются специальные настройки CMake. В частности, такая ситуация возникла на MacOS — пришлось специально создавать целый блок настроек RPATH. Как оказалось, поведение линковщика в MacOS отличается от стандартного последовательностью путей, в которой он ищет зависимые библиотеки. Также через CMake настраиваются различные флаги компиляции, но необходимость флагов обычно определяется следующими этапами.

Следующий шаг — собственно сборка библиотеки. Проблемы, которые возникали у нас на этом этапе, можно разделить на два блока: связанные с операционной системой и связанные с компилятором.

АСКОН представил план перевода PLM-решения на Linux

Особенности операционных систем

От операционной системы в основном зависят пути к стандартным библиотекам, реализация стандартных библиотек libc/stdlibc++. Например, в коде ядра при низкоуровневой отладке выделения памяти используются функции, которые в одной операционной системе определяются в файле malloc/malloc.h, в другой — в stdlib.h, в третьей — в malloc.h. Или же могут вызываться разные функции в зависимости от ОС. Подобные проблемы были решены использованием макросов препроцессора, например:

#if defined(C3D_MacOS)
#include <malloc/malloc.h>
#elif defined(C3D_FreeBSD)
#include <stdlib.h>
#else
#include <malloc.h>
#endif

Также геометрическое ядро отвечает за чтение и сохранение данных, поэтому неизбежно приходится учитывать особенности систем, чтобы корректно открыть файл по заданной строке. В одних системах используют WCHAR-представление путей, в других — TCHAR. Да и вызовы операций чтения/записи находятся в разных заголовочных файлах. Более серьезная проблема заключается в том, что данные, в том числе строковые, должны читаться и записываться в файл одинаково, вне зависимости от операционной системы и ее разрядности. Однако, как известно, размер типа wchar_t зависит от платформы, а сама стандартная строка зависит от опций компиляции.

Для удобства разработчиков было принято соглашение о введении собственного типа для работы со строками и предоставлены методы преобразования c3d-строк в std::string и обратно, в пути и т.д., скрывающие в себе директивы препроцессора.

Аналогичным образом была решена проблема для встроенных типов данных, допускающих разный размер в зависимости от платформы.

Особенности компиляторов

Зависимость от компилятора проявляется в основном в различной поддержке стандартов разными компиляторами и различной обработке выражений, не оговоренных стандартом. Так, на первых этапах работы по переносу кода на Linux, мы столкнулись с проблемой, что код, написанный не совсем по стандарту, великолепно работал с компилятором от Microsoft и совершенно не хотел компилироваться GCC. Пришлось исправлять, делать выводы и более внимательно относиться к стандартам языка.

Поскольку мы предлагаем пользователю широкий выбор средств компиляции, то приходится поддерживать совместимость со старыми стандартами языка C++. С другой стороны, писать весь код на старом стандарте — тоже неправильно. Так что пришлось искать некоторый компромисс. В результате нашими разработчиками была проделана большая работа по выявлению кода различных стандартов: c++17, c++14, c++11 и более ранних, а затем внедрен механизм, позволяющий писать универсальный код. Приведем пример. Спецификатор constexpr поддерживается, начиная со стандарта c++11. Соответственно, для если для современного кода допустима запись

constexpr size_t VAR = 100;

то старый стандарт ничего не знает о таком ключевом слове, и нужно писать

const size_t VAR = 100;

и никак иначе. Наше решение — определить макросы стандартов и макрос препроцессора, зависящий от стандарта:

#ifdef C3D_STANDARD_CXX_11
 
  #define c3d_constexpr  constexpr

#else

  #define c3d_constexpr  const

#endif

Теперь при корректном определении параметра C3D_STANDARD_CXX_11 код c3d_constexpr size_t VAR = 100; будет работать при любом используемом стандарте, и разработчику не нужно об этом задумываться или загромождать код препроцессорными директивами — это делается единожды. Кстати, подходящий макрос стандарта определяется автоматически из опроса компилятора.

Отдельное место занимает многопоточность. Библиотека C3D использует OpenMP для реализации многопоточности. Однако, не во всех операционных системах OpenMP включена в базовую поставку, поэтому приходится писать код, универсально подходящий как для сборки с включенной опцией openmp, так и без нее. Сейчас мы занимаемся портированием ядра на платформу e2k. Как оказалось, используемый компилятор LCC понимает не все директивы openMP, и с этим тоже пришлось бороться.

С портированием на Эльбрус пришлось повозиться
С портированием на Эльбрус пришлось повозиться

Вообще проблемы с компиляцией в основном возникают при портировании на совершенно новую для нас систему или платформу. На данный момент, например, сборка для Linux у нас налажена так, что добавить еще один дистрибутив или компилятор к списку поддерживаемых не составляет никаких проблем. Поддержку Astra Linux мы реализовали буквально за день. Зато с портированием на Эльбрус приходится изрядно возиться.

Проверка результата

Наконец, когда сборка завершена, файл библиотеки получен, наступает время тестирования результирующего продукта. Бывает, что тесты выявляют какие-то неочевидные ошибки в исходном коде, которые приводят к некорректной работе приложения. К сожалению, общих методов, как устранять такие ошибки мы не знаем — каждая из них решается в индивидуальном порядке. Бывает и другая ситуация — результат слегка отличается от ожидаемого. Дело в том, что геометрическое ядро, построеннное на собственных сложных алгоритмах и вычислительных методах, а также использующее возможности стандартной библиотеки STL и встроенные математические функции, является довольно хрупким продуктом. Практически невозможно обеспечить абсолютно идентичную работу библиотеки, собранной разными компиляторами. Поэтому мы считаем результат удовлетворительным, если он совпадает с некоторым эталоном с заданной точностью.

Доставка ядра до конечного пользователя

Все изложенные выше шаги выполняются вручную лишь один раз, после чего налаживается схема автоматической сборки и тестирования ядра C3D. Автоматизация сборки для Linux-систем основана на технологии Docker. Таким образом, когда нужно добавить поддержку очередного компилятора с определенным системным окружением, мы создаем контейнер с базовой системой, устанавливаем в него все зависимости, настраиваем и запускаем в эксплуатацию. Сборки для Windows, MacOS и FreeBSD выполняются на целевой операционной системе. В планах реализация кроссплатформенной сборки, которая должна ускорить процесс, т.к. сможет задействовать более мощные машины, однако на этом пути наверняка встретятся подводные камни.

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

В качестве инструмента непрерывной интеграции мы используем BuildBot, который по сигналу от системы управления версиями запускает все необходимые сборки, а по их завершении — и различные тесты: от юнит-тестирования до длительных регрессионных тестов. Пока такая схема себя оправдывает и уже позволила значительно сократить сроки предоставления дистрибутива конечному пользователю.

В заключение отмечу, что мы не останавливаемся на достигнутом результате: постоянно расширяется список поддерживаемых систем, улучшается качество кода, ускоряется процесс доставки ядра пользователю.

Автор — Анна Ладилова, к.ф.-м.н., инженер C3D Labs

Комментарии (3)


  1. andy_p
    28.09.2021 12:40

    Дело в том, что геометрическое ядро, построеннное на собственных сложных алгоритмах и вычислительных методах, а также использующее возможности стандартной библиотеки STL и встроенные математические функции, является довольно хрупким продуктом. Практически невозможно обеспечить абсолютно идентичную работу библиотеки, собранной разными компиляторами. Поэтому мы считаем результат удовлетворительным, если он совпадает с некоторым эталоном с заданной точностью.

    Чтобы абсолютная точность была, существует такая наука - exact geometric computation.


    1. vitaliy31
      29.09.2021 05:35

      Что хорошего это может здесь дать?


    1. kompas_3d
      29.09.2021 14:16
      +1

      Можете целую книгу почитать на эту тему) Её наш главный математик-программист и создатель нашего ядра C3D написал.