Многопоточность и мультипроцессорность

Для повышения производительности расчетов 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;
}			
			

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