<?xml version='1.0' standalone='no' ?>
<journal xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="JournalArticulus.xsd">
    <operCard>
        <operator>ИПМ им. М.В.Келдыша РАН|Publicator</operator>
        <date>2026-06-03 19:26:15</date>
        <cntArticle>1</cntArticle>
        <cs>0</cs>
    </operCard>
    <titleid>32018</titleid>
    <issn>2071-2901</issn>
    <codeNEB>20712901</codeNEB>
    <journalInfo lang="RUS">
        <title>Препринты ИПМ им. М.В.Келдыша</title>
    </journalInfo>
    <issue>
        <volume/>
        <number>23</number>
        <altNumber/>
        <part/>
        <dateUni>2026</dateUni>
        <issTitle/>
        <pages>1-16</pages>
        <articles>
            <article>
                <pages>1-16</pages>
                <artType>RAR</artType>
                <authors>
                    <author num="001">
                        <authorCodes>
                            <spin>9722-7653</spin>
                            <orcid>0000-0001-6645-9998</orcid>
                        </authorCodes>
                        <individInfo lang="RUS">
                            <surname>Чухарев</surname>
                            <initials>Федор Сергеевич</initials>
                            <orgName>Институт прикладной математики им. М.В.Келдыша РАН</orgName>
                            <email>fedor.s.chukharev@mail.ru</email>
                        </individInfo>
                        <individInfo lang="ENG">
                            <surname>Chukharev</surname>
                            <initials>F.S.</initials>
                            <orgName>Keldysh Institute of Applied Mathematics RAS</orgName>
                        </individInfo>
                    </author>
                </authors>
                <artTitles>
                    <artTitle lang="RUS">Об особенностях программной реализации метода частиц для задач переноса фотонов и электронов в стандартах OpenMP и MPI</artTitle>
                    <artTitle lang="ENG">On the characteristics of the software implementation of the particle method for photon and electron transport problems in the OpenMP and MPI standards</artTitle>
                </artTitles>
                <abstracts>
                    <abstract lang="RUS">Представлена реализация алгоритма метода частиц на прямоугольной разностной сетке. Алгоритм вычисляет энерговыделение и плотность тока электронов отдачи, образующихся при рассеянии фотонов в сложной среде. Рассмотрены свойства алгоритма и конкретной вычислительной системы, определяющие подход к параллельной реализации вычислений. Приведены количественные метрики производительности разработанной программы, рассмотрены направления доработки программы и их перспективность.</abstract>
                    <abstract lang="ENG">The implementation for particle method algorithm on a rectangular difference grid is presented. The algorithm calculates the energy release and current density of recoil electrons generated by photon scattering in a complex environment. The properties of the algorithm and a specific computing system that determine the approach to parallel computing are considered. Quantitative performance metrics of the developed program are given; the directions of program refinement and their prospects are considered.</abstract>
                </abstracts>
                <text lang="RUS">   

Введение
    Возможный подход к моделированию плотности мощности энерговыделения и плотности тока электронов, возникающих в среде при переносе квантов и электронов, представлен в работе [1]. Рассматривается рассеяние квантов в среде с кусочно-постоянными свойствами. Она задается совокупностью прямоугольных ячеек. Для этого в области расчета строится прямоугольная разностная сетка, каждой ячейке которой ставится в соответствие материал с известными рассеивающими свойствами – элементным составом и плотностью. Значения выделенной энергии и плотности тока относятся к ячейкам сетки. 
    В данной работе представлены особенности использования стандарта Open Multi-Processing (OpenMP) и интерфейса Message Passing Interface (MPI) для поставленной задачи. Описаны приёмы программирования, позволяющие значительно уменьшить количество вычислений и ускорить расчеты. Приведена информация о производительности программы. Даны оценки дальнейшего развития реализации программы.
