Skip to content

Commit

Permalink
feat: thread-safe configuration reload functions (#258)
Browse files Browse the repository at this point in the history
  • Loading branch information
c-dilks authored Sep 26, 2024
1 parent 08df2cc commit a107c98
Show file tree
Hide file tree
Showing 38 changed files with 1,088 additions and 351 deletions.
66 changes: 29 additions & 37 deletions .github/make-benchmark-table.rb
Original file line number Diff line number Diff line change
@@ -1,51 +1,43 @@
#!/usr/bin/env ruby

if ARGV.empty?
$stderr.puts "USAGE: #{$0} [benchmark_output]"
$stderr.puts "USAGE: #{$0} [benchmark_outputs]"
exit 2
end

# parse the benchmark output
benchmark_results = File.readlines(ARGV[0])
.grep(/.*benchmark-algorithm.*/)
.map(&:chomp)
.map do |line|
results = Hash.new
suites = Array.new

ARGV.each do |benchmark_file|
File.readlines(benchmark_file).grep(/.*benchmark-.*/).map(&:chomp).each do |line|

tokens = line.split
if tokens.size == 4
[
tokens[1],
tokens[3].gsub('s','').to_f
]
else
nil
end
end.compact

# collect the algorithms' execution times into a Hash
benchmark_hash = Hash.new
benchmark_results.each do |result|
name = result[0]
.gsub(/benchmark-algorithm-/, '')
.gsub(/-/, '::')
unless benchmark_hash.has_key? name
benchmark_hash[name] = Array.new
next unless tokens.size == 6

name = tokens[3]
time = tokens[5].gsub('s','').to_f
suite = name.split('-')[1]
algo_name = name.split('-')[2..-1].join('::')

results[algo_name] = Hash.new unless results.has_key? algo_name
results[algo_name][suite] = time
suites << suite
end
benchmark_hash[name] << result[1]
end

# print the table
suites.uniq!

def row(arr)
puts '| ' + arr.join(' | ') + ' |'
end
puts "### Benchmarks\n\n"
row ['Algorithm', 'Average Time (s)']
row ['---', '---']
benchmark_hash.each do |name,times|
n = times.size
ave = times.sum / n
stddev = Math.sqrt( 1.0 / n * times.map{ |t| (t-ave)**2 }.sum )
err = stddev / Math.sqrt(n)
prec = 3
row ["`#{name}`", "$#{ave.round prec}~~~\\pm~~~#{err.round prec}$"]
puts """### Benchmarks
Algorithm execution times in seconds
"""
row ['Algorithm', *suites.map{|suite|suite.gsub /_/, ' '}]
row (suites.size+1).times.map{ |i| '---' }

results.each do |algo_name, algo_results|
times = suites.map{ |suite| algo_results[suite] }
row ["`#{algo_name}`", *times]
end
puts ''
26 changes: 15 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ env:
hipo_ref: 20094876df040ec234dd376fa527cf58442b775a
# test options
num_events: 1000
num_threads: 0 # use 0 for "all" available cores
verbose_test: 'false' # only set this to 'true' if `meson test` fails and you need more output to investigate

jobs:
Expand Down Expand Up @@ -364,14 +365,15 @@ jobs:
### build
- name: meson setup
run: |
meson setup iguana_build iguana_src \
--prefix=$(pwd)/iguana \
--pkg-config-path=$(pwd)/hipo/lib/pkgconfig \
-Dwerror=true \
-Dinstall_examples=true \
-Dtest_data_file=$(pwd)/test_data.hipo \
-Dtest_num_events=${{ env.num_events }} \
-Dtest_output_dir=$(pwd)/validation_results \
meson setup iguana_build iguana_src \
--prefix=$(pwd)/iguana \
--pkg-config-path=$(pwd)/hipo/lib/pkgconfig \
-Dwerror=true \
-Dinstall_examples=true \
-Dtest_data_file=$(pwd)/test_data.hipo \
-Dtest_num_events=${{ env.num_events }} \
-Dtest_output_dir=$(pwd)/validation_results \
-Dtest_multithreading=${{ env.num_threads }} \
${{ matrix.opts }}
- name: dump all build options
run: meson configure iguana_build --no-pager
Expand Down Expand Up @@ -407,8 +409,10 @@ jobs:
- name: benchmark algorithms
if: ${{ matrix.id == 'coverage' }} # use the coverage job's GITHUB_STEP_SUMMARY
run: |
meson test --benchmark --repeat 5 -C iguana_build > benchmark.txt
iguana_src/.github/make-benchmark-table.rb benchmark.txt | xargs -0 -I{} echo {} >> $GITHUB_STEP_SUMMARY
for suite in single_threaded memoize; do
meson test --benchmark --suite $suite -C iguana_build | tee benchmark_$suite.txt
done
iguana_src/.github/make-benchmark-table.rb benchmark_{single_threaded,memoize}.txt | xargs -0 -I{} echo {} >> $GITHUB_STEP_SUMMARY
### coverage
- name: coverage
if: ${{ matrix.id == 'coverage' }}
Expand Down Expand Up @@ -487,7 +491,7 @@ jobs:
uses: actions/upload-artifact@v4
if: always()
with:
name: logs_iguana_build_${{ matrix.id }}
name: meson_logs_${{ matrix.id }}
retention-days: 5
path: iguana_build/meson-logs
- name: upload coverage artifacts
Expand Down
1 change: 1 addition & 0 deletions doc/gen/Doxyfile.in
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ ALIASES += begin_doc_config="\par Configuration Options:^^<table><tr><th>Name</t
ALIASES += config_param{3|}="<tr><td>`\1`</td><td>`\2`</td><td>\3</td></tr>"
# action functions
ALIASES += action_function{1}="\xrefitem action \"Function Type\" \"List of all Action Functions\" \1 \brief **Action Function:** "
ALIASES += when_to_call{1}="@note This function should be called **\1**"
# examples
ALIASES += begin_doc_example{1}="@addtogroup examples_\1^^@{"
ALIASES += end_doc_example="@}"
Expand Down
2 changes: 2 additions & 0 deletions doc/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ The following sections (🟩) list the dependencies and how to obtain them.
> git log --tags --decorate --simplify-by-decoration --oneline # list all the tags (latest first)
> git checkout 1.0.0 # checkout the tag '1.0.0'
> ```
<!--`-->
### 🟩 `meson`: Build system used by Iguana
<https://mesonbuild.com/>
Expand Down Expand Up @@ -146,6 +147,7 @@ meson install # installs Iguana to your prefix (build option 'prefix')
> meson setup --wipe /path/to/iguana-source
> ```
> This will preserve your build options; then try to rebuild.
<!--`-->
<a name="env"></a>
Expand Down
9 changes: 3 additions & 6 deletions examples/config/my_combined_config_file.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,15 @@ clas12::ZVertexFilter:

# default cuts
- default:
pids: [ 11 ]
cuts: [ -33.0, 11.0 ]
electron_vz: [ -33.0, 11.0 ]

#RG-A fall2018 inbending
- runs: [ 4760, 5419 ]
pids: [ 11 ]
cuts: [ -13.0, 12.0 ]
electron_vz: [ -13.0, 12.0 ]

#RG-A fall2018 outbending
- runs: [ 5420, 5674 ]
pids: [ 11 ]
cuts: [ -18.0, 10.0 ]
electron_vz: [ -18.0, 10.0 ]

another::Algorithm:
#Cuts below are just examples:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,12 @@ clas12::ZVertexFilter:

# default cuts
- default:
pids: [ 11 ]
cuts: [ -15.0, 15.0 ]
electron_vz: [ -15.0, 15.0 ]

# RG-A fall2018 inbending
- runs: [ 4760, 5419 ]
pids: [ 11 ]
cuts: [ -5.0, 3.0 ]
electron_vz: [ -5.0, 3.0 ]

# RG-A fall2018 outbending
- runs: [ 5420, 5674 ]
pids: [ 11 ]
cuts: [ -8.0, 7.0 ]
electron_vz: [ -8.0, 7.0 ]
9 changes: 3 additions & 6 deletions examples/config/my_z_vertex_cuts.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,12 @@ clas12::ZVertexFilter:

# default cuts
- default:
pids: [ 11 ]
cuts: [ -1.5, 1.3 ]
electron_vz: [ -1.5, 1.3 ]

# RG-A fall2018 inbending
- runs: [ 4760, 5419 ]
pids: [ 11 ]
cuts: [ -0.5, 0.5 ]
electron_vz: [ -0.5, 0.5 ]

# RG-A fall2018 outbending
- runs: [ 5420, 5674 ]
pids: [ 11 ]
cuts: [ -0.8, 0.7 ]
electron_vz: [ -0.8, 0.7 ]
4 changes: 4 additions & 0 deletions examples/iguana_ex_cpp_01_action_functions.cc
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ int main(int argc, char** argv)
enum banks_enum { b_particle,
b_config }; // TODO: users shouldn't have to do this

// set the concurrency model to single-threaded, since this example is single-threaded;
// not doing this will use the thread-safe model, `"memoize"`
iguana::GlobalConcurrencyModel = "single";

// create the algorithms
iguana::clas12::EventBuilderFilter algo_eventbuilder_filter;
iguana::clas12::MomentumCorrection algo_momentum_correction;
Expand Down
3 changes: 2 additions & 1 deletion examples/iguana_ex_cpp_ROOT_macro.C
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ void iguana_ex_cpp_ROOT_macro() {

// run the inclusive kinematics action function for a scattered electron lepton momentum,
// and print out the resulting inclusive kinematics
auto result = algo.ComputeFromLepton(0.3, 0.3, 5.0);
auto key = algo.PrepareEvent(5032);
auto result = algo.ComputeFromLepton(0.3, 0.3, 5.0, key);
std::cout << "kinematics:"
<< "\n Q2 = " << result.Q2
<< "\n x = " << result.x
Expand Down
116 changes: 67 additions & 49 deletions examples/iguana_ex_cpp_config_files.cc
Original file line number Diff line number Diff line change
Expand Up @@ -42,67 +42,85 @@ int main(int argc, char** argv)
switch(example) {

case 1:
// Use the default configuration, from `../src/iguana/algorithms/clas12/ZVertexFilter.yaml`
algo->SetOption("runnum", 4800);
algo->Start();
assert((algo->GetRunNum() == 4800));
assert((algo->GetZcutLower() == -13.0));
assert((algo->GetZcutUpper() == 12.0));
break;
{
// Use the default configuration, from `../src/iguana/algorithms/clas12/ZVertexFilter.yaml`
algo->Start();
auto key = algo->PrepareEvent(4800); // sets the run number and loads the cuts
assert((algo->GetRunNum(key) == 4800)); // pass the key into the 'Get*' methods
assert((algo->GetElectronZcuts(key).at(0) == -13.0));
assert((algo->GetElectronZcuts(key).at(1) == 12.0));
break;
}

case 2:
// Use `SetOption` to set the cuts
// - note that this will OVERRIDE any value used in any configuration file
// - only use `SetOption` if for any reason you want to hard-code a specific value; usage
// of configuration files is preferred in general
algo->SetOption<std::vector<double>>("cuts", {-5.0, 3.0});
algo->Start();
assert((algo->GetZcutLower() == -5.0));
assert((algo->GetZcutUpper() == 3.0));
break;
{
// Use `SetElectronZcuts` to set the cuts
// - note that this will OVERRIDE any value used in any configuration file
// - only use `SetElectronZcuts` if for any reason you want to hard-code a specific value; usage
// of configuration files is preferred in general
algo->Start();
iguana::concurrent_key_t const key = 0; // need the same key in `SetElectronZcuts` and `GetElectronZcuts`
algo->SetElectronZcuts(-5.0, 3.0, key);
assert((algo->GetElectronZcuts(key).at(0) == -5.0));
assert((algo->GetElectronZcuts(key).at(1) == 3.0));
break;
}

case 3:
// Use a specific configuration file
algo->SetConfigFile(configDir + "/my_z_vertex_cuts.yaml");
algo->SetOption("runnum", 5500);
algo->Start();
assert((algo->GetZcutLower() == -0.8));
assert((algo->GetZcutUpper() == 0.7));
break;
{
// Use a specific configuration file
algo->SetConfigFile(configDir + "/my_z_vertex_cuts.yaml");
algo->Start();
auto key = algo->PrepareEvent(5500);
assert((algo->GetElectronZcuts(key).at(0) == -0.8));
assert((algo->GetElectronZcuts(key).at(1) == 0.7));
break;
}

case 4:
// Use the same specific configuration file, but don't set a run number;
// note also the usage of `SetConfigDirectory`, as another example how to set a specific configuration file
algo->SetConfigDirectory(configDir);
algo->SetConfigFile("my_z_vertex_cuts.yaml");
algo->Start();
assert((algo->GetZcutLower() == -1.5));
assert((algo->GetZcutUpper() == 1.3));
break;
{
// Use the same specific configuration file, but don't set a run number;
// note also the usage of `SetConfigDirectory`, as another example how to set a specific configuration file
algo->SetConfigDirectory(configDir);
algo->SetConfigFile("my_z_vertex_cuts.yaml");
algo->Start();
auto key = algo->PrepareEvent(0); // run number "0" means "no run number"
assert((algo->GetElectronZcuts(key).at(0) == -1.5));
assert((algo->GetElectronZcuts(key).at(1) == 1.3));
break;
}

case 5:
// Use a custom directory of configuration files; if a configuration file within
// has the same path and name as the default (`ZVertexFilter.yaml`), it will be used instead of the default.
// This is designed such that if you copy the full installed configuration directory to a new location, you
// may use that directory instead of the default, and modify any configuration file within.
algo->SetConfigDirectory(configDir + "/my_config_directory");
algo->Start();
assert((algo->GetZcutLower() == -15.0));
assert((algo->GetZcutUpper() == 15.0));
break;
{
// Use a custom directory of configuration files; if a configuration file within
// has the same path and name as the default (`ZVertexFilter.yaml`), it will be used instead of the default.
// This is designed such that if you copy the full installed configuration directory to a new location, you
// may use that directory instead of the default, and modify any configuration file within.
algo->SetConfigDirectory(configDir + "/my_config_directory");
algo->Start();
auto key = algo->PrepareEvent(0); // run number "0" means "no run number"
assert((algo->GetElectronZcuts(key).at(0) == -15.0));
assert((algo->GetElectronZcuts(key).at(1) == 15.0));
break;
}

case 6:
// Use a single, combined configuration file; each algorithm's options are in a separate section
algo->SetConfigDirectory(configDir);
algo->SetConfigFile("my_combined_config_file.yaml");
algo->Start();
assert((algo->GetZcutLower() == -33.0));
assert((algo->GetZcutUpper() == 11.0));
break;
{
// Use a single, combined configuration file; each algorithm's options are in a separate section
algo->SetConfigDirectory(configDir);
algo->SetConfigFile("my_combined_config_file.yaml");
algo->Start();
auto key = algo->PrepareEvent(0); // run number "0" means "no run number"
assert((algo->GetElectronZcuts(key).at(0) == -33.0));
assert((algo->GetElectronZcuts(key).at(1) == 11.0));
break;
}

default:
fmt::print(stderr, "ERROR: unknown example number '{}'\n", example);
return 1;
{
fmt::print(stderr, "ERROR: unknown example number '{}'\n", example);
return 1;
}
}

algo->Stop();
Expand Down
Loading

0 comments on commit a107c98

Please sign in to comment.