diff --git a/src/communication/export_manager.hpp b/src/communication/export_manager.hpp index d3628a847abcdaedb93b92b99ffb5e61f0d8d320..4211b747f41308faaf07be2489d232c0305139da 100644 --- a/src/communication/export_manager.hpp +++ b/src/communication/export_manager.hpp @@ -23,21 +23,19 @@ class export_manager { public: using time_type = Time; using spike_type = spike<cell_member_type, time_type>; - export_manager() + export_manager(bool file_per_rank) { - if (true) { // single file per rank + if (file_per_rank) { // single file per rank rank_exporters_.push_back( nest::mc::util::make_unique< nest::mc::communication::exporter_spike_file<Time, CommunicationPolicy> >( "rank", "./", "gdf")); - - } - if (true) { // single file per simulation + if (!file_per_rank) { // single file per simulation single_exporters_.push_back( nest::mc::util::make_unique< nest::mc::communication::exporter_spike_single_file<Time, CommunicationPolicy> >( diff --git a/src/model.hpp b/src/model.hpp index 19e75a132b3cc523bf8f50ebe13f52e1b1696a9f..f7516c5ccb51bcc66123dfdbb07c71e9dfd986d5 100644 --- a/src/model.hpp +++ b/src/model.hpp @@ -72,7 +72,7 @@ public: bool single_file = true; if (single_file == true) { - exporter_ = nest::mc::util::make_unique<exporter_manager_type>(); + exporter_ = nest::mc::util::make_unique<exporter_manager_type>(false); } // Allocate an empty queue buffer for each cell group diff --git a/tests/performance/CMakeLists.txt b/tests/performance/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..351be926371b61bcf130d43efc42e5fb0ff53eee --- /dev/null +++ b/tests/performance/CMakeLists.txt @@ -0,0 +1,2 @@ +# Unit tests +add_subdirectory(io) diff --git a/tests/performance/io/CMakeLists.txt b/tests/performance/io/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..861b4e411bb5425f483444ae1d1c1ec906612770 --- /dev/null +++ b/tests/performance/io/CMakeLists.txt @@ -0,0 +1,18 @@ +set(HEADERS +) + +set(DISK_IO_SOURCES + disk_io.cpp +) + +add_executable(disk_io.exe ${DISK_IO_SOURCES} ${HEADERS}) + +target_link_libraries(disk_io.exe LINK_PUBLIC cellalgo) + +if(WITH_MPI) + target_link_libraries(disk_io.exe LINK_PUBLIC ${MPI_C_LIBRARIES}) + set_property(TARGET disk_io.exe APPEND_STRING PROPERTY LINK_FLAGS "${MPI_C_LINK_FLAGS}") +endif() + +# Copy the python file that drives the performance tests and produces the output +file(COPY disk_io.py DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) \ No newline at end of file diff --git a/tests/performance/io/disk_io.cpp b/tests/performance/io/disk_io.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f9353c2053961d02ba7115c9d21f39a60e18fcbe --- /dev/null +++ b/tests/performance/io/disk_io.cpp @@ -0,0 +1,155 @@ +#include <iostream> +#include <cstdlib> +#include <ctime> +#include <fstream> +#include <numeric> + + +#include <common_types.hpp> +#include <fvm_cell.hpp> +#include <cell.hpp> +#include <cell_group.hpp> + +#include <communication/communicator.hpp> +#include <communication/global_policy.hpp> +#include <communication/export_manager.hpp> + + + +using namespace nest::mc; + +using global_policy = communication::global_policy; +using lowered_cell = nest::mc::fvm::fvm_cell<double, cell_local_size_type>; +using cell_group_type = cell_group<lowered_cell>; +using time_type = typename cell_group_type::time_type; +using spike_type = communication::exporter_spike_file<time_type, + global_policy>::spike_type; + +int main(int argc, char** argv) { + // Setup the possible mpi environment + nest::mc::communication::global_policy_guard global_guard(argc, argv); + + // very simple command line parsing + if (argc < 3) + { + std::cout << "disk_io <int nrspikes> <int nr_repeats> <file_per_rank (true|false)> [simple_output (false|true)]" << std::endl; + << " Simple performance test runner for the exporter manager" + << " It exports nrspikes nr_repeats using the export_manager and will produce" + << " the total, mean and std of the time needed to perform the output to disk" + << " <file_per_rank> true will produce a single file per mpi rank" + << " <simple_output> true will produce a simplyfied comma seperated output for automatic parsing" + << " The application can be started with mpi support and will produce output on a single rank"; + + std::cout << " if nrspikes is not a multiple of the nr of mpi rank, floor is take" << std::endl; + exit(1); + } + int nr_spikes = atoi(argv[1]); + + if (nr_spikes == 0) + { + std::cout << "disk_io <nrspikes>" << std::endl; + std::cout << " nrspikes should be a valid integer higher then zero" << std::endl; + exit(1); + } + int nr_repeats = atoi(argv[2]); + + if (nr_repeats == 0) + { + std::cout << "disk_io <nrspikes>" << std::endl; + std::cout << " nr_repeats should be a valid integer higher then zero" << std::endl; + exit(1); + } + + bool file_per_rank = false; + std::string single(argv[3]); + if (single == std::string("true")) + { + file_per_rank = true; + } + + bool simple_stats = false; + if (argc == 5) + { + std::string simple(argv[4]); + if (simple == std::string("true")) + { + simple_stats = true; + } + } + + // Create the sut + nest::mc::communication::export_manager<time_type, global_policy> manager(file_per_rank); + + // We need the nr of ranks to calculate the nr of spikes to produce per + // rank + global_policy communication_policy; + unsigned nr_ranks = communication_policy.size(); + unsigned spikes_per_rank = nr_spikes / nr_ranks; + + // Create a set of spikes + std::vector<spike_type> spikes; + for (unsigned idx = 0; idx < spikes_per_rank; ++idx) + { + spikes.push_back({ { idx, 0 }, 0.0f + idx }); + } + + std::vector<int> timings; + + int time_total = 0; + + // now output to disk nr_repeats times, while keeping track of the times + for (int idx = 0; idx < nr_repeats; ++idx) + { + int time_start = clock(); + + manager.do_export_rank(spikes); + + + int time_stop = clock(); + int run_time = (time_stop - time_start); + time_total += run_time; + timings.push_back(run_time); + } + + + // Autoput the individual timings to a comma seperated file + std::ofstream file("file_io_results.csv"); + file << *timings.begin() / double(CLOCKS_PER_SEC) * 1000; + for (auto time_entry = timings.begin()++; time_entry < timings.end(); ++time_entry) + { + + file << "," << *time_entry / double(CLOCKS_PER_SEC) * 1000 ; + } + file << std::endl; + + // Calculate some statistics + double sum = std::accumulate(timings.begin(), timings.end(), 0.0); + double mean = sum / timings.size(); + + std::vector<double> diff(timings.size()); + std::transform(timings.begin(), timings.end(), diff.begin(), + std::bind2nd(std::minus<double>(), mean)); + double sq_sum = std::inner_product(diff.begin(), diff.end(), diff.begin(), 0.0); + double stdev = std::sqrt(sq_sum / timings.size()); + + if (communication_policy.id() != 0) + { + return 0; + } + + // and output + if (simple_stats) + { + std::cout << time_total / double(CLOCKS_PER_SEC) * 1000 << "," << + mean / double(CLOCKS_PER_SEC) * 1000 << "," << + stdev / double(CLOCKS_PER_SEC) * 1000; + } + else + { + std::cout << "total time (ms): " << time_total / double(CLOCKS_PER_SEC) * 1000 << std::endl; + std::cout << "mean time (ms): " << mean / double(CLOCKS_PER_SEC) * 1000 << std::endl; + std::cout << "stdev time (ms): " << stdev / double(CLOCKS_PER_SEC) * 1000 << std::endl; + } + + return 0; +} diff --git a/tests/performance/io/disk_io.py b/tests/performance/io/disk_io.py new file mode 100644 index 0000000000000000000000000000000000000000..e8b40b7b8052d10cc5d4a0ab950359ffd4cc84bc --- /dev/null +++ b/tests/performance/io/disk_io.py @@ -0,0 +1,42 @@ +import subprocess +import os + +import matplotlib.pyplot as plt + + +current_script_dir = os.path.dirname(os.path.abspath(__file__)) + +spikes_to_save = 100000 + +range_nr_rank = [1, 2, 4, 8, 16, 24, 32, 48, 64] +mean = [] +std = [] +for n_rank in range_nr_rank: + # open the disk_io executable + p1 = subprocess.Popen(["mpirun", "-n",str(n_rank), + os.path.join(current_script_dir, "disk_io.exe"), + str(spikes_to_save), str(10), "true" ,"true"], + + stdout=subprocess.PIPE) + + #and grab the raw stats + stats = p1.communicate()[0] + + # convert into list + stats = stats.split(",") + + mean.append(float(stats[1])) + std.append(float(stats[2])) + + print ( "performed test for n_rank= " + str(n_rank)) + + + +print ( range_nr_rank ) +print ( mean ) +print ( std ) + +plt.errorbar(range_nr_rank, mean, yerr=std, fmt='-o') +plt.xscale('log') +plt.yscale('log') +plt.show() \ No newline at end of file