Особенности структуры программы
    Программа реализована на языке C++, с использованием стандарта C++201 [2]. Она является кроссплатформенной, с поддержкой компиляторов начиная с GCC 11.1.0, ICC (ICX) 2022.0.1 и MSVC 19.29 (соответствует Visual Studio 16.11). Сборка программы производится с помощью CMake 3.11 и выше.
    Несмотря на то что программа реализована с использованием OpenMP и MPI, на этапе сборки можно отключить по отдельности и OpenMP, и MPI, получив таким образом либо параллельную версию с применением только одной технологии, либо последовательную программу. Это может быть необходимо в тех случаях, если у пользователя нет необходимых библиотек. Например, MSVС до сих пор поддерживает только OpenMP 2.0, в то время как для работы программы необходим хотя бы OpenMP 4.0 – для работы директив, связанных с параллелизмом уровня задач (task-based parallelism).
    Таким образом, при приоритизации кластерных и высокопроизводительных систем поддерживается и максимальная кроссплатформенность для всех современных систем, в том числе для персональных компьютеров.
    Программа состоит из нескольких модулей.
1. Модуль чтения входных данных;
2. Основной расчётный модуль, реализующий метод частиц;
3. Библиотека расчета взаимодействия частиц с материалом (далее в тексте – библиотека);
4. Модуль записи результатов.
    Входными данными являются файлы, содержащие описание декартовой разностной сетки, сопоставление материалов ячейкам сетки, описание свойств материалов в виде таблиц, описание падающего на область излучения, заданного в виде дискретного спектра частиц и направления.
    Специфичными входными данными для этой программы являются файлы: описание априорно определенных параметров алгоритма, таких как максимально моделируемые поколения для частиц, размеры шага по траектории частицы, количество рассеянных для каждого эффекта частиц и т.д., описание запрошенных пользователем рассчитываемых характеристик.
    После чтения всех входных файлов происходит переход в расчётный модуль. На каждой границе области, откуда воздействует излучение, в центре граней ячеек генерируются стартовые частицы нулевого поколения, соответствующие входному спектру, с долями, пропорциональными площадям граней. Например, в случае области с сеткой размерности  воздействующим на нее в положительном направлении оси Z излучением со спектром из частиц сгенерировалось бы  частиц. Несмотря на то что спектры обычно имеют небольшую размерность, до десятка различных энергий, размеры сеток достигают сотен ячеек по каждому из направлений, что в итоге дает десятки тысяч частиц нулевого поколения. После этого начинается решение уравнения переноса для каждой из частиц и всех производных от неё.
    В процессе решения уравнения переноса используется библиотека расчета взаимодействия частиц с материалом, с помощью которой вычисляются характеристики рассеянных частиц.
    По окончании расчётов создаются выходные файлы – трёхмерные распределения стороннего тока, энерговыделения, а также спектрально-угловые характеристики потоков частиц.
    Устройство модулей ввода-вывода определяется требованиями по интеграции с другими программами, а также не является узким местом производительности программы. Как ни странно, реализация вспомогательной библиотеки тоже почти не опирается на свойства алгоритма. Безусловно, к ней применимы все приёмы программирования высокопроизводительных C++ программ – уменьшение частоты выделения и освобождения памяти, укорачивание цепочки вызовов функции, использование move-семантики и прочие другие. Известны оптимизации для ускорения логарифмической интерполяции [3], не планируемые к использованию в этой программе. Однако оптимизаций библиотеки, специфичных для основного алгоритма, нет.
Особенности реализации расчётного модуля
    В этом модуле реализована основная логика программы, управляющая созданием частиц, решением уравнений переноса и координацией параллелизма, с применением OpenMP и MPI.
    Алгоритм, представленный в [1], реализует решение линейных уравнений переноса для квантов и электронов отдачи в стационарном приближении. Уравнение для электронов рассматривается в приближении средней передачи энергии при неупругих взаимодействиях и релаксации к равновесному распределению при упругом рассеянии. Для решения уравнений переноса используются метод поколений и аппроксимация распределений линейными непрерывными функционалами – переход к методу частиц.
    В отличие от метода Монте-Карло, в котором параметры частиц после актов рассеяния вычисляются как реализации случайных величин, распределенных в соответствии с дифференциальными сечениями, процедура разыгрывания случайных величин исключается за счет специальной аппроксимации пространственных и спектральных распределений, а также дифференциальных сечений рассеяния.
    Сформулируем явным образом несколько утверждений об алгоритме, которые определяют программные решения, описанные в этом разделе:
1. Уравнение переноса можно решать для каждой частицы независимо от других частиц. Общее решение есть сумма вкладов каждой частицы;
2. Входными данными является дискретное описание спектра частиц, с фиксированным набором энергий;
3. Каждая частица во время движения создает большое число вторичных, рассеянных частиц;
4. Количество моделируемых поколений ограничено – это продиктовано их уменьшающимся вкладом в общее решение;
5. Аппроксимация распределений рассеянных частиц задаётся априорным образом, зависит только от энергии частицы и материала и не меняется во время работы программы;
6. Во время движения у кванта не изменяется энергия, только доля (вес) частицы. У электрона во время движения изменяется и энергия, и доля.
    Эти утверждения позволяют реализовать производительную программу, максимально утилизирующую возможности кластерных CPU систем. Далее опишем ключевые особенности программы, их преимущества и недостатки.
Применение MPI
    MPI используется только для масштабирования по узлам кластерных систем путем равномерного распределения начальных частиц между ними. Для этого не требуются операции пересылок – каждый процесс, прочитав входные данные, может определить, какие частицы нужно обработать именно ему, отбросив остальные. В конце расчетов производится глобальная операция редукции, суммирующая накопленные результаты (это возможно в силу линейности задачи по частицам), и финализация контекста MPI. Больше никаких операций MPI не производится – каждый процесс хранит полную копию сетки, вспомогательных структур и так далее.
    Преимущества этого подхода заключаются в следующем:
1. Минимизированы накладные расходы на взаимодействие между процессами – нет пересылок, кроме заключительного сбора результата, нет потенциальных ожиданий процессами друг друга;
2. Простая масштабируемость, с учётом того, что количество начальных частиц обычно достигает десятка тысяч, позволяет запускать программу на кластере с почти любым количеством узлом.
    Недостатки этого подхода:
1. Распределение частиц может быть несбалансированным в плане фактической вычислительной нагрузки. Во время расчёта отсутствует способ распределить не обсчитанные частицы на освободившиеся узлы. В предельном случае, если количество начальных частиц мало, например, моделируется точечный источник с одной начальной частицей, распределение с помощью MPI может быть неприменимо.
    Исходя из преимуществ и недостатков рекомендуется использовать MPI параллелизм только для запуска программы на нескольких узлах по принципу «1 узел – 1 MPI процесс». Для параллелизма внутри узла стоит использовать OpenMP нити.
Структура данных хранения частиц
    Прежде чем переходить к описанию использования OpenMP в программе, опишем структуру хранения частиц для обработки и алгоритм их обработки для однопоточной последовательной программы. Будем стремиться минимизировать количество частиц, стоящих в очередь на обработку, чтобы сэкономить оперативную память. Помимо сохранения памяти под более нужные вещи вроде более подробных спектрально-угловых распределений и других оптимизаций, это позволит улучшить локальность данных.
    Для этого воспользуемся тем фактом, что нам известно максимально возможное поколение частицы .
    Реализуем аналог pull-очереди («вытягивающей» очереди) для нескольких поколений. Для этого заведем для каждого поколения i массив частиц  для обработки. Если  не пуст, обработаем все частицы оттуда, пока они не кончатся – новых частиц они не произведут. Будем заполнять пустые массивы для частиц начиная с  по следующему рекурсивному алгоритму fillGenerations для поколения :
1. Если текущее поколение , то есть это поколение начальных частиц, закончи алгоритм;
2. Пока текущий массив  пуст (имеет меньше порогового числа частиц), для его заполнения обработай частицу из предыдущего поколения , после обработки убрав её со стека обработки через операцию pop.
3. Если при этом предыдущее поколение  тоже пусто, примени к нему эту же процедуру fillGenerations.
    Более наглядным будет следующий листинг, адаптированный из исходного кода, реализующий этот алгоритм.
void fillGenerations(
    ParticleQueue&lt;Photon&gt;&amp; tgt_queue, int gen_num
) {
    if (gen_num &lt;= 0) { return; }
    if (gen_num &gt;= tgt_queue.size()) { return; }
    int prev_gen = gen_num - 1;
    auto&amp; prev_photons = tgt_queue.get_gen(prev_gen);
    while (tgt_queue.get_gen(gen_num).size() &lt; BATCH_MIN_SIZE) {
        if (prev_photons.empty()) {
            fillGenerations(tgt_queues, prev_gen);
        }
        if (!prev_photons.empty()) {
            processPhoton(
                tgt_queues,
                prev_photons[prev_photons.size() - 1],
                prev_gen
            );
            prev_photons.pop_back();
        } else { break; }
    }
}    В нём очередь передается как аргумент функции, чтобы дальше быть переданной в функцию processPhoton обработки отдельно взятого фотона. Внутри этой функции обработки уже происходит заполнение поколения gen_num ( в алгоритме) через операцию push. В данном случае подразумевается, что электроны обрабатываются с помощью processElectron мгновенно при появлении и не имеют своей очереди.
    Если предположить, что за обработку отдельно взятой частицы появляется в среднем  рассеянных частиц нового поколения, а начальных частиц всего , то в таком случае в каждый момент времени у нас хранится в памяти программы не больше ~частиц, при , иначе не больше . Это позволяет хранить минимально возможное число частиц в очереди на обработку. Конкретной структурой для хранения частиц может быть std::vector, с заранее выделенной памятью, так как он поддерживает операции pop и push, необходимые для реализации структуры выше.
    Дальше возможно сделать две последовательных модификации получившейся структуры. Несмотря на то что описание приведено для одного типа частиц, можно создать структуру вида:
struct AllParticlesQueues {
    ParticleQueue&lt;Photon&gt; photons;
    ParticleQueue&lt;Electron&gt; electrons;
}    И заполнять в processElectron, processPhoton эти очереди. Это станет необходимо для реализации моделирования процесса тормозного излучения, в результате которого при движении электрона образуются фотоны. Процедура fillGenerations в таком случае тоже немного изменяется – теперь надо из обеих очередей пытаться обработать поколение , прежде чем подниматься ещё на уровень выше. При этом, так как мы можем обрабатывать только очередь одного вида частиц за раз, возникает риск переполнения очереди других частиц.
    Чтобы решить эту проблему, сделаем вторую модификацию алгоритма. Определим для каждой очереди максимальный размер – количество частиц в каждом поколении. Допустим, мы обрабатываем очередь частиц типа  . Если при попытке добавить частицу типа  поколения  в очередь частиц  мы превысим этот лимит , то приостановим обработку текущей частицы и переключимся на обработку очереди частиц , пока не обработаем все элементы в этом поколении, чтобы высвободить место. В процессе обработки мы можем столкнуться с тем, что теперь уже в очереди частиц предыдущего типа  мы достигли лимита и теперь надо аналогично переключиться на обработку этой очереди, приостановив второй уровень обработки частиц. Однако это не проблема – каждый раз мы пытаемся добавить частицу следующего поколения, то есть рано или поздно мы дойдем до максимально возможного поколения, которое не может производить никаких частиц, и обработаем его. После этого сможем обработать накопленный стек вызова функций.
    Это гарантирует нам обработку всех частиц, при этом оставив возможность контролировать количество частиц, хранимых к будущей обработке. 
Распределение частиц между потоками исполнения
    У структуры хранения частиц есть предельный случай при . Если мы будем хранить только начальные частицы и мгновенно обрабатывать таким образом все рассеянные частицы, то стек вызовов будет хранить все промежуточные шаги, и мы лучшим образом оптимизируем расход оперативной памяти. К сожалению, даже для последовательной программы это скорее всего не лучшее решение – будут часто чередоваться вызовы функций обработок для разных частиц, что негативно скажется на кэше инструкций. Для параллельной программы с общей памятью, то есть использующей технологию OpenMP в текущем случае, очереди с частицами на обработку являются основным источником параллелизма.
    Опишем механизм распределения частиц между потоками исполнения, а также генерации новых партий частиц к обработке. Предположим, что у нас уже есть массив частиц нулевого поколения к обработке  (в листинге - gen0). Зафиксируем этот массив, больше не изменяя его – это необходимо для потокобезопасной обработки. Для каждого потока заведем свой AllParticlesQueues, изначально пустой. С помощью механизма задач OpenMP сгенерируем множество задач следующего типа – «взять i-й элемент массива gen0, скопировать в локальный для потока AllParticlesQueues и обработать все частицы». С точки зрения кода это выглядит следующим образом:
void Calculator::calculate() {
…
#pragma omp taskloop default(none) shared(gen0)
    for (size_t i = 0; i &lt; gen0.size(); i++) {
        processThreadStack(gen0[i], 0);
    }
…
}

template&lt;class Ptcl&gt;
void Calculator::processThreadStack(
    Ptcl&amp; start_photon, int gen_num
) {
    OMPThreadData&amp; threadData =\
      omp_thread_data[PreCalcCaches::thread_id];
    // техническая часть с получением правильной очереди
    threadData.particles.clear_all();
    threadData.particles.add_to_queue(gen_num, start_photon);
    exhaustQueues(threadData.particles);
}    Где в processThreadStack отбирается нужная, локальная для потока очередь, а exhaustQueues определяет операцию «обработай все частицы в заданной очереди». Использование механизма задач позволяет динамически распределять нагрузку между потоками, в случае если некоторые частицы и образованные ими вторичные, например близкие к границе области, обработались слишком быстро. Это выгодное отличие параллелизма по OpenMP от параллелизма по MPI. Также стоит отметить, что такая реализация работает без ожиданий, то есть является wait free. Это обеспечивает полную загрузку всех доступных нитей и позволяет максимально эффективно использовать линейность задачи по источнику.
    Для отдельных случаев, если частиц нулевого поколения мало, в связи с постановкой задачи или распределением этого поколения по процессам MPI, есть опция задания поколения , начиная с которого производится параллельная обработка. В таком случае необходимо:
1. Подготовить партию частиц to_process поколения  в глобальной очереди с помощью fillGenerations(), обработав частицы ранних поколений;
2. Обработать её параллельным образом аналогично процессу, описанному для нулевого поколения – не модифицируя, для параллельного доступа разными потоками; 
3. Если ещё остались необработанные частицы ранних поколений, перейти к шагу 1.
    При неоптимальной реализации может оказаться, что пока один поток обрабатывает глобальную очередь и генерирует новую партию к обработке, остальные потоки простаивают. С помощью механизма задач можно выполнять эти этапы параллельно. После генерации массива to_process его можно скопировать, оставив эту неизменяемую копию на параллельную обработку остальными потоками. В это же время создать задачу на заполнение нового массива to_process_next. На новом шаге с помощью операции обмена поменять to_process_next и process местами и продолжить работу алгоритма. Если время на обработку партии частиц будет больше, чем на создание новой партии, то мы опять получим полную загрузку всех доступных потоков. В данном случае реализация потеряла свойство wait free, однако она до сих пор работает без блокировок, то есть является lock free.
    Был разобран механизм эффективного распределения задач-частиц, однако ещё ни разу не упоминалось то, как накапливаются искомые результаты расчёта, а именно энерговыделение , плотность тока электронов отдачи  и спектрально-угловые распределения потоков частиц на разностной сетке. Решений этой проблемы может быть несколько. Один из вариантов – использовать атомарные операции обновления величин. Другой – завести каждому потоку свою копию величины, как уже делали с очередями на обработку AllParticlesQueues, а после в конце работы программы суммировать данные потоков. В программе применяются оба подхода.
    Для скалярных величин , , ,  используется вариант с приватной копией для каждого потока. Такой выбор сделан из следующих соображений:
1. Каждая частица постоянно обновляет эти значения при движении сквозь ячейки. Быстрее обновить значения, чем проверить, требуется ли расчет этих величин в данном материале;
2. Атомарные операции являются дорогими, неочевидным образом влияют на оптимизацию кода;
3. На доступных кластерах много памяти, относительно физических ядер, а копии занимают относительно немного места – даже если брать большие сетки 300x300x300, с 28 потоками, то это займёт .
    Другая ситуация с потоками частиц. Потоки частиц вычисляются на границе ячеек – то есть их гипотетически может быть в 3 раза больше, чем ячеек. Также в них хранятся не скалярные величины, а распределения по энергии и двум углам. Хранить копии для каждого потока уже не представляется возможным. Но, с другой стороны, обновление этих величин происходит только при пересечении граней между ячейками, при условии, что они были выбраны пользователем для отслеживания. Это происходит гораздо реже, чем обновление энерговыделения или тока, из-за чего последствия использования атомарных операций не так значительны.
    Таким образом, мы описали основной алгоритм параллелизации с помощью OpenMP, показав, что он как минимум lock free и позволяет максимально утилизировать доступные потоки, оставаясь потокобезопасным.
Предварительный подсчет характеристик рассеянных частиц
    Обработка частиц производится в функциях processPhoton и processElectron соответственно. Рассмотрим, какие оптимизации, основанные на свойствах вычислительной модели, возможны в них.
    В данной модели, как было упомянуто ранее, аппроксимация распределений рассеянных частиц задаётся априорным образом, зависит только от энергии частицы и материала и не меняется во время работы программы. Во время движения у гамма-кванта не изменяется энергия – это значит, что во время его перемещения внутри ячейки одного материала все характеристики рассеянных частиц, а именно энергия, направление и доля, относительно текущего направления и доли первичного кванта будут одинаковы на каждом шаге. Таким образом, при входе частицы в ячейку мы можем один раз обратиться к библиотеке взаимодействия с материалом, посчитать для всех процессов коэффициенты для рассеянных частиц, и дальше свести каждый шаг внутри ячейки к добавлению новых частиц на обработку с использованием полученных коэффициентов. Так мы очевидным образом уменьшили количество вычислений.
    Но в таком случае мы пересчитываем коэффициенты для каждой пары материала и частицы даже в том случае, если энергии у частиц совпадают. Учтем следующие свойства алгоритма и практических задач:
1. Максимальное поколение кванта ограничено и редко превышает 2-3;
2. Количество различных материалов не превышает нескольких десятков;
3. Количество различных рассеянных частиц для одного шага не превышает нескольких десятков.
    Тогда можно, не исследуя реальную геометрию объекта и возможные траектории частиц (что включало бы в себя в решение задачи), предварительно рассчитать все возможные комбинации рассеяния квантов для всех материалов, во всех поколениях. Это возможно, так как для расчета коэффициентов нам нужны лишь энергия и свойства материала, а входной спектр фотонов конечен и имеет достаточно мало уникальных значений энергий. Результаты расчётов будем хранить в хэш-таблице photon_caches. Ключом будет пара «энергия-слой», значением – вычисленные коэффициенты. Опишем алгоритм заполнения хэш-таблицы photon_caches.
1. Сформируем из набора энергий стартовых частиц массив cur_energies, заведём пустой массив next_energies, положим cur_gen равным нулю;
2. Для каждой энергии En из cur_energies переберем все материалы Mat в задаче;
a. Для каждой пары (En, Mat) рассчитаем коэффициенты для рассеянных частиц и запишем эти результаты в photon_caches;
b. Из вычисленных коэффициентов дополним next_energies энергиями рассеянных фотонов;
3. Если cur_gen равен максимальному поколению для фотонов, то завершаем алгоритм;
4. Иначе увеличиваем cur_gen на 1, обнуляем cur_energies и меняем местами cur_energies и next_energies;
5. Переходим к шагу 2.
    Перебрав возможные комбинации энергий и материалов до начала расчета, мы построили видимый для всех потоков, доступный только для чтения (read-only) кэш, основанный на хэш-таблице. Так удалось исключить обращение к библиотеке при обработке фотонов нулевого поколения или рассеянных, заменив его на работу с кэшем. В данном случае это выгодно, так мы избежали большого числа вызовов функций для вычисления экспоненты и логарифма. На практике это изменение дало ускорение в 2 с небольшим раза, оставив узким местом обработку электронов.
    В части обработки электронов такой подход применим лишь отчасти – при расчёте кэша для фотонов мы можем предварительно рассчитать коэффициенты для первого шага рассеянного электрона внутри материала и дальше при моделировании фактических частиц использовать эти величины. Это имеет смысл в предположении, что большинство электронов закончит своё движение в этом же материале почти сразу же после порождения. Коэффициенты второго шага для электронов невозможно предварительно рассчитать, т.к. точная энергия электрона после первого шага зависит от длины первого шага, в отличие от фотона.
    Сейчас в программе реализовано кэширование одного шага, т.к. это занимает не так много места и позволяет обработать электрон «на месте», не заводя под него новую частицу в очереди. Такая оптимизация дала ускорение до 10% на некоторых задачах с плотными материалами.
    В остальном моделирование движения электронов оптимизировать предварительными расчетами невозможно из-за изменения энергии и явной зависимости от фактической траектории частицы. Это же будет распространяться на кванты, образовавшиеся в результате тормозного излучения – для них применим только вариант с разовым расчётом коэффициентов при входе в ячейку.
Производительность программы
    Продемонстрируем качество полученной параллельной реализации, измерив получившееся ускорение для фиксированной задачи в зависимости от разного количества потоков OpenMP или процессов MPI. Вычисления проведены с помощью гибридного суперкомпьютера K60, установленного в Суперкомпьютерном Центре коллективного пользования ИПМ им. М.В. Келдыша РАН [4]. Один узел К60 представляет собой двухсокетную систему с двумя CPU Intel Xeon E5-2690 v4. Всего на узле 28 физических ядер (2x14) и 256 ГБайт оперативной памяти.
    На рис. 1 приведен график ускорения от количества MPI процессов. Для каждого процесса заводилось фиксированное число OpenMP потоков, равное 14, чтобы уместиться на один процессор. Ускорение почти что совпадает с линейным. На рис. 2 приведен график ускорения от количества OpenMP потоков. В этом варианте запускался всего один MPI процесс, размещавшийся на одном узле. В данном случае ускорение близко к линейному, однако с ростом количества потоков становится меньше. Скорее всего, это связано с эффектом неоднородного доступа к памяти (Non-Uniform Memory Access, NUMA) в случаях, когда потокам одного из двух процессоров приходилось обращаться к оперативной памяти, подключенной к плате через шину другого сокета. Это подтверждается результатами запуска утилиты Application Performance Snapshot (APS), входящей в пакет Intel® VTune™ Profiler [5], также установленной на К60. APS используется для анализа аспектов производительности параллельных программ во время их работы.
    В таблице 1 приведены результаты запуска утилиты APS в том формате, в котором их выдала программа. В них можно отметить утилизацию физических ядер в 99%, полученную благодаря сбалансированному распределению работы по нитям OpenMP, и высокую производительность в 44.97 GFLOPS двойной точности. Из недостатков – большое количество NUMA в 29.2%, из-за чего не удалось достичь линейного ускорения.
Рис. 1. График ускорения в зависимости от числа MPI процессовРис. 2. График ускорения в зависимости от числа OpenMP нитей
Таблица 1
Результаты запуска APS
Название параметраЗначениеElapsed Time, s1616.68SP GFLOPS0.11DP GFLOPS44.97Average CPU Frequency, GHz3.19Instructions Per Cycle Rate2.23OpenMP Imbalance, % of Elapsed Time0.92Physical Core Utilization, %99Average Physical Core Utilization (out of 28)27.71Memory Stalls, % of Pipeline Slots12.8Cache Stalls, % of Cycles9.7DRAM Stalls, % of Cycles2.9NUMA, % of Remote Accesses29.2Vectorization, %12.7SP FLOPs, % of uOps0.1DP FLOPs, % of uOps19.8DP Packed, % from DP FP12.7DP Scalar, % from DP FP87.3Non-FP, % of uOps80.1FP Arith/Mem Rd Instr. Ratio0.5FP Arith/Mem Wr Instr. Ratio1.21Resident Memory, MB3401    
Развитие программной реализации
    Несмотря на достаточно хорошие показатели производительности, существует ряд направлений, по которым можно произвести улучшение текущей программной реализации.
    По результатам запуска APS, приведенным в таблице 1, виден низкий процент векторизации в 12.7%. Это является возможным источником производительности, поэтому исследуем, насколько его возможно применить в данном случае.
    Если построить информационные зависимости [6] между операциями для алгоритма движения частиц, станет видно, что векторизовать его не представляется возможным – каждый шаг производит множество новых частиц, которые необходимо сохранить для будущей обработки. Одновременная обработка нескольких частиц также труднореализуема, так как траектории частиц, длина их пути и, как следствие, количество шагов различаются. Перегруппировка частиц по ячейкам внутри обрабатываемых поколений приведет к большому числу накладных расходов, скорее всего перевешивающих потенциальное ускорение. И, наконец, движение частицы внутри ячейки плохо векторизуется, так как есть информационная зависимость каждого следующего шага от предыдущего как в части расчетов, так и в части учёта новых рассеянных частиц.
    Другим направлением развития является использование GPU как незадействованных на текущий момент устройств, которые часто встречаются на гибридных суперкомпьютерах. Текущая вычислительная модель плохо сочетается с вычислениями на GPU по тем же причинам, по которым трудозатратна реализация векторизации на CPU, в том числе потому, что у них похожие модели вычислений – Single Instruction Multiple Data (SIMD) у векторизации и Single Instruction Multiple Threads (SIMT) у GPU. Однако в отличие от векторизации, где мы раскрываем полные возможности уже эффективно используемых устройств, любая работа, переложенная на GPU, будет положительным вкладом в общую производительность. Так как это отдельные устройства с отдельным кодом, мы можем, не изменяя значительно основной решатель, отправлять на GPU частицы, на которые не будут распространяться проблемы, связанные с частой работой с памятью, – например, частицы из максимальных поколений. В любом случае исследование использования GPU является перспективным направлением развития программной реализации.
    С прикладной точки зрения, недостатком является то, что в задачах с малым числом начальных частиц неэффективна или порой невозможна параллелизация по нескольким узлам с помощью MPI. Решений может быть несколько – можно ввести синхронизацию между процессами и распределение оставшихся вычислений. Другим решением может быть, по аналогии с текущим вариантом, одновременная обработка всеми процессами начальных частиц, но уже отбор «своей» нагрузки на следующих поколениях – тогда придётся правильно учесть, что начальные частицы были обработаны несколько раз. В любом случае, несмотря на ожидаемые дополнительные вычисления (либо дополнительная синхронизация, либо дублированные вычисления), это однозначно ускорит решение задач с точечным источником за счёт увеличения количества вычислительных устройств.
Заключение
    Алгоритм решения уравнений переноса фотонов и электронов, аналогичный методу частиц, реализован на параллельном суперкомпьютере в технологиях OpenMP и MPI. Представлено, как использование свойств конкретной вычислительной модели позволяет значительно уменьшить количество вычислений и ускорить расчеты.
    Приведены конкретные количественные характеристики производительности программной реализации. Рассмотрены дальнейшие направления разработки программы и их перспективность.
</text>
                <codes>
                    <udk>519.6</udk>
                    <edn>UDIAQE</edn>
                </codes>
                <keywords>
                    <kwdGroup lang="RUS">
                        <keyword>параллельные вычисления</keyword>
                        <keyword>MPI</keyword>
                        <keyword>OpenMP</keyword>
                        <keyword>фотон</keyword>
                        <keyword>электрон</keyword>
                        <keyword>перенос</keyword>
                    </kwdGroup>
                    <kwdGroup lang="ENG">
                        <keyword>parallel computing</keyword>
                        <keyword>MPI</keyword>
                        <keyword>OpenMP</keyword>
                        <keyword>photon</keyword>
                        <keyword>electron</keyword>
                        <keyword>transfer</keyword>
                    </kwdGroup>
                </keywords>
                <references>
                    <reference>Березин А.В., Марков М.Б., Чухарев Ф.С. Моделирование рассеяния фотонов и электронов методом частиц // Препринты ИПМ им.М.В.Келдыша. 2025. № 60. https://library.keldysh.ru/preprint.asp?id=2025-60</reference>
                    <reference>ISO/IEC 14882:2020, C++20 Standard [PDF]. – URL: https://wg21.link/std20 (дата обращения: 30.03.2026)</reference>
                    <reference>Peter C. Hammond, Jacob M. Fields, Jonah M. Miller, and Brandon L. Barker. Not-quite-transcendental Functions for Logarithmic Interpolation of Tabulated Data. The Astrophysical Journal Supplement Series. Volume 277. Number 2. p65. https://doi.org/10.3847/1538-4365/adbbd4</reference>
                    <reference>ЦКП ИПМ РАН [сайт]. – URL: https://ckp.kiam.ru/?hard (дата обращения: 30.03.2026)</reference>
                    <reference>VTune — Википедия [сайт]. – URL: https://ru.wikipedia.org/wiki/VTune (дата обращения: 30.03.2026)</reference>
                    <reference>Воеводин В.В. «Вычислительная математика и структура алгоритмов» Учебник. – 2е издание, стереотипное – М.: Издательство Московского Университета, 2010. Лекция 7, С.99. ISBN 978-5-211-05933-7</reference>
                </references>
                <files>
                    <furl>https://keldysh.ru/papers/2026/prep2026_23.pdf</furl>
                </files>
            </article>
        </articles>
    </issue>
</journal>
