diff --git a/CMakeLists.txt b/CMakeLists.txt index cee0375..b802b2e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -295,3 +295,8 @@ add_subdirectory(exposed) if(ENABLE_TESTS) add_subdirectory(test) endif() +if(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") + add_subdirectory(bench) +else() + message(STATUS "Benchmarking disabled in debug builds.") +endif() diff --git a/bench/CMakeLists.txt b/bench/CMakeLists.txt new file mode 100644 index 0000000..9531820 --- /dev/null +++ b/bench/CMakeLists.txt @@ -0,0 +1,12 @@ +# benchmark runner + +foreach(pkg_config_lib CAIRO) + include_directories(${${pkg_config_lib}_INCLUDE_DIRS}) + link_directories(${${pkg_config_lib}_LIBRARY_DIRS}) +endforeach() + +add_executable(solvespace_benchmark + harness.cpp) + +target_link_libraries(solvespace_benchmark + solvespace_headless) diff --git a/bench/harness.cpp b/bench/harness.cpp new file mode 100644 index 0000000..bdae469 --- /dev/null +++ b/bench/harness.cpp @@ -0,0 +1,135 @@ +//----------------------------------------------------------------------------- +// Our harness for running benchmarks. +// +// Copyright 2016 whitequark +//----------------------------------------------------------------------------- +#include "solvespace.h" +#if defined(WIN32) +#include +#else +#include +#endif + +namespace SolveSpace { + // These are defined in headless.cpp, and aren't exposed in solvespace.h. + extern std::string resourceDir; +} + +static std::string ResourceRoot() { + static std::string rootDir; + if(!rootDir.empty()) return rootDir; + + // No especially good way to do this, so let's assume somewhere up from + // the current directory there's our repository, with CMakeLists.txt, and + // pivot from there. +#if defined(WIN32) + wchar_t currentDirW[MAX_PATH]; + GetCurrentDirectoryW(MAX_PATH, currentDirW); + rootDir = Narrow(currentDirW); +#else + rootDir = "."; +#endif + + // We're never more than four levels deep. + for(size_t i = 0; i < 4; i++) { + std::string listsPath = rootDir; + listsPath += PATH_SEP; + listsPath += "CMakeLists.txt"; + FILE *f = ssfopen(listsPath, "r"); + if(f) { + fclose(f); + rootDir += PATH_SEP; + rootDir += "res"; + return rootDir; + } + + if(rootDir[0] == '.') { + rootDir += PATH_SEP; + rootDir += ".."; + } else { + rootDir.erase(rootDir.rfind(PATH_SEP)); + } + } + + ssassert(false, "Couldn't locate repository root"); +} + +static bool RunBenchmark(std::function setupFn, + std::function benchFn, + std::function teardownFn, + size_t minIter = 5, double minTime = 5.0) { + // Warmup + setupFn(); + if(!benchFn()) { + fprintf(stderr, "Benchmark failed\n"); + return false; + } + teardownFn(); + + // Benchmark + size_t iter = 0; + double time = 0.0; + while(iter < minIter || time < minTime) { + setupFn(); + auto testStartTime = std::chrono::steady_clock::now(); + benchFn(); + auto testEndTime = std::chrono::steady_clock::now(); + teardownFn(); + + std::chrono::duration testTime = testEndTime - testStartTime; + time += testTime.count(); + iter += 1; + } + + // Report + fprintf(stdout, "Iterations: %zd\n", iter); + fprintf(stdout, "Time: %.3f s\n", time); + fprintf(stdout, "Per iter.: %.3f s\n", time / (double)iter); + + return true; +} + +int main(int argc, char **argv) { +#if defined(_MSC_VER) + _set_abort_behavior(0, _WRITE_ABORT_MSG); +#endif +#if defined(WIN32) + InitHeaps(); +#endif + + resourceDir = ResourceRoot(); + + std::string mode, filename; + if(argc == 3) { + mode = argv[1]; + filename = argv[2]; + } else { + fprintf(stderr, "Usage: %s [mode] [filename]\n", argv[0]); + fprintf(stderr, "Mode can be one of: load.\n"); + return 1; + } + + bool result = false; + if(mode == "load") { + result = RunBenchmark( + [] { + SS.Init(); + }, + [&] { + if(!SS.LoadFromFile(filename)) + return false; + if(!SS.ReloadAllImported(/*canCancel=*/false)) + return false; + SS.AfterNewFile(); + return true; + }, + [] { + SK.Clear(); + SS.Clear(); + }); + } else { + fprintf(stderr, "Unknown mode \"%s\"\n", mode.c_str()); + } + + return (result == true ? 0 : 1); +}