TkN can be used in a multi-threaded read-only program. In this example, a large number of nuclei are randomly produced and one state is selected for each nucleus. If the example is linked with ROOT, every worker fills private histograms; they are merged only after all workers have joined.
By default, the example preloads all default level schemes before starting the workers:
The following indicative wall times were measured with the ROOT-free Release build on an Apple M4 Pro (14 cores), using the TkN 2.7 database and 107 events. They are one representative run, not a performance guarantee. Initialisation took about 0.07 s without preloading; full preloading took about 2.3 s.
Preloading moves the serial database work before the timed event loop. Lazy loading avoids loading unused nuclei, but the first access to each encountered nucleus is included in the event time.
#include <algorithm>
#include <chrono>
#include <getopt.h>
#include <iomanip>
#include <iostream>
#include <memory>
#include <random>
#include <thread>
#include <vector>
#include "tkmanager.h"
#ifdef HAS_ROOT
#include "TH2F.h"
#include "TFile.h"
#include "TROOT.h"
#endif
#define no_argument 0
#define required_argument 1
#define optional_argument 2
using namespace std;
struct thread_results {
#ifdef HAS_ROOT
explicit thread_results(int worker)
: hNucChart(new TH2F(
tkstring::Form(
"NucChart_worker_%d", worker).data(),
"NucChart", 90, 0, 180, 60, 0, 120)),
hNucChartBetaminus(new TH2F(
tkstring::Form(
"NucChartBetaminus_worker_%d", worker).data(),
"NucChartBetaminus", 90, 0, 180, 60, 0, 120)),
hNucChartBetaplus(new TH2F(
tkstring::Form(
"NucChartBetaplus_worker_%d", worker).data(),
"NucChartBetaplus", 90, 0, 180, 60, 0, 120)),
hNucChartStable(new TH2F(
tkstring::Form(
"NucChartStable_worker_%d", worker).data(),
"NucChartStable", 90, 0, 180, 60, 0, 120)),
hRandExcitedState(new TH1F(
tkstring::Form(
"RandExcitedState_worker_%d", worker).data(),
"RandExcitedState", 20000, 0, 20000))
{
hNucChart->SetDirectory(nullptr);
hNucChartBetaminus->SetDirectory(nullptr);
hNucChartBetaplus->SetDirectory(nullptr);
hNucChartStable->SetDirectory(nullptr);
hRandExcitedState->SetDirectory(nullptr);
}
unique_ptr<TH2F> hNucChart;
unique_ptr<TH2F> hNucChartBetaminus;
unique_ptr<TH2F> hNucChartBetaplus;
unique_ptr<TH2F> hNucChartStable;
unique_ptr<TH1F> hRandExcitedState;
#else
explicit thread_results(int) {}
#endif
};
void a_thread(int ithread, long NEvents, thread_results *results);
void print_help();
void print_help() {
glog <<
"this utility allows to test the multi thread compatibility" <<
do_endl;
glog <<
"supported options:" <<
do_endl;
glog <<
" --evts N number of random nuclei to process (default 1e6)" <<
do_endl;
glog <<
" --workers N number of workers to be used (default 1)" <<
do_endl;
glog <<
" --no-preload do not preload level schemes before starting workers" <<
do_endl;
}
int main(int argc, char **argv) {
int option_index = 0;
static struct option long_options[] = {
{"evts", required_argument, nullptr, 'e'},
{"workers", required_argument, nullptr, 'w'},
{"no-preload", no_argument, nullptr, 'n'},
{"help", no_argument, nullptr, 'h'},
{nullptr, 0, nullptr, 0}};
long NEvents=1e6;
int NWorkers = 1;
bool preload = true;
while (true) {
int c = getopt_long (argc, argv, "e:w:nh", long_options, &option_index);
if (c == -1) break;
switch (c) {
case 'e':
NEvents = atof(optarg);
break;
case 'w':
NWorkers = atoi(optarg);
break;
case 'n':
preload = false;
break;
case 'h':
print_help();
exit(EXIT_SUCCESS);
}
}
if (NEvents < 0 || NWorkers < 1) {
glog <<
error <<
"--evts must be non-negative and --workers must be positive" <<
do_endl;
return EXIT_FAILURE;
}
cout << "***********************************************************************************" << endl;
cout << "* Randomly produce a nucleus and extract a random excited state in multi thread *" << endl;
cout << "***********************************************************************************" << endl;
cout<<endl;
cout << "Nevents : " << NEvents << endl;
cout << "N workers: " << NWorkers << endl;
cout << "Preload : " << (preload ? "yes" : "no") << endl;
glog.set_warnings(false);
auto tsload = std::chrono::high_resolution_clock::now();
(void)gmanager->get_nuclei();
if (preload) gmanager->preload_level_schemes(true);
cout<<endl;
auto tstart = std::chrono::high_resolution_clock::now();
vector<thread> threads;
vector<unique_ptr<thread_results>> results;
threads.reserve(NWorkers);
results.reserve(NWorkers);
for(int i=0 ; i<NWorkers ; i++) {
results.emplace_back(make_unique<thread_results>(i));
const long worker_events = NEvents / NWorkers + (i < NEvents % NWorkers ? 1 : 0);
threads.emplace_back(thread(a_thread, i, worker_events, results.back().get()));
}
for(int i=0 ; i<NWorkers ; i++) {
threads.at(i).join();
}
auto tstop = std::chrono::high_resolution_clock::now();
#ifdef HAS_ROOT
TFile fout("tkn-thread.root", "recreate");
TH2F hNucChart("NucChart", "NucChart", 90, 0, 180, 60, 0, 120);
TH2F hNucChartBetaminus("NucChartBetaminus", "NucChartBetaminus", 90, 0, 180, 60, 0, 120);
TH2F hNucChartBetaplus("NucChartBetaplus", "NucChartBetaplus", 90, 0, 180, 60, 0, 120);
TH2F hNucChartStable("NucChartStable", "NucChartStable", 90, 0, 180, 60, 0, 120);
TH1F hRandExcitedState("RandExcitedState", "RandExcitedState", 20000, 0, 20000);
for (const auto &worker : results) {
hNucChart.Add(worker->hNucChart.get());
hNucChartBetaminus.Add(worker->hNucChartBetaminus.get());
hNucChartBetaplus.Add(worker->hNucChartBetaplus.get());
hNucChartStable.Add(worker->hNucChartStable.get());
hRandExcitedState.Add(worker->hRandExcitedState.get());
}
hNucChart.Write();
hNucChartBetaminus.Write();
hNucChartBetaplus.Write();
hNucChartStable.Write();
hRandExcitedState.Write();
glog <<
info <<
"tkn-thread.root created with content: " <<
do_endl;
fout.ls();
fout.Close();
#endif
auto dtload = std::chrono::duration<double, std::milli>(tstart-tsload).count() * 0.001;
auto dt_process = std::chrono::duration<double, std::milli>(tstop-tstart).count() * 0.001;
auto dt = std::chrono::duration<double, std::milli>(tstop-tsload).count() * 0.001;
cout<<endl;
cout << fixed << setprecision(4) << "Initialization/preload wall time: " << dtload << " s\n";
cout << fixed << setprecision(4) << "Event processing wall time : " << dt_process << " s\n";
cout << fixed << setprecision(4) << "Total measured wall time : " << dt << " s\n";
}
void a_thread(int ithread, long NEvents, thread_results *results) {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dist_nuc(0, gmanager->get_nuclei().size()-1);
const long Fact = std::max(1L, NEvents / 100);
for(long entry=0 ; entry<NEvents ; entry++) {
if(ithread==0 && entry%Fact==0) {
cout<<"\r"<<"Analysis progress : "<<setw(3)<<setprecision(3)<<(double)entry/(double)NEvents*100<<" %"<<flush;
}
long inuc = dist_nuc(gen);
auto nuc = gmanager->get_nuclei().at(inuc);
const auto level_scheme = nuc->get_level_scheme();
const auto &levels = level_scheme->get_levels();
if(levels.size()>1) {
std::uniform_int_distribution<> dist_lev(0, levels.size()-1);
long ilvl = dist_lev(gen);
#ifdef HAS_ROOT
results->hNucChart->Fill(nuc->get_n(),nuc->get_z());
if(nuc->has_property("QbetaMinus") && nuc->get("QbetaMinus")->get_value()>0) results->hNucChartBetaminus->Fill(nuc->get_n(),nuc->get_z());
if(nuc->has_property("QpositronEmission") && nuc->get("QpositronEmission")->get_value()>0) results->hNucChartBetaplus->Fill(nuc->get_n(),nuc->get_z());
if(nuc->is_stable()) results->hNucChartStable->Fill(nuc->get_n(),nuc->get_z());
if(elev>0) results->hRandExcitedState->Fill(elev);
#endif
}
}
}
std::string with usefull tricks from TString (ROOT) and KVString (KaliVeda) and more....
tklog & error(tklog &log)
tklog & do_endl(tklog &log)
tklog & high_intensity(tklog &log)
Colors.