diff --git a/mechanisms/generate.sh b/mechanisms/generate.sh index 1fcd49beb79603e6d79cf35e1e4b9f699fcb2f49..89dfd5e277f9811bd91cdc363a61f41a1e81b706 100755 --- a/mechanisms/generate.sh +++ b/mechanisms/generate.sh @@ -1,3 +1,5 @@ +#!/usr/bin/env bash + #flags="-t cpu -O" flags="-t cpu" diff --git a/miniapp/io.cpp b/miniapp/io.cpp index 6b81a62c853fbc2098d343c0c61b926903f47c52..fbc70ef823a660c4a14cb4610f1dd011251e4f2b 100644 --- a/miniapp/io.cpp +++ b/miniapp/io.cpp @@ -113,8 +113,27 @@ static void update_option(util::optional<T>& opt, const nlohmann::json& j, const cl_options read_options(int argc, char** argv) { // Default options: - const cl_options defopts{1000, 500, "expsyn", 100, 100., 0.025, false, - false, 1.0, "trace_", util::nothing}; + const cl_options defopts{ + 1000, // number of cells + 500, // synapses_per_cell + "expsyn", // synapse type + 100, // compartments_per_segment + 100., // tfinal + 0.025, // dt + false, // all_to_all + false, // probe_soma_only + 1.0, // probe_ratio + "trace_", // trace_prefix + util::nothing, // trace_max_gid + + // spike_output_parameters: + false, // spike output + false, // single_file_per_simulation + true, // Overwrite outputfile if exists + "./", // output path + "spikes", // file name + "gdf" // file extention + }; cl_options options; std::string save_file = ""; @@ -189,6 +208,17 @@ cl_options read_options(int argc, char** argv) { update_option(options.probe_soma_only, fopts, "probe_soma_only"); update_option(options.trace_prefix, fopts, "trace_prefix"); update_option(options.trace_max_gid, fopts, "trace_max_gid"); + + // Parameters for spike output + update_option(options.spike_file_output, fopts, "spike_file_output"); + if (options.spike_file_output) { + update_option(options.single_file_per_rank, fopts, "single_file_per_rank"); + update_option(options.over_write, fopts, "over_write"); + update_option(options.output_path, fopts, "output_path"); + update_option(options.file_name, fopts, "file_name"); + update_option(options.file_extention, fopts, "file_extention"); + } + } catch (std::exception& e) { throw model_description_error( @@ -241,8 +271,8 @@ cl_options read_options(int argc, char** argv) { else { fopts["trace_max_gid"] = nullptr; } - fid << std::setw(3) << fopts << "\n"; + } catch (std::exception& e) { throw model_description_error( @@ -257,7 +287,7 @@ cl_options read_options(int argc, char** argv) { } std::ostream& operator<<(std::ostream& o, const cl_options& options) { - o << "simultion options:\n"; + o << "simulation options:\n"; o << " cells : " << options.cells << "\n"; o << " compartments/segment : " << options.compartments_per_segment << "\n"; o << " synapses/cell : " << options.synapses_per_cell << "\n"; diff --git a/miniapp/io.hpp b/miniapp/io.hpp index 510a0611c9e72c4084cf3487579a1a5b239efadc..9de6299f2a582dccda0913fb520cf133d2546bc4 100644 --- a/miniapp/io.hpp +++ b/miniapp/io.hpp @@ -25,6 +25,14 @@ struct cl_options { double probe_ratio; std::string trace_prefix; util::optional<unsigned> trace_max_gid; + + // Parameters for spike output + bool spike_file_output; + bool single_file_per_rank; + bool over_write; + std::string output_path; + std::string file_name; + std::string file_extention; }; class usage_error: public std::runtime_error { diff --git a/miniapp/miniapp.cpp b/miniapp/miniapp.cpp index 3e9d4dd227efbb90823b99db2fedd00cc94e4f81..cf7592cd319d8cc3365f8e6465976fd0f31a3a34 100644 --- a/miniapp/miniapp.cpp +++ b/miniapp/miniapp.cpp @@ -3,20 +3,23 @@ #include <iostream> #include <fstream> #include <memory> +#include <vector> #include <json/src/json.hpp> #include <common_types.hpp> #include <cell.hpp> #include <cell_group.hpp> +#include <communication/communicator.hpp> +#include <communication/global_policy.hpp> #include <fvm_cell.hpp> +#include <io/exporter_spike_file.hpp> #include <mechanism_catalogue.hpp> #include <model.hpp> -#include <threading/threading.hpp> #include <profiling/profiler.hpp> -#include <communication/communicator.hpp> -#include <communication/global_policy.hpp> +#include <threading/threading.hpp> #include <util/ioutil.hpp> +#include <util/nop.hpp> #include <util/optional.hpp> #include "io.hpp" @@ -26,15 +29,17 @@ using namespace nest::mc; using global_policy = communication::global_policy; - using lowered_cell = fvm::fvm_cell<double, cell_local_size_type>; using model_type = model<lowered_cell>; -using sample_trace_type = sample_trace<model_type::time_type, model_type::value_type>; - +using time_type = model_type::time_type; +using sample_trace_type = sample_trace<time_type, model_type::value_type>; +using file_export_type = io::exporter_spike_file<time_type, global_policy>; void banner(); std::unique_ptr<recipe> make_recipe(const io::cl_options&, const probe_distribution&); std::unique_ptr<sample_trace_type> make_trace(cell_member_type probe_id, probe_spec probe); std::pair<cell_gid_type, cell_gid_type> distribute_cells(cell_size_type ncells); +using communicator_type = communication::communicator<time_type, communication::global_policy>; +using spike_type = typename communicator_type::spike_type; void write_trace_json(const sample_trace_type& trace, const std::string& prefix = "trace_"); @@ -61,9 +66,41 @@ int main(int argc, char** argv) { auto recipe = make_recipe(options, pdist); auto cell_range = distribute_cells(recipe->num_cells()); - // build model from recipe + model_type m(*recipe, cell_range.first, cell_range.second); + // File output is depending on the input arguments + std::unique_ptr<file_export_type> file_exporter; + std::function<void(const std::vector<spike_type>&)> do_nothing{ + util::nop_function }; + if (!options.spike_file_output) { + m.set_global_spike_callback(do_nothing); + m.set_local_spike_callback(do_nothing); + } + else { + // The exporter is the same for both global and local output + // just registered as a different callback + file_exporter = + util::make_unique<file_export_type>( + options.file_name, options.output_path, + options.file_extention, options.over_write); + + if (options.single_file_per_rank) { + m.set_global_spike_callback(do_nothing); + m.set_local_spike_callback( + [&](const std::vector<spike_type>& spikes) { + file_exporter->output(spikes); + }); + } + else { + m.set_global_spike_callback( + [&](const std::vector<spike_type>& spikes) { + file_exporter->output(spikes); + }); + m.set_local_spike_callback(do_nothing); + } + } + // inject some artificial spikes, 1 per 20 neurons. cell_gid_type spike_cell = 20*((cell_range.first+19)/20); for (; spike_cell<cell_range.second; spike_cell+=20) { @@ -155,7 +192,7 @@ std::unique_ptr<sample_trace_type> make_trace(cell_member_type probe_id, probe_s case probeKind::membrane_current: name = "i"; units = "mA/cm²"; - break; + break; default: ; } name += probe.location.segment? "dend" : "soma"; @@ -183,5 +220,3 @@ void write_trace_json(const sample_trace_type& trace, const std::string& prefix) std::ofstream file(path); file << std::setw(1) << jrep << std::endl; } - - diff --git a/nrn/generate_validation.sh b/nrn/generate_validation.sh index 532a7cd738d53a1a0f8342d07f09c016b5e24857..15149cddc022aa47d2f94149fc4efc6c833e9ecd 100755 --- a/nrn/generate_validation.sh +++ b/nrn/generate_validation.sh @@ -1,5 +1,7 @@ -python2.7 ./soma.py -python2.7 ./ball_and_stick.py -python2.7 ./ball_and_3stick.py -python2.7 ./simple_synapse.py --synapse exp2 -python2.7 ./simple_synapse.py --synapse exp +#!/usr/bin/env bash + +python2 ./soma.py +python2 ./ball_and_stick.py +python2 ./ball_and_3stick.py +python2 ./simple_synapse.py --synapse exp2 +python2 ./simple_synapse.py --synapse exp diff --git a/src/communication/communicator.hpp b/src/communication/communicator.hpp index 8c72430f5c4646ce7747c76cac088bee65a58df8..50ce644c2bc62904625fd74c68fe7a98de059395 100644 --- a/src/communication/communicator.hpp +++ b/src/communication/communicator.hpp @@ -4,6 +4,7 @@ #include <iostream> #include <vector> #include <random> +#include <functional> #include <spike.hpp> #include <util/double_buffer.hpp> @@ -90,11 +91,15 @@ public: /// Returns a vector of event queues, with one queue for each local cell group. The /// events in each queue are all events that must be delivered to targets in that cell /// group as a result of the global spike exchange. - std::vector<event_queue> exchange(const std::vector<spike_type>& local_spikes) { + std::vector<event_queue> exchange(const std::vector<spike_type>& local_spikes, + std::function<void(const std::vector<spike_type>&)> global_export_callback) + { // global all-to-all to gather a local copy of the global spike list on each node. auto global_spikes = communication_policy_.gather_spikes( local_spikes ); num_spikes_ += global_spikes.size(); + global_export_callback(global_spikes); + // check each global spike in turn to see it generates local events. // if so, make the events and insert them into the appropriate event list. auto queues = std::vector<event_queue>(num_groups_local()); diff --git a/src/io/exporter.hpp b/src/io/exporter.hpp new file mode 100644 index 0000000000000000000000000000000000000000..a5885b7b10841593059402a491ccf21148b78df3 --- /dev/null +++ b/src/io/exporter.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include <random> +#include <string> + +#include <common_types.hpp> +#include <spike.hpp> + +namespace nest { +namespace mc { +namespace io { + +// interface for exporters. +// Exposes one virtual functions: +// do_export(vector<type>) receiving a vector of parameters to export + +template <typename Time, typename CommunicationPolicy> +class exporter { + +public: + using time_type = Time; + using spike_type = spike<cell_member_type, time_type>; + + // Performs the export of the data + virtual void output(const std::vector<spike_type>&) = 0; + + // Returns the status of the exporter + virtual bool good() const = 0; +}; + +} //communication +} // namespace mc +} // namespace nest diff --git a/src/io/exporter_spike_file.hpp b/src/io/exporter_spike_file.hpp new file mode 100644 index 0000000000000000000000000000000000000000..e74169d19f17090c3ca16781a5f380fc0d29cc89 --- /dev/null +++ b/src/io/exporter_spike_file.hpp @@ -0,0 +1,98 @@ +#pragma once + +#include <fstream> +#include <iomanip> +#include <memory> +#include <random> +#include <stdexcept> +#include <vector> + +#include <cstring> +#include <cstdio> + +#include <common_types.hpp> +#include <io/exporter.hpp> +#include <spike.hpp> +#include <util.hpp> + +namespace nest { +namespace mc { +namespace io { + +template <typename Time, typename CommunicationPolicy> +class exporter_spike_file : public exporter<Time, CommunicationPolicy> { + +public: + using time_type = Time; + using spike_type = spike<cell_member_type, time_type>; + using communication_policy_type = CommunicationPolicy; + + // Constructor + // over_write if true will overwrite the specified output file (default = true) + // output_path relative or absolute path + // file_name will be appended with "_x" with x the rank number + // file_extention a seperator will be added automatically + exporter_spike_file( + const std::string& file_name, const std::string& path, + const std::string& file_extention, bool over_write=true) + { + auto file_path = + create_output_file_path(file_name, path, file_extention, + communication_policy_.id()); + + //test if the file exist and depending on over_write throw or delete + if (!over_write && file_exists(file_path)) { + throw std::runtime_error("Tried opening file for writing but it exists and over_write is false: " + + file_path); + } + + file_handle_.open(file_path); + } + + // Performs the a export of the spikes to file + // one id and spike time with 4 decimals after the comma on a + // line space separated + void output(const std::vector<spike_type>& spikes) override { + + for (auto spike : spikes) { + char linebuf[45]; + auto n = std::snprintf(linebuf, sizeof(linebuf), "%u %.4f\n", + unsigned{spike.source.gid}, float(spike.time)); + file_handle_.write(linebuf, n); + } + } + + bool good() const override { + + return file_handle_.good(); + } + + // Creates an indexed filename + static std::string create_output_file_path( + const std::string& file_name, const std::string& path, + const std::string& file_extention, unsigned index) + { + // Nest does not produce the indexing for nrank == 0 + // I have the feeling this disrupts consistent output. Id rather + // always put the zero in. it allows a simpler regex when opening + // files + return path + file_name + "_" + std::to_string(index) + "." + + file_extention; + } + +private: + bool file_exists(const std::string& file_path) { + + std::ifstream fid(file_path); + return fid.good(); + } + + // Handle to opened file handle + std::ofstream file_handle_; + + communication_policy_type communication_policy_; +}; + +} //communication +} // namespace mc +} // namespace nest diff --git a/src/model.hpp b/src/model.hpp index 84dd84298a78418ce882b68a29f2211a6e50401b..823fcd45662cfc414b7a04964bc7fb91b9b78281 100644 --- a/src/model.hpp +++ b/src/model.hpp @@ -1,17 +1,19 @@ #pragma once -#include <cstdlib> +#include <memory> #include <vector> +#include <cstdlib> + #include <common_types.hpp> #include <cell.hpp> #include <cell_group.hpp> -#include <fvm_cell.hpp> -#include <recipe.hpp> -#include <thread_private_spike_store.hpp> #include <communication/communicator.hpp> #include <communication/global_policy.hpp> +#include <fvm_cell.hpp> #include <profiling/profiler.hpp> +#include <recipe.hpp> +#include <thread_private_spike_store.hpp> #include "trace_sampler.hpp" @@ -26,6 +28,8 @@ public: using value_type = typename cell_group_type::value_type; using communicator_type = communication::communicator<time_type, communication::global_policy>; using sampler_function = typename cell_group_type::sampler_function; + using spike_type = typename communicator_type::spike_type; + using spike_export_function = std::function<void(const std::vector<spike_type>&)>; struct probe_record { cell_member_type id; @@ -105,7 +109,7 @@ public: auto update_cells = [&] () { threading::parallel_for::apply( 0u, cell_groups_.size(), - [&](unsigned i) { + [&](unsigned i) { auto &group = cell_groups_[i]; PE("stepping","events"); @@ -125,10 +129,18 @@ public: // the previous integration period, generating the postsynaptic // events that must be delivered at the start of the next // integration period at the latest. + + //TODO: + //An improvement might be : + //the exchange method simply exchanges spikes, and does not generate the event queues.It returns a struct that has both 1) the global spike list 2) an integer vector that describes the distribution of spikes across the ranks + //another method called something like build_queues that takes this spike info and returns the local spikes + // and the callbacks can then be called on the spike information directly in the model. auto exchange = [&] () { PE("stepping", "exchange"); auto local_spikes = previous_spikes().gather(); - future_events() = communicator_.exchange(local_spikes); + local_export_callback_(local_spikes); + future_events() = communicator_.exchange( + local_spikes, global_export_callback_); PL(2); }; @@ -167,6 +179,20 @@ public: std::size_t num_spikes() const { return communicator_.num_spikes(); } std::size_t num_groups() const { return cell_groups_.size(); } + // register a callback that will perform a export of the global + // spike vector + void set_global_spike_callback(spike_export_function export_callback) { + + global_export_callback_ = export_callback; + } + + // register a callback that will perform a export of the rank local + // spike vector + void set_local_spike_callback(spike_export_function export_callback) { + + local_export_callback_ = export_callback; + } + private: cell_gid_type cell_from_; cell_gid_type cell_to_; @@ -174,7 +200,6 @@ private: std::vector<cell_group_type> cell_groups_; communicator_type communicator_; std::vector<probe_record> probes_; - using spike_type = typename communicator_type::spike_type; using event_queue_type = typename communicator_type::event_queue; util::double_buffer< std::vector<event_queue_type> > event_queues_; @@ -182,6 +207,9 @@ private: using local_spike_store_type = thread_private_spike_store<time_type>; util::double_buffer< local_spike_store_type > local_spikes_; + spike_export_function global_export_callback_; + spike_export_function local_export_callback_; + // Convenience functions that map the spike buffers and event queues onto // the appropriate integration interval. // diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 10fb65efd052c1d9d7d7fdd5f50bdec0f93cb5a5..c39e22a80f408f824af43f31f1d5e965b1f37ff5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -14,6 +14,9 @@ add_subdirectory(validation) # Test for the internode communication (eg. mpi) add_subdirectory(global_communication) +# Tests for performance: This could include stand alone tests. These do not necessarily be run automatically +add_subdirectory(performance) + # Proposed additional test types: @@ -21,7 +24,6 @@ add_subdirectory(global_communication) # Test to check integration between components -# Tests for performance: This could include stand alone tests. These do not necessarily be run automatically # Numbered tests based on bugs in the tracker diff --git a/tests/global_communication/CMakeLists.txt b/tests/global_communication/CMakeLists.txt index f7d000157e4cfd130399d1f718b67fbc5eb2f86d..82bb6583ea1310db1657e2a5110143fc484abd86 100644 --- a/tests/global_communication/CMakeLists.txt +++ b/tests/global_communication/CMakeLists.txt @@ -1 +1,31 @@ -# Nothing to be done yet +set(HEADERS + ${PROJECT_SOURCE_DIR}/src/swcio.hpp +) +set(COMMUNICATION_SOURCES + test_exporter_spike_file.cpp + # unit test driver + test.cpp +) + +add_executable(global_communication.exe ${COMMUNICATION_SOURCES} ${HEADERS}) + +set(TARGETS global_communication.exe) + +foreach(target ${TARGETS}) + target_link_libraries(${target} LINK_PUBLIC cellalgo gtest) + + if(WITH_TBB) + target_link_libraries(${target} LINK_PUBLIC ${TBB_LIBRARIES}) + endif() + + if(WITH_MPI) + target_link_libraries(${target} LINK_PUBLIC ${MPI_C_LIBRARIES}) + set_property(TARGET ${target} APPEND_STRING PROPERTY LINK_FLAGS "${MPI_C_LINK_FLAGS}") + endif() + + set_target_properties(${target} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/tests" + ) +endforeach() + diff --git a/tests/global_communication/test.cpp b/tests/global_communication/test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c67a065bd88ed85aa56e865b1d4f8f9fa3d5677e --- /dev/null +++ b/tests/global_communication/test.cpp @@ -0,0 +1,19 @@ +#include <iostream> +#include <fstream> +#include <numeric> +#include <vector> + +#include "gtest.h" + +#include "../../src/communication/communicator.hpp" +#include "../../src/communication/global_policy.hpp" + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + + // We need to set the communicator policy at the top level + // this allows us to build multiple communicators in the tests + nest::mc::communication::global_policy_guard global_guard(argc, argv); + + return RUN_ALL_TESTS(); +} diff --git a/tests/global_communication/test_exporter_spike_file.cpp b/tests/global_communication/test_exporter_spike_file.cpp new file mode 100644 index 0000000000000000000000000000000000000000..85065a0c64b81be75a6797ffde8eb3308546210b --- /dev/null +++ b/tests/global_communication/test_exporter_spike_file.cpp @@ -0,0 +1,127 @@ +#include "gtest.h" + +#include <cstdio> +#include <fstream> +#include <iostream> +#include <string> +#include <vector> + +#include <communication/communicator.hpp> +#include <communication/global_policy.hpp> +#include <communication/exporter_spike_file.hpp> + +class exporter_spike_file_fixture : public ::testing::Test +{ +protected: + using time_type = float; + using communicator_type = nest::mc::communication::global_policy; + + using spike_type = + nest::mc::communication::exporter_spike_file<time_type, + communicator_type>::spike_type; + + using exporter_type = + nest::mc::communication::exporter_spike_file<time_type, + communicator_type>; + std::string file_name; + std::string path; + std::string extention; + unsigned index; + + exporter_spike_file_fixture() : + file_name("spikes_exporter_spike_file_fixture"), + path("./"), + extention("gdf"), + index(0) + {} + + std::string get_standard_file_name() + { + return exporter_type::create_output_file_path( + file_name, path, extention, 0); + } + + void SetUp() + { + // code here will execute just before the test ensues + } + + void TearDown() + { + // delete the start create file + std::remove(get_standard_file_name().c_str()); + } + + ~exporter_spike_file_fixture() + {} +}; + +TEST_F(exporter_spike_file_fixture, constructor) +{ + exporter_type exporter(file_name, path, extention, true); + + //test if the file exist and depending on over_write throw or delete + std::ifstream f(get_standard_file_name()); + + EXPECT_TRUE(f.good()); + + // We now know the file exists, so create a new exporter with overwrite false + try + { + exporter_type exporter1(file_name, path, extention, false); + FAIL() << "expected a file already exists error"; + } + catch (std::runtime_error const & err) + { + EXPECT_EQ(err.what(), std::string("Tried opening file for writing but it exists and over_write is false: " + + get_standard_file_name())); + } + catch (...) { + FAIL() << "expected a file already exists error"; + } +} + +TEST_F(exporter_spike_file_fixture, create_output_file_path) +{ + // Create some random paths, no need for fancy tests here + std::string produced_filename = + exporter_type::create_output_file_path( + "spikes", "./", "gdf", 0); + EXPECT_STREQ(produced_filename.c_str(), "./spikes_0.gdf"); + + produced_filename = + exporter_type::create_output_file_path( + "a_name", "../../", "txt", 5); + EXPECT_STREQ(produced_filename.c_str(), "../../a_name_5.txt"); +} + +TEST_F(exporter_spike_file_fixture, do_export) +{ + { + exporter_type exporter(file_name, path, extention); + + // Create some spikes + std::vector<spike_type> spikes; + spikes.push_back({ { 0, 0 }, 0.0 }); + spikes.push_back({ { 0, 0 }, 0.1 }); + spikes.push_back({ { 1, 0 }, 1.0 }); + spikes.push_back({ { 1, 0 }, 1.1 }); + + // now do the export + exporter.do_export(spikes); + } // Force destruction of exporter and explicit flush of the stream + // Test if we have spikes in the file? + std::ifstream f(get_standard_file_name()); + EXPECT_TRUE(f.good()); + + std::string line; + + EXPECT_TRUE(std::getline(f, line)); + EXPECT_STREQ(line.c_str(), "0 0.0000"); + EXPECT_TRUE(std::getline(f, line)); + EXPECT_STREQ(line.c_str(), "0 0.1000"); + EXPECT_TRUE(std::getline(f, line)); + EXPECT_STREQ(line.c_str(), "1 1.0000"); + EXPECT_TRUE(std::getline(f, line)); + EXPECT_STREQ(line.c_str(), "1 1.1000"); +} 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..5d6149398089611dc161fc6591ebabe9bfc9db91 --- /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}) diff --git a/tests/performance/io/disk_io.cpp b/tests/performance/io/disk_io.cpp new file mode 100644 index 0000000000000000000000000000000000000000..526263bf395a81eb36ed050b96cfe1923e04cd25 --- /dev/null +++ b/tests/performance/io/disk_io.cpp @@ -0,0 +1,160 @@ +#include <stdio.h> + +#include <fstream> +#include <iostream> +#include <numeric> + +#include <cell.hpp> +#include <cell_group.hpp> +#include <common_types.hpp> +#include <communication/communicator.hpp> +#include <communication/global_policy.hpp> +#include <fvm_cell.hpp> +#include <io/exporter_spike_file.hpp> +#include <profiling/profiler.hpp> + +using namespace nest::mc; + +using global_policy = communication::global_policy; +using lowered_cell = 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 = io::exporter_spike_file<time_type, global_policy>::spike_type; +using timer = util::timer_type; + +int main(int argc, char** argv) { + + //Setup the possible mpi environment + 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> [simple_output (false|true)]\n" + << " Simple performance test runner for the exporter manager\n" + << " It exports nrspikes nr_repeats using the export_manager and will produce\n" + << " the total, mean and std of the time needed to perform the output to disk\n\n" + + << " <file_per_rank> true will produce a single file per mpi rank\n" + << " <simple_output> true will produce a simplyfied comma seperated output for automatic parsing\n\n" + + << " The application can be started with mpi support and will produce output on a single rank\n" + << " if nrspikes is not a multiple of the nr of mpi rank, floor is take\n" ; + return 1; + } + auto nr_spikes = atoi(argv[1]); + + if (nr_spikes == 0) { + std::cout << "disk_io <nrspikes>\n"; + std::cout << " nrspikes should be a valid integer higher then zero\n"; + + return 1; + } + auto nr_repeats = atoi(argv[2]); + + if (nr_repeats == 0) { + std::cout << "disk_io <nrspikes>\n"; + std::cout << " nr_repeats should be a valid integer higher then zero\n"; + return 1; + } + + auto simple_stats = false; + if (argc == 4) { + std::string simple(argv[3]); + if (simple == std::string("true")) + { + simple_stats = true; + } + } + + // Create the sut + io::exporter_spike_file<time_type, global_policy> exporter( + "spikes", "./", "gdf", true); + + // We need the nr of ranks to calculate the nr of spikes to produce per + // rank + global_policy communication_policy; + + auto nr_ranks = unsigned( communication_policy.size() ); + auto spikes_per_rank = nr_spikes / nr_ranks; + + // Create a set of spikes + std::vector<spike_type> spikes; + + // ********************************************************************* + // To have a somewhat realworld data set we calculate from the nr of spikes + // (assuming 20 hz average) the number of nr of 'simulated' neurons, + // and create idxs using this value. The number of chars in the number + // influences the size of the output and thus the speed + // Also taken that we have only a single second of simulated time + // all spike times should be between 0.0 and 1.0: + auto simulated_neurons = spikes_per_rank / 20; + for (auto idx = unsigned{ 0 }; idx < spikes_per_rank; ++idx) { + + spikes.push_back({ + {idx % simulated_neurons, 0 }, // correct idx + 0.0f + 1 / (0.05f + idx % 20) + }); // semi random float + } + + double timings_arr[nr_repeats]; + double time_total = 0; + + // now output to disk nr_repeats times, while keeping track of the times + for (auto idx = 0; idx < nr_repeats; ++idx) { + auto time_start = timer::tic(); + exporter.output(spikes); + auto run_time = timer::toc(time_start); + + time_total += run_time; + timings_arr[idx] = run_time; + } + + // create the vector here to prevent changes on the heap influencing the + // timeing + std::vector<double> timings; + for (auto idx = 0; idx < nr_repeats; ++idx) { + timings.push_back(timings_arr[idx]); + } + + + // Calculate some statistics + auto sum = std::accumulate(timings.begin(), timings.end(), 0.0); + auto mean = sum / timings.size(); + + std::vector<double> diff(timings.size()); + std::transform( + timings.begin(), timings.end(), diff.begin(), + std::bind2nd(std::minus<double>(), mean) + ); + auto sq_sum = std::inner_product( + diff.begin(), diff.end(), diff.begin(), + 0.0 + ); + auto stdev = std::sqrt(sq_sum / timings.size()); + + auto min = *std::min_element(timings.begin(), timings.end()); + auto max = *std::max_element(timings.begin(), timings.end()); + + + if (communication_policy.id() != 0) { + return 0; + } + + // and output + if (simple_stats) { + std::cout << time_total<< "," + << mean << "," + << stdev << "," + << min << "," + << max << std::endl; + } + else { + std::cout << "total time (ms): " << time_total << std::endl; + std::cout << "mean time (ms): " << mean << std::endl; + std::cout << "stdev time (ms): " << std::endl; + std::cout << "min time (ms): " << min << std::endl; + std::cout << "max time (ms): " << max << 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..ea42304b23e2da851058b8b6d7625f8185ec0db3 --- /dev/null +++ b/tests/performance/io/disk_io.py @@ -0,0 +1,49 @@ +import matplotlib.pyplot as plt +import subprocess +import os + + + +current_script_dir = os.path.dirname(os.path.abspath(__file__)) + +spikes_to_save = 1000000 + +print ( "Simple performance runner for spike output to file. \n" + + str(spikes_to_save) + " spikes will be written to a file and the duration of this \n" + + "operation measured for different number of ranks\n" ) + + +range_nr_rank = [1, 2, 4, 8, 16, 24, 32, 48, 64] +mean = [] +std = [] +min = [] +max = [] +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"], + 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])) + min.append(float(stats[3])) + max.append(float(stats[4])) + + 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', label="mean (std)") +plt.errorbar(range_nr_rank, min, fmt='-', label="min") +plt.errorbar(range_nr_rank, max, fmt='-', label="max") +plt.legend() +plt.show()