diff --git a/miniapp/miniapp.cpp b/miniapp/miniapp.cpp index b2f0d9ccbbd8760cbb8c56355d5526659c74cc5c..d7db89deab84daa8d49e09b30b6c1d4b86e5f61c 100644 --- a/miniapp/miniapp.cpp +++ b/miniapp/miniapp.cpp @@ -178,8 +178,8 @@ int main(int argc, char** argv) { void banner() { std::cout << "====================\n"; - std::cout << " starting miniapp\n"; - std::cout << " - " << threading::description() << " threading support\n"; + std::cout << " NestMC miniapp\n"; + std::cout << " - " << threading::description() << " threading support (" << threading::num_threads() << ")\n"; std::cout << " - communication policy: " << std::to_string(global_policy::kind()) << " (" << global_policy::size() << ")\n"; std::cout << " - gpu support: " << (config::has_cuda? "on": "off") << "\n"; std::cout << "====================\n"; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 47b79f5fd7ac9e2a036c40eeb10b41bb3bc3be2c..93643789a848522927570eaed4a6833d3c0b6d80 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,7 +1,7 @@ set(BASE_SOURCES + backends/multicore/fvm.cpp common_types_io.cpp cell.cpp - #cell_group_factory.cpp event_binner.cpp model.cpp morphology.cpp @@ -11,14 +11,15 @@ set(BASE_SOURCES profiling/power_meter.cpp profiling/profiler.cpp swcio.cpp - threading/affinity.cpp + hardware/affinity.cpp + hardware/gpu.cpp + hardware/memory.cpp + hardware/power.cpp + threading/threading.cpp util/debug.cpp util/hostname.cpp - util/memory.cpp util/path.cpp - util/power.cpp util/unwind.cpp - backends/multicore/fvm.cpp ) set(CUDA_SOURCES backends/gpu/fvm.cu diff --git a/src/threading/affinity.cpp b/src/hardware/affinity.cpp similarity index 79% rename from src/threading/affinity.cpp rename to src/hardware/affinity.cpp index 16c24197d6e18e8d4964d9a8f2c87cb3adf8d398..8d1895a0d7cf26d0c303f6491ee9cfb0d1137cec 100644 --- a/src/threading/affinity.cpp +++ b/src/hardware/affinity.cpp @@ -1,6 +1,7 @@ +#include <cstdlib> #include <vector> -#include <cstdlib> +#include <util/optional.hpp> #ifdef __linux__ @@ -16,7 +17,7 @@ namespace nest { namespace mc { -namespace threading { +namespace hw { #ifdef __linux__ std::vector<int> get_affinity() { @@ -51,10 +52,14 @@ std::vector<int> get_affinity() { } #endif -unsigned count_available_cores() { - return get_affinity().size(); +util::optional<std::size_t> num_cores() { + auto cores = get_affinity(); + if (cores.size()==0u) { + return util::nothing; + } + return cores.size(); } -} // namespace threading +} // namespace hw } // namespace mc } // namespace nest diff --git a/src/threading/affinity.hpp b/src/hardware/affinity.hpp similarity index 86% rename from src/threading/affinity.hpp rename to src/hardware/affinity.hpp index e46e829eb09017c9412156b1f4c6fdcadbb5e053..598917cdb08dcd0e615982ede0b409d5dec31b98 100644 --- a/src/threading/affinity.hpp +++ b/src/hardware/affinity.hpp @@ -1,10 +1,13 @@ #pragma once +#include <cstdint> #include <vector> +#include <util/optional.hpp> + namespace nest { namespace mc { -namespace threading { +namespace hw { // The list of cores for which the calling thread has affinity. // If calling from the main thread at application start up, before @@ -22,8 +25,8 @@ std::vector<int> get_affinity(); // been playing with thread affinity. // // Returns 0 if unable to determine the number of cores. -unsigned count_available_cores(); +util::optional<std::size_t> num_cores(); -} // namespace threading +} // namespace util } // namespace mc } // namespace nest diff --git a/src/hardware/gpu.cpp b/src/hardware/gpu.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8ee70b1d3e5b62c4ea8581fc0cace736f4e007b5 --- /dev/null +++ b/src/hardware/gpu.cpp @@ -0,0 +1,23 @@ +#ifdef NMC_HAVE_GPU + #include <cuda_runtime.h> +#endif + +namespace nest { +namespace mc { +namespace hw { + +#ifdef NMC_HAVE_GPU +unsigned num_gpus() { + int n; + cudaGetDeviceCount(&n); + return n; +} +#else +unsigned num_gpus() { + return 0u; +} +#endif + +} // namespace hw +} // namespace mc +} // namespace nest diff --git a/src/hardware/gpu.hpp b/src/hardware/gpu.hpp new file mode 100644 index 0000000000000000000000000000000000000000..953509f7bc96bb5257379596b4bb7ade67822fda --- /dev/null +++ b/src/hardware/gpu.hpp @@ -0,0 +1,11 @@ +#pragma once + +namespace nest { +namespace mc { +namespace hw { + +unsigned num_gpus(); + +} // namespace hw +} // namespace mc +} // namespace nest diff --git a/src/util/memory.cpp b/src/hardware/memory.cpp similarity index 94% rename from src/util/memory.cpp rename to src/hardware/memory.cpp index f6172d6e9f6675bfd8291345341e67cdefd17501..60427ac3a1b7f818dab3dad096be1ab78e112f95 100644 --- a/src/util/memory.cpp +++ b/src/hardware/memory.cpp @@ -12,7 +12,7 @@ extern "C" { namespace nest { namespace mc { -namespace util { +namespace hw { #if defined(__linux__) memory_size_type allocated_memory() { @@ -39,6 +39,6 @@ memory_size_type gpu_allocated_memory() { } #endif -} // namespace util +} // namespace hw } // namespace mc } // namespace nest diff --git a/src/util/memory.hpp b/src/hardware/memory.hpp similarity index 95% rename from src/util/memory.hpp rename to src/hardware/memory.hpp index 7dab29b80a67f3a5604a04c3a0c936cb97b43d3b..3688746b6a54e4dcc7956b58bfa32ac20aa60078 100644 --- a/src/util/memory.hpp +++ b/src/hardware/memory.hpp @@ -4,7 +4,7 @@ namespace nest { namespace mc { -namespace util { +namespace hw { // Use a signed type to store memory sizes because it can be used to store // the difference between two readings, which may be negative. @@ -21,6 +21,6 @@ memory_size_type allocated_memory(); // Returns a negative value on error, or if not using the gpu memory_size_type gpu_allocated_memory(); -} // namespace util +} // namespace hw } // namespace mc } // namespace nest diff --git a/src/util/power.cpp b/src/hardware/power.cpp similarity index 91% rename from src/util/power.cpp rename to src/hardware/power.cpp index 07037ea8051844cad00d92b8fa4cae954beb3060..3c5c9306a84b94efa62f4b3c398f97967c6639b6 100644 --- a/src/util/power.cpp +++ b/src/hardware/power.cpp @@ -4,7 +4,7 @@ namespace nest { namespace mc { -namespace util { +namespace hw { #ifdef NMC_HAVE_CRAY @@ -27,6 +27,6 @@ energy_size_type energy() { #endif -} // namespace util +} // namespace hw } // namespace mc } // namespace nest diff --git a/src/util/power.hpp b/src/hardware/power.hpp similarity index 68% rename from src/util/power.hpp rename to src/hardware/power.hpp index 7c13624795a00d40d4c5743101e46ce957f8a270..de1bebf710a93f806b84e83e59a439113d43c18c 100644 --- a/src/util/power.hpp +++ b/src/hardware/power.hpp @@ -4,14 +4,14 @@ namespace nest { namespace mc { -namespace util { +namespace hw { // Energy in Joules (J) using energy_size_type = std::uint64_t; -// Returns negative value if unable to read energy +// Returns energy_size_type(-1) if unable to read energy energy_size_type energy(); -} // namespace util +} // namespace hw } // namespace mc } // namespace nest diff --git a/src/profiling/memory_meter.cpp b/src/profiling/memory_meter.cpp index 5d894167b3014461596188b66f5c09ea9a7bfe71..a502255b913c1f3422e3e696553e8dae594af834 100644 --- a/src/profiling/memory_meter.cpp +++ b/src/profiling/memory_meter.cpp @@ -2,8 +2,10 @@ #include <vector> #include <util/config.hpp> +#include <hardware/memory.hpp> #include "memory_meter.hpp" +#include "meter.hpp" namespace nest { namespace mc { @@ -15,7 +17,7 @@ namespace util { class memory_meter: public meter { protected: - std::vector<memory_size_type> readings_; + std::vector<hw::memory_size_type> readings_; public: std::string name() override { @@ -27,7 +29,7 @@ public: } void take_reading() override { - readings_.push_back(allocated_memory()); + readings_.push_back(hw::allocated_memory()); } std::vector<double> measurements() override { @@ -61,7 +63,7 @@ public: } void take_reading() override { - readings_.push_back(gpu_allocated_memory()); + readings_.push_back(hw::gpu_allocated_memory()); } }; diff --git a/src/profiling/memory_meter.hpp b/src/profiling/memory_meter.hpp index d19e1b84becd1451ee95efaa96915aca9c7a17e7..cf9d71529baaf19bcc4a93670043567e797a4d1f 100644 --- a/src/profiling/memory_meter.hpp +++ b/src/profiling/memory_meter.hpp @@ -1,10 +1,5 @@ #pragma once -#include <string> -#include <vector> - -#include <util/memory.hpp> - #include "meter.hpp" namespace nest { diff --git a/src/profiling/power_meter.cpp b/src/profiling/power_meter.cpp index 2601f114e0ba1037ecd140d00fd8d2575af858f8..f4cbd2b9e9208b3f24d2cf858a1e95ba182b392a 100644 --- a/src/profiling/power_meter.cpp +++ b/src/profiling/power_meter.cpp @@ -1,16 +1,17 @@ #include <string> #include <vector> -#include <util/config.hpp> +#include "meter.hpp" -#include "power_meter.hpp" +#include <util/config.hpp> +#include <hardware/power.hpp> namespace nest { namespace mc { namespace util { class power_meter: public meter { - std::vector<energy_size_type> readings_; + std::vector<hw::energy_size_type> readings_; public: std::string name() override { @@ -32,7 +33,7 @@ public: } void take_reading() override { - readings_.push_back(energy()); + readings_.push_back(hw::energy()); } }; diff --git a/src/profiling/power_meter.hpp b/src/profiling/power_meter.hpp index 40682acb5fd4816b6cdf54abaa0213fd4b19c482..7c54e15c93aee945f1eb62a6e34fdcab6c72f01f 100644 --- a/src/profiling/power_meter.hpp +++ b/src/profiling/power_meter.hpp @@ -1,10 +1,5 @@ #pragma once -#include <string> -#include <vector> - -#include <util/power.hpp> - #include "meter.hpp" namespace nest { diff --git a/src/threading/cthread.cpp b/src/threading/cthread.cpp index 399dc635eb81a8f5b71e2d9e13c6de8b0d1c9787..0495ffd51cfb1bf5565ad255ea247db9e19f475a 100644 --- a/src/threading/cthread.cpp +++ b/src/threading/cthread.cpp @@ -5,9 +5,10 @@ #include <regex> #include "cthread.hpp" -#include "affinity.hpp" +#include "threading.hpp" using namespace nest::mc::threading::impl; +using namespace nest::mc; // RAII owner for a task in flight struct task_pool::run_task { @@ -133,55 +134,8 @@ void task_pool::wait(task_group* g) { run_tasks_while(g); } -[[noreturn]] -static void terminate(std::string msg) { - std::cerr << "NMC_NUM_THREADS_ERROR: " << msg << std::endl; - std::terminate(); -} - -// should check string, throw exception on missing or badly formed -static size_t global_get_num_threads() { - const char* str; - - // select variable to use: - // If NMC_NUM_THREADS_VAR is set, use $NMC_NUM_THREADS_VAR - // else if NMC_NUM_THREAD set, use it - // else if OMP_NUM_THREADS set, use it - if (auto nthreads_var_name = std::getenv("NMC_NUM_THREADS_VAR")) { - str = std::getenv(nthreads_var_name); - } - else if (! (str = std::getenv("NMC_NUM_THREADS"))) { - str = std::getenv("OMP_NUM_THREADS"); - } - - // If the selected var is unset set the number of threads to - // the hint given by the standard library - if (!str) { - unsigned nthreads = nest::mc::threading::count_available_cores(); - if (nthreads==0u) { - terminate( - "The number of threads was not set by the user, and I am unable " - "to determine a sane default number of threads on this system. " - "Use the NMC_NUM_THREADS environment variable to explicitly " - "set the number of threads."); - } - return nthreads; - } - - auto nthreads = std::strtoul(str, nullptr, 10); - - // check that the environment variable string describes a non-negative integer - if (nthreads==0 || errno==ERANGE || - !std::regex_match(str, std::regex("\\s*\\d*[1-9]\\d*\\s*"))) - { - terminate("The requested number of threads \""+std::string(str) - +"\" is not a reasonable positive integer"); - } - - return nthreads; -} - task_pool& task_pool::get_global_task_pool() { - static task_pool global_task_pool{global_get_num_threads()}; + auto num_threads = threading::num_threads(); + static task_pool global_task_pool(num_threads); return global_task_pool; } diff --git a/src/threading/threading.cpp b/src/threading/threading.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7f13a42a57f2e8b42769440e961f873f993114cb --- /dev/null +++ b/src/threading/threading.cpp @@ -0,0 +1,85 @@ +#include <cstdlib> +#include <exception> +#include <regex> +#include <string> + +#include <util/optional.hpp> +#include <hardware/affinity.hpp> + +#include "threading.hpp" + +namespace nest { +namespace mc { +namespace threading { + +// Test environment variables for user-specified count of threads. +// +// NMC_NUM_THREADS is used if set, otherwise OMP_NUM_THREADS is used. +// +// If neither variable is set, returns no value. +// +// Valid values for the environment variable are: +// 0 : NestMC is responsible for picking the number of threads. +// >0: The number of threads to use. +// +// Throws std::runtime_error: +// NMC_NUM_THREADS or OMP_NUM_THREADS is set with invalid value. +util::optional<size_t> get_env_num_threads() { + const char* str; + + // select variable to use: + // If NMC_NUM_THREADS_VAR is set, use $NMC_NUM_THREADS_VAR + // else if NMC_NUM_THREAD set, use it + // else if OMP_NUM_THREADS set, use it + if (auto nthreads_var_name = std::getenv("NMC_NUM_THREADS_VAR")) { + str = std::getenv(nthreads_var_name); + } + else if (! (str = std::getenv("NMC_NUM_THREADS"))) { + str = std::getenv("OMP_NUM_THREADS"); + } + + // If the selected var is unset set the number of threads to + // the hint given by the standard library + if (!str) { + return util::nothing; + } + + auto nthreads = std::strtoul(str, nullptr, 10); + + // check that the environment variable string describes a non-negative integer + if (errno==ERANGE || + !std::regex_match(str, std::regex("\\s*\\d*[0-9]\\d*\\s*"))) + { + throw std::runtime_error("The requested number of threads \"" + +std::string(str)+"\" is not a valid value\n"); + } + + return nthreads; +} + +size_t num_threads_init() { + auto env_threads = get_env_num_threads(); + if (!env_threads || *env_threads==0u) { + auto detect_threads = hw::num_cores(); + return detect_threads? *detect_threads: 1; + } + return *env_threads; +} + +// Returns the number of threads used by the threading back end. +// Throws: +// std::runtime_error if an invalid environment variable was set for the +// number of threads. +size_t num_threads() { + // TODO: this is a bit of a hack until we have user-configurable threading. +#if defined(NMC_HAVE_SERIAL) + return 1; +#else + static size_t num_threads_cached = num_threads_init(); + return num_threads_cached; +#endif +} + +} // namespace threading +} // namespace mc +} // namespace nest diff --git a/src/threading/threading.hpp b/src/threading/threading.hpp index 6ed393fde98fbc9d379d6c58d1e6382df31e9bdc..493b5260dc2788ef71b7b3c27acbb3eec163578e 100644 --- a/src/threading/threading.hpp +++ b/src/threading/threading.hpp @@ -1,5 +1,32 @@ #pragma once +#include <util/optional.hpp> + +namespace nest { +namespace mc { +namespace threading { + +// Test environment variables for user-specified count of threads. +// Potential environment variables are tested in this order: +// 1. use the environment variable specified by NMC_NUM_THREADS_VAR +// 2. use NMC_NUM_THREADS +// 3. use OMP_NUM_THREADS +// 4. If no variable is set, returns no value. +// +// Valid values for the environment variable are: +// 0 : NestMC is responsible for picking the number of threads. +// >0 : The number of threads to use. +// +// Throws std::runtime_error: +// Environment variable is set with invalid value. +util::optional<size_t> get_env_num_threads(); + +size_t num_threads(); + +} // namespace threading +} // namespace mc +} // namespace nest + #if defined(NMC_HAVE_TBB) #include "tbb.hpp" #elif defined(NMC_HAVE_CTHREAD) @@ -8,4 +35,3 @@ #define NMC_HAVE_SERIAL #include "serial.hpp" #endif -