diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 21cfa0d..3f65273 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -28,4 +28,5 @@ FetchContent_MakeAvailable(nanobench) # gersemi: on add_subdirectory(self_contained) +add_subdirectory(compilation_time) add_subdirectory(src) diff --git a/tests/compilation_time/CMakeLists.txt b/tests/compilation_time/CMakeLists.txt new file mode 100644 index 0000000..f594193 --- /dev/null +++ b/tests/compilation_time/CMakeLists.txt @@ -0,0 +1,41 @@ +# +# Copyright (C) 2020-2023 Krylov Yaroslav. +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +# +add_executable(ureact_compilation_time_benchmarks EXCLUDE_FROM_ALL) + +target_sources( + ureact_compilation_time_benchmarks # + PRIVATE compiler_args.cpp std_headers_include_time.cpp +) + +target_link_libraries( + ureact_compilation_time_benchmarks + PRIVATE ureact::ureact Catch2::Catch2WithMain nanobench::nanobench +) + +target_compile_options( + ureact_compilation_time_benchmarks + PRIVATE ${UREACT_WARNING_OPTION} +) + +get_filename_component( + UREACT_INCLUDE_PATH + "${CMAKE_CURRENT_SOURCE_DIR}/../../include" + ABSOLUTE +) +get_filename_component( + TEST_SAMPLES_PATH + "${CMAKE_CURRENT_SOURCE_DIR}/test_samples" + ABSOLUTE +) + +target_compile_definitions( + ureact_compilation_time_benchmarks + PRIVATE + UREACT_INCLUDE_PATH="${UREACT_INCLUDE_PATH}" + TEST_SAMPLES_PATH="${TEST_SAMPLES_PATH}" +) diff --git a/tests/compilation_time/compiler_args.cpp b/tests/compilation_time/compiler_args.cpp new file mode 100644 index 0000000..031af9c --- /dev/null +++ b/tests/compilation_time/compiler_args.cpp @@ -0,0 +1,199 @@ +// +// Copyright (C) 2020-2023 Krylov Yaroslav. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +#include "compiler_args.hpp" + +#include +#include +#include + +#include + +std::ostream& operator<<( std::ostream& os, const BuildConfiguration value ) +{ + switch( value ) + { + case BuildConfiguration::Default: os << "Default"; break; + case BuildConfiguration::Release: os << "Release"; break; + case BuildConfiguration::Debug: os << "Debug"; break; + } + return os; +} + +std::ostream& operator<<( std::ostream& os, const CompilerName value ) +{ + switch( value ) + { + case CompilerName::clang: os << "clang++"; break; + case CompilerName::gcc: os << "g++"; break; + } + return os; +} + +std::ostream& operator<<( std::ostream& os, const Compiler value ) +{ + return os << value.name << '-' << value.version; +} + +std::string to_string( const Compiler value ) +{ + std::ostringstream ss; + ss << value; + return ss.str(); +} + +std::string CompilerArgs::build() const +{ + assert( isValid() ); + std::ostringstream ss; + ss << m_compiler.value() << ' '; + if( m_standard.has_value() ) + ss << "-std=" << m_standard.value() << ' '; + switch( m_configuration.value_or( BuildConfiguration::Default ) ) + { + case BuildConfiguration::Debug: ss << "-O0 -g" << ' '; break; + case BuildConfiguration::Release: ss << "-O3" << ' '; break; + case BuildConfiguration::Default: break; + } + ss << "-I" << UREACT_INCLUDE_PATH << ' '; + for( const auto& def : m_definitions ) + ss << "-D" << def << ' '; + if( m_stdlib.has_value() ) + ss << "-stdlib=" << m_stdlib.value() << ' '; + ss << TEST_SAMPLES_PATH << '/' << m_source.value(); + return ss.str(); +} + +std::string CompilerArgs::get_name() const +{ + assert( m_compiler.has_value() ); + std::ostringstream ss; + ss << m_compiler.value(); + if( m_standard ) + ss << ' ' << m_standard.value(); + if( m_stdlib ) + ss << ' ' << m_stdlib.value(); + if( m_configuration ) + ss << ' ' << m_configuration.value(); + return ss.str(); +} + +std::vector generateCompilerArgs( const std::vector& compilers, + const std::vector& configurations, + const int minimalStandard ) +{ + constexpr bool VERBOSE_ERRORS = false; + + std::vector result; + + for( const auto& compiler : compilers ) + { + if( std::system( ( CompilerArgs{} // + .compiler( to_string( compiler ) ) + .source( "minimal.cpp" ) + .build() + + " > /dev/null 2>&1" ) + .c_str() ) + != 0 ) + { + if constexpr( VERBOSE_ERRORS ) + { + std::cerr << "skipping (" << to_string( compiler ) + << ") because it can't compile the simple program" << std::endl; + } + continue; + } + + const auto stdlibs = [compiler]() -> std::vector { + if( compiler.name == clang ) + return { "libstdc++", "libc++" }; + else + return { "" }; + }(); + + const auto standards = [compiler, minimalStandard]() -> std::vector { + std::vector result; + if( minimalStandard <= 11 ) + result.emplace_back( "c++11" ); + if( minimalStandard <= 14 ) + result.emplace_back( "c++14" ); + if( minimalStandard <= 17 ) + result.emplace_back( "c++17" ); + if( minimalStandard <= 20 ) + { + if( compiler.name == gcc && compiler.version == 9 ) + result.emplace_back( "c++2a" ); + else + result.emplace_back( "c++20" ); + } + return result; + }(); + + for( const auto& configuration : configurations ) + { + for( const auto& stdlib : stdlibs ) + { + if( !stdlib.empty() ) + { + if( const auto args = CompilerArgs{} // + .compiler( to_string( compiler ) ) + .stdlib( stdlib ) + .source( "minimal.cpp" ); + std::system( ( args.build() + " > /dev/null 2>&1" ).c_str() ) != 0 ) + { + if constexpr( VERBOSE_ERRORS ) + { + std::cerr << "skipping (" << args.get_name() + << ") because it can't compile the simple program" + << std::endl; + } + continue; + } + } + + for( const auto& standard : standards ) + { + auto compilerArgs = CompilerArgs{} // + .compiler( to_string( compiler ) ) + .standard( standard ) + .configuration( configuration ); + if( !stdlib.empty() ) + compilerArgs.stdlib( stdlib ); + + if( std::system( ( CompilerArgs{ compilerArgs } // + .source( "minimal.cpp" ) + .build() + + " > /dev/null 2>&1" ) + .c_str() ) + == 0 ) + { + result.push_back( compilerArgs ); + } + else + { + if constexpr( VERBOSE_ERRORS ) + { + std::cerr << "skipping (" << compilerArgs.get_name() + << ") because it can't compile the simple program" + << std::endl; + } + } + } + } + } + } + + return result; +} + +void perform_test( + ankerl::nanobench::Bench& bench, const std::string& name, const CompilerArgs& compilerArgs ) +{ + bench.run( name, [&] { + std::system( compilerArgs.build().c_str() ); // + } ); +} diff --git a/tests/compilation_time/compiler_args.hpp b/tests/compilation_time/compiler_args.hpp new file mode 100644 index 0000000..82dfe6a --- /dev/null +++ b/tests/compilation_time/compiler_args.hpp @@ -0,0 +1,112 @@ +// +// Copyright (C) 2020-2023 Krylov Yaroslav. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include +#include +#include +#include + +enum class BuildConfiguration +{ + Default, + Release, + Debug +}; + +std::ostream& operator<<( std::ostream& os, const BuildConfiguration value ); + +enum class CompilerName +{ + clang, + gcc +}; + +constexpr inline auto clang = CompilerName::clang; +constexpr inline auto gcc = CompilerName::gcc; + +std::ostream& operator<<( std::ostream& os, const CompilerName value ); + +struct Compiler +{ + CompilerName name; + int version; +}; + +std::ostream& operator<<( std::ostream& os, const Compiler value ); + +std::string to_string( const Compiler value ); + +class CompilerArgs +{ +public: + CompilerArgs& compiler( const std::string& value ) + { + m_compiler = value; + return *this; + } + + CompilerArgs& standard( const std::string& value ) + { + m_standard = value; + return *this; + } + + CompilerArgs& configuration( const BuildConfiguration value ) + { + m_configuration = value; + return *this; + } + + CompilerArgs& source( const std::string& value ) + { + m_source = value; + return *this; + } + + CompilerArgs& definition( const std::string& value ) + { + m_definitions.push_back( value ); + return *this; + } + + CompilerArgs& stdlib( const std::string& value ) + { + m_stdlib = value; + return *this; + } + + [[nodiscard]] bool isValid() const + { + return m_compiler.has_value() && m_source.has_value(); + } + + [[nodiscard]] std::string build() const; + + [[nodiscard]] std::string get_name() const; + +private: + std::optional m_compiler; + std::optional m_standard; + std::optional m_configuration; + std::vector m_definitions; + std::optional m_stdlib; + std::optional m_source; +}; + +std::vector generateCompilerArgs( const std::vector& compilers, + const std::vector& configurations, + const int minimalStandard ); + +namespace ankerl::nanobench +{ +class Bench; +} + +void perform_test( + ankerl::nanobench::Bench& bench, const std::string& name, const CompilerArgs& compilerArgs ); diff --git a/tests/compilation_time/std_headers_include_time.cpp b/tests/compilation_time/std_headers_include_time.cpp new file mode 100644 index 0000000..0b1a6e2 --- /dev/null +++ b/tests/compilation_time/std_headers_include_time.cpp @@ -0,0 +1,79 @@ +// +// Copyright (C) 2020-2023 Krylov Yaroslav. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +#include +#include +#include +#include +#include + +#include +#include + +#include "compiler_args.hpp" + +TEST_CASE( "std headers include time" ) +{ + using namespace std::chrono_literals; + + ankerl::nanobench::Bench bench; + bench.title( "std headers include time" ); + bench.relative( true ); + bench.performanceCounters( false ); + bench.timeUnit( 1ms, "ms" ); + bench.minEpochIterations( 1 ); + + std::vector compilers = { + Compiler{ clang, 11 }, + Compiler{ clang, 12 }, + Compiler{ clang, 13 }, + Compiler{ clang, 14 }, + Compiler{ gcc, 9 }, + Compiler{ gcc, 10 }, + Compiler{ gcc, 11 }, + Compiler{ gcc, 12 }, + }; + + const auto args = generateCompilerArgs( compilers, { BuildConfiguration::Default }, 17 ); + for( const auto& compilerArgs : args ) + { + const auto compilerString = compilerArgs.get_name(); + + perform_test( bench, + std::string{ "baseline" } + " (" + compilerString + ')', + CompilerArgs{ compilerArgs } // + .source( "minimal.cpp" ) + // + ); + + for( const std::string& type : { + "", + "vector", + "deque", + "memory", + "iterator", + "functional", + "algorithm", + "all", + } ) + { + std::string name = "std"; + auto stdCompilerArgs = CompilerArgs{ compilerArgs } // + .source( "include_std.cpp" ); + if( !type.empty() ) + { + name += '+' + type; + std::string definition = "INCLUDE_" + type; + std::transform( + definition.begin(), definition.end(), definition.begin(), ::toupper ); + stdCompilerArgs.definition( definition ); + } + + perform_test( bench, name + " (" + compilerString + ')', stdCompilerArgs ); + } + } +} diff --git a/tests/compilation_time/test_samples/include_std.cpp b/tests/compilation_time/test_samples/include_std.cpp new file mode 100644 index 0000000..429fc1f --- /dev/null +++ b/tests/compilation_time/test_samples/include_std.cpp @@ -0,0 +1,42 @@ +// +// Copyright (C) 2020-2023 Krylov Yaroslav. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +// List of headers that are used on ureact +// headers that I assume have small impact on the compilation time are ouside of +// conditional compilation +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined( INCLUDE_VECTOR ) or defined( INCLUDE_ALL ) +# include +#endif +#if defined( INCLUDE_DEQUE ) or defined( INCLUDE_ALL ) +# include +#endif +#if defined( INCLUDE_MEMORY ) or defined( INCLUDE_ALL ) +# include +#endif +#if defined( INCLUDE_ITERATOR ) or defined( INCLUDE_ALL ) +# include +#endif +#if defined( INCLUDE_FUNCTIONAL ) or defined( INCLUDE_ALL ) +# include +#endif +#if defined( INCLUDE_ALGORITHM ) or defined( INCLUDE_ALL ) +# include +#endif + +int main() +{} diff --git a/tests/compilation_time/test_samples/minimal.cpp b/tests/compilation_time/test_samples/minimal.cpp new file mode 100644 index 0000000..d0b9987 --- /dev/null +++ b/tests/compilation_time/test_samples/minimal.cpp @@ -0,0 +1,11 @@ +// +// Copyright (C) 2020-2023 Krylov Yaroslav. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +#include // single include to ensure std lib is here + +int main() +{}