24.01.2020 • C3D Toolkit

Поддержка многопоточности в геометрическом ядре C3D

Параллельные вычисления — наше будущее.
И так будет всегда!

Эта старая программистская шутка напоминает о важности использования многопоточности в приложениях и о перспективах развития параллельных вычислений, а также намекает на сложности параллельного программирования.

Работы по организации многопоточной обработки данных в геометрическом ядре C3D начались несколько лет назад и активно продолжаются сейчас.

Поддержка многопоточности в ядре C3D включает в себя:

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

Для организации внутренней многопоточности ядро C3D использует технологию OpenMP.

OpenMP — это открытый стандарт для распараллеливания программ на языках С, С++ и Fortran, который реализуется в той или иной степени большинством популярных компиляторов. Применение технологии OpenMP для оптимизации кода ядра C3D позволяет решить проблемы его кроссплатформенности и совместимости.

Компиляторы обеспечивают поддержку OpenMP в разной степени. Например, на данный момент компилятор Intel С++ реализует OpenMP версии 4.5 и некоторое подмножество OpenMP версии 5.0, тогда как компилятор Microsoft C++ обеспечивает поддержку только OpenMP версии 2.0.

Параллельные вычисления в ядре C3D

Основные многопоточные операции ядра включают в себя:

  • построение плоских проекций
  • расчет полигональных сеток
  • расчет массо-центровочных характеристик
  • операции конвертеров

Но не ограничиваются данным списком.


Время работы функции MassInertiaProperties() в разных режимах ядра

Реализация параллельной обработки независимых данных, как правило, относительно проста и достаточно эффективна.

Однако гораздо чаще встречаются ситуации, когда объекты, обрабатываемые в разных потоках, зависят друг от друга (например, используют одни те же данные) или же один и тот же объект участвует в вычислениях в разных потоках. Тогда на первый план выходит задача обеспечения потокобезопасного доступа к обрабатываемым данным.

Самым простым способом является использование блокировок, когда поток получает монопольный доступ к совместно используемым данным путем их блокирования.

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

В ядре C3D эффективность параллельных вычислений и потокобезопасность объектов обеспечивается специальным механизмом — многопоточными кэшами.

Потокобезопасный доступ к объектам ядра

Одним из методов оптимизации вычислений является кэширование данных. Оно основано на предположении, что значения параметров, для которых проводятся вычисления, не являются произвольными, а подчиняются некоторой (предопределенной или статистически предсказуемой) закономерности. Это позволяет повторно использовать уже вычисленные ранее результаты.

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

Для решения проблемы потокобезопасного доступа к данным в ядре C3D реализовано и используется многопоточное кэширование.

Как работают многопоточные кэши

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

Каждый поток работает со своей копией кэшированных данных. Это предотвращает конкуренцию за данные между потоками и минимизирует использование блокировок.

Управляет многопоточными кэшами в конкретном объекте его менеджер кэшей, который отвечает за создание, хранение и выдачу кэшированных данных объекта для текущего потока.

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

Работа механизма многопоточных кэшей управляется переключением режима многопоточности ядра.

Режимы многопоточности ядра

Режим многопоточности ядра управляет механизмом потокобезопасности объектов ядра, а также определяет, какие операции в ядре будут распараллеливаться.

C3D может работать в следующих режимах:

  • Режим Off — многопоточность ядра отключена. Все операции ядра выполняются последовательно. Механизм, обеспечивающий потокобезопасность объектов ядра, отключен.
  • Режим Standard — стандартный режим многопоточности. Работает ограниченное распараллеливание операций ядра: распараллеливаются только операции, обрабатывающие независимые данные. Механизм потокобезопасности объектов ядра отключен.
  • Режим SafeItems — режим потокобезопасности объектов ядра, при котором включается механизм многопоточного кэширования, но по-прежнему работает ограниченное распараллеливание операций ядра. Разработан для поддержки многопоточных операций в пользовательских приложениях.
  • Режим Items — максимальный режим многопоточности ядра, когда включен механизм многопоточного кэширования и идет распараллеливание всех операций ядра, где вычисления могут выполняться параллельно.

C3D предоставляет пользователям возможность динамически изменять режим многопоточности ядра.

Описание режимов многопоточности ядра C3D и менеджера многопоточных кэшей смотрите в онлайн-документации: https://c3d.ascon.ru/doc/math/tool__multithreading_8h.html.

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

Реализация ядра ориентирована на поддержку многопоточного использования интерфейсов ядра в пользовательских приложениях.

Как обеспечивается потокобезопасность ядра

Все геометрические объекты ядра являются потокобезопасными при условии использования механизма многопоточного кэширования (режим многопоточности не ниже SafeItems).

Большинство операций ядра (включая те, которые не являются многопоточными) являются потокобезопасными, то есть могут использоваться в нескольких потоках, если используется режим многопоточности не ниже SafeItems.

Блокировки ядра реализованы на базе нативных механизмов синхронизации (таких, как WinAPI на Windows и pthread API на Linux), что обеспечивает безопасность использования интерфейсов ядра в пользовательских приложениях, использующих различные механизмы распараллеливания.

Ядро C3D предоставляет интерфейсы для уведомления ядра о начале и конце параллельных вычислений, в которых используются интерфейсы C3D.

Важно! Пользовательское приложение, вызывающее интерфейсы C3D из нескольких потоков, должно уведомлять ядро о входе в параллельные вычисления и выходе из них.

Описание интерфейсов синхронизации ядра C3D смотрите в онлайн-документации: https://c3d.ascon.ru/doc/math/tool__mutex_8h.html.

Управление режимами многопоточности ядра

C3D предоставляет пользователям возможность динамически менять режим многопоточности ядра. Изменение режима многопоточности позволяет:

  • управлять потокобезопасностью объектов ядра (включать/выключать многопоточное кэширование)
  • определять, какие операции ядра будут распараллеливаться — все или только те, которые обрабатывают независимые данные.

Когда это может пригодиться? В некоторых случаях наличие внутренних параллельных циклов ядра может оказывать влияние на эффективность внешнего распараллеливания в пользовательском приложении.

Важно! Необходимость переключения режима многопоточности ядра надо анализировать в каждом конкретном случае!

Описание интерфейсов для просмотра и управления режимом многопоточности ядра C3D смотрите в онлайн-документации: https://c3d.ascon.ru/doc/math/class_math.html.

Использование интерфейсов C3D в многопоточном приложении

Перед началом использования интерфейсов C3D в нескольких потоках, необходимо убедиться, что в ядре установлен режим многопоточности не ниже SafeItems. По умолчанию ядро работает в максимальном режиме многопоточности.

При использовании параллельных механизмов, отличных от OpenMP, пользовательское приложение обязано уведомлять ядро о входе в параллельный регион и выходе из него.

Для этого может быть использован один из следующих способов:

  • класс ParallelRegionGuard (защитник параллельного региона в области видимости)
  • функции EnterParallelRegion и ExitParallelRegion
  • макросы ENTER_PARALLEL и EXIT_PARALLEL.

Примеры уведомления ядра о входе в параллельный регион и выходе из него

Заключение

Подведем итоги. Ядро C3D активно использует внутренние многопоточные вычисления. Особенностью ядра является возможность выбора режимов многопоточности при осуществлении математических расчетов. Ядро предоставляет пользователям возможность динамически менять режим внутренней многопоточности.

Ядро C3D поддерживает пользовательскую многопоточность, обеспечивая потокобезопасность операций ядра и предоставляя потокобезопасный доступ к данным объектов ядра. Ядро может быть сконфигурировано под особенности организации многопоточности в пользовательском приложении.

Татьяна Митина, Руководитель отдела программирования C3D Labs
Автор:
Татьяна Митина
Руководитель отдела программирования C3D Labs