Compile time benchmarks

The benchmarks are done with this script using CMake. There are 2 benchmarking scenarios:

Compilers used:

  • WINDOWS: Microsoft Visual Studio Community 2015 - Version 14.0.25431.01 Update 3
  • WINDOWS: gcc 6.2.0 (x86_64-posix-seh-rev0, Built by MinGW-W64 project)
  • LINUX: gcc 5.2.1 20151010 (Ubuntu 5.2.1-22ubuntu2)
  • LINUX: clang 3.6.2-1 (tags/RELEASE_362/final) (based on LLVM 3.6.2) Ubuntu - Target: x86_64-pc-linux-gnu

Environment used (Intel i7 3770k, 16g RAM)

  • Windows 7 - on an SSD
  • Ubuntu 15.10 in a VirtualBox VM - on a HDD

doctest version: 1.1.0 (released on 2016.09.20)

Catch version: 1.5.6 (released on 2016.06.09)

Cost of including the header

This is a benchmark that is relevant only to single header and header only frameworks - like doctest and Catch.

The script generates 201 source files and in 200 of them makes a function in the form of int f135() { return 135; } and in main.cpp it forward declares all the 200 such dummy functions and accumulates their result to return from the main() function. This is done to ensure that all source files are built and that the linker doesn't remove/optimize anything.

  • baseline - how much time the source files need for a single threaded build with msbuild/make

  • + implement - only in main.cpp the header is included with a #define before it so the test runner gets implemented:

#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include "doctest.h" ```

  • + header everywhere - the framework header is also included in all the other source files
  • + disabled - doctest specific - only this framework can remove everything related to it from the binary
doctestbaseline+ implement+ header everywhere+ disabled
MSVC Debug5.97.18.37.0
MSVC Release5.46.98.76.5
MinGW GCC Debug9.411.714.411.1
MinGW GCC Release9.612.314.911.4
Linux GCC Debug6.37.110.27.4
Linux GCC Release6.58.410.87.8
Linux Clang Debug6.97.610.68.2
Linux Clang Release7.28.411.48.4
Catchbaseline+ implement+ header everywhere
MSVC Debug5.98.5102
MSVC Release5.410.396
MinGW GCC Debug9.424.5125
MinGW GCC Release9.618.4113
Linux GCC Debug6.310.459
Linux GCC Release6.514.164
Linux Clang Debug6.99.864
Linux Clang Release7.212.867

Conclusion

doctest

  • instantiating the test runner in one source file costs ~1.5 seconds implement - baseline
  • the inclusion of doctest.h in one source file costs below 9ms (header_everywhere - implement) / 200
  • including the library everywhere - but everything disabled - costs less than 2 seconds disabled - baseline

Catch

  • instantiating the test runner in one source file costs ~5 second implement - baseline (~12 seconds for MinGW-w64)
  • the inclusion of catch.hpp in one source file costs around 430ms (header_everywhere - implement) / 200 (below 280ms for MinGW-w64 which is really odd)

So if doctest.h costs 8ms and catch.hpp costs 430ms on MSVC - then the doctest header is >> 54 << times lighter!


The results are in seconds and are in no way intended to bash Catch - the doctest framework wouldn't exist without it.

The reason the doctest header is so light on compile times is because it forward declares everything and doesn't drag any headers in the source files (except for the source file where the test runner gets implemented). This was a key design decision.

Cost of an assertion macro

The script generates 11 .cpp files and in 10 of them makes 50 test cases with 100 asserts in them (of the form CHECK(a==b) where a and b are always the same int variables) - 50k asserts! The testing framework gets implemented in main.cpp.

  • baseline - how much time a single threaded build takes with the header included everywhere - no test cases or asserts!
  • CHECK(a==b) - will add CHECK() asserts which decompose the expression with template machinery

doctest specific:

  • +disabled - all test case and assert macros will be disabled with DOCTEST_CONFIG_DISABLE
  • CHECK_EQ(a,b) - will use CHECK_EQ(a,b) instead of the expression decomposing ones
  • CHECK_EQ_FAST(a,b) - will use FAST_CHECK_EQ(a,b) instead of the expression decomposing ones
  • +faster - will add DOCTEST_CONFIG_SUPER_FAST_ASSERTS which speeds up FAST_CHECK_EQ(a,b) even more
doctestbaselineCHECK(a==b)+disabledCHECK_EQ(a,b)CHECK_EQ_FAST(a,b)+faster
MSVC Debug2.5212.216.26.74.4
MSVC Release2.6641.855635.3
MinGW GCC Debug3.2771.65229.512.2
MinGW GCC Release3.94251.92958118.6
Linux GCC Debug1.3720.94820.39.5
Linux GCC Release2.33391.32104218.3
Linux Clang Debug1.3700.94618.87.0
Linux Clang Release1.82051.11363010.8
CatchbaselineCHECK(a==b)
MSVC Debug8.434
MSVC Release9.777
MinGW GCC Debug20.5115
MinGW GCC Release15.1496
Linux GCC Debug7.3101
Linux GCC Release10.3435
Linux Clang Debug6.091
Linux Clang Release8.5159

The following table is with normal CHECK(a==b) asserts using doctest 1.0 (it didn't have any other) - released on 2016.05.22

doctest 1.0CHECK(a==b)
MSVC Debug58
MSVC Release367
MinGW GCC Debug202
MinGW GCC Release1257
Linux GCC Debug204
Linux GCC Release1090
Linux Clang Debug167
Linux Clang Release494

Conclusion

doctest 1.1:

  • compared to 1.0 it improves the compile time of it's expression decomposing CHECK(a==b) macros by roughly 3 times - making it faster rather and not slower than Catch
  • adds alternative macros of the form CHECK_EQ(a,b) with no expression decomposition - around 20% faster than CHECK(a==b)
  • adds even faster asserts like FAST_CHECK_EQ(a,b) which don't have try/catch blocks - around 30-70% faster than CHECK_EQ(a,b)
  • adds the DOCTEST_CONFIG_SUPER_FAST_ASSERTS identifier which makes the fast assertions even faster by another 35-80%
  • using the DOCTEST_CONFIG_DISABLE identifier the assertions just disappear as if they were never written

Home