Многопоточность и мультипроцессорность
Для повышения производительности расчетов ROOT предлагает инструмент, работа которого основывается на параллелизме распределенных систем - PROOF. С другой стороны, часто желательны решения, связанные с простой многопотоковой и многопроцессорной обработкой данных. Начиная с версии 6.08 в ROOT появились методы, обеспечивающие эти свойства. Для того, чтобы эти функции были доступны, ROOT должен быть скомпилирован с флагом imt = ON. При работе на кластере HybriLit пользователь должен установить модуль ROOT/v6-13-02-1.
Многопоточность
ROOT может использоваться как библиотека из нескольких потоков при условии использования специальной функции ROOT::EnableImplicitMT(numthreads). Эта функция обеспечивает глобальное включение неявной многопоточности в ROOT, активацию параллельного выполнения методов в ROOT, что обеспечивают внутреннюю параллелизацию.
Параметр' numthreads ' позволяет управлять количеством потоков, используемых неявной многопоточностью. Однако, этот параметр всего лишь просит ROOT установить такое количество потоков, и ROOT постарается удовлетворить запрос, если исполнение сценария позволяет это сделать. А если ROOT настроен на использование внешнего планировщика, установка значения 'numthreads' может не иметь никакого эффекта. ROOT::EnableImplicitMT(numthreads) вызывается перед выполнением операций в нескольких потоках.
Функция ROOT::EnableThreadSafety() oбеспечивает глобальный мьютекс, чтобы сделать ROOT потокобезопасным.
Мьютекс (англ. mutex, от mutual exclusion «взаимное исключение») аналог одноместного семафора, служащий в программировании для синхронизации одновременно выполняющихся потоков. Мьютекс отличается от семафора тем, что только владеющий им поток может его освободить, т.е. перевести в отмеченное состояние. Мьютексы это один из вариантов семафорных механизмов для организации взаимного исключения. Они реализованы во многих ОС, их основное назначение организация взаимного исключения для потоков из одного и того же или из разных процессов. Мьютексы это простейшие двоичные семафоры, которые могут находиться в одном из двух состояний отмеченном или неотмеченном (открыт и закрыт соответственно). Когда какой-либо поток, принадлежащий любому процессу, становится владельцем объекта mutex, последний переводится в неотмеченное состояние. Если задача освобождает мьютекс, его состояние становится отмеченным. Задача мьютекса защита объекта от доступа к нему других потоков, отличных от того, который завладел мьютексом. В каждый конкретный момент только один поток может владеть объектом, защищённым мьютексом. Если другому потоку будет нужен доступ к переменной, защищённой мьютексом, то этот поток блокируется до тех пор, пока мьютекс не будет освобождён. Цель использования мьютексов защита данных от повреждения в результате асинхронных изменений.
Методы ROOT::DisableImplicitMT() и ROOT::IsImplicitMTEnabled() используются для отключения и проверки состояния глобальной неявной многопоточности в ROOT соответственно. Также необходимо, чтобы в каждом потоке читался или записывался только один файл.
Мультипроцессорность
Инструмент, предоставляемый ROOT для выполнения простых операций с использованием многопроцессорности - это класс TProcPool. Новый интерфейс, реализованный в классе TProcPool, обеспечивает возможность параллельно выполнять очень общий набор задач, описываемых макросами или функциями. Основные методы класса TProcPool::Map(F func, unsigned nTimes) и TProcPool::MapReduce(F func, unsigned nTimes, R redfunk) анализируют деревья, используя все имеющиеся в наличии ядра.
По этой ссылке находятся обучающие программы, предназначенные для иллюстрации многоядерных особенностей ROOT, таких как осведомленность и безопасность потоков, многопоточность и многопроцессорность.
Программа, представленная ниже, демонстрирует, как активировать и использовать неявное распараллеливание метода TTree::GetEntry().
int imt001_parBranchProcessing() { //Сначала включаем неявную многопоточность, чтобы использовалась неявная параллелизация. // Параметр вызова определяет количество потоков int nthreads = 4; ROOT::EnableImplicitMT(nthreads); // Открываем файл, содержащий дерево TFile *file = TFile::Open("http://root.cern.ch/files/h1/dstarmb.root"); // Вытаскиваем дерево TTree *tree = nullptr; file->GetObject("h42", tree); // Считаваем ветви параллельно. // Интерфейс ничем не отличается от непараллельного случая, поскольку параллелизация внутренняя for (Long64_t i = 0; i < tree->GetEntries(); ++i) { tree->GetEntry(i); // параллельное чтение } // IMT параллелизация может быть отключена для отдельного дерева tree->SetImplicitMT(false); // Если сейчас вызвать GetEntry, то считывание будет происходить последовательно for (Long64_t i = 0; i < tree->GetEntries(); ++i) { tree->GetEntry(i); // последовательное считывание } // Параллельное считывание может быть восстановлено tree->SetImplicitMT(true); // IMT можно также отключить глобально. ROOT::DisableImplicitMT(); // В этом случае даже если конкретному дереву разрешено параллельное считывание, // считывание будет происходить последовательно for (Long64_t i = 0; i < tree->GetEntries(); ++i) { tree->GetEntry(i); // последовательное считывание } return 0; }
Такая параллелизация создает одну задачу для каждой ветви верхнего уровня анализируемого дерева. В этом примере большинство ветвей являются числами с плавающей запятой, которые очень быстро читаются. Однако эта параллелизация может использоваться и на больших деревьях со многими (сложными) ветвями. В этом случае выгода от ускорения будет более очевидной.