diff --git a/src/libexpr-tests/bench-main.cc b/src/libexpr-tests/bench-main.cc new file mode 100644 index 000000000..13dca6b4e --- /dev/null +++ b/src/libexpr-tests/bench-main.cc @@ -0,0 +1,14 @@ +#include + +#include "nix/expr/eval-gc.hh" +#include "nix/store/globals.hh" + +int main(int argc, char ** argv) +{ + nix::initLibStore(false); + nix::initGC(); + + ::benchmark::Initialize(&argc, argv); + ::benchmark::RunSpecifiedBenchmarks(); + return 0; +} diff --git a/src/libexpr-tests/dynamic-attrs-bench.cc b/src/libexpr-tests/dynamic-attrs-bench.cc new file mode 100644 index 000000000..553d5f9a8 --- /dev/null +++ b/src/libexpr-tests/dynamic-attrs-bench.cc @@ -0,0 +1,55 @@ +#include + +#include "nix/expr/eval.hh" +#include "nix/expr/eval-settings.hh" +#include "nix/fetchers/fetch-settings.hh" +#include "nix/store/store-open.hh" + +using namespace nix; + +static std::string mkDynamicAttrsExpr(size_t attrCount) +{ + std::string res; + res.reserve(attrCount * 24); + res += "{ "; + for (size_t i = 0; i < attrCount; ++i) { + res += "${\"a"; + res += std::to_string(i); + res += "\"} = "; + res += std::to_string(i); + res += "; "; + } + res += "}"; + return res; +} + +static void BM_EvalDynamicAttrs(benchmark::State & state) +{ + const auto attrCount = static_cast(state.range(0)); + const auto exprStr = mkDynamicAttrsExpr(attrCount); + + for (auto _ : state) { + state.PauseTiming(); + + auto store = openStore("dummy://"); + fetchers::Settings fetchSettings{}; + bool readOnlyMode = true; + EvalSettings evalSettings{readOnlyMode}; + evalSettings.nixPath = {}; + + EvalState st({}, store, fetchSettings, evalSettings, nullptr); + Expr * expr = st.parseExprFromString(exprStr, st.rootPath(CanonPath::root)); + + Value v; + + state.ResumeTiming(); + + st.eval(expr, v); + st.forceValue(v, noPos); + benchmark::DoNotOptimize(v); + } + + state.SetItemsProcessed(state.iterations() * attrCount); +} + +BENCHMARK(BM_EvalDynamicAttrs)->Arg(100)->Arg(500)->Arg(2'000); diff --git a/src/libexpr-tests/get-drvs-bench.cc b/src/libexpr-tests/get-drvs-bench.cc new file mode 100644 index 000000000..c6a6fc32c --- /dev/null +++ b/src/libexpr-tests/get-drvs-bench.cc @@ -0,0 +1,64 @@ +#include + +#include "nix/expr/get-drvs.hh" +#include "nix/expr/eval-settings.hh" +#include "nix/fetchers/fetch-settings.hh" +#include "nix/store/store-open.hh" +#include "nix/util/fmt.hh" + +using namespace nix; + +namespace { + +struct GetDerivationsEnv +{ + ref store = openStore("dummy://"); + fetchers::Settings fetchSettings{}; + bool readOnlyMode = true; + EvalSettings evalSettings{readOnlyMode}; + EvalState state; + + Bindings * autoArgs = nullptr; + Value attrsValue; + + explicit GetDerivationsEnv(size_t attrCount) + : evalSettings([&]() { + EvalSettings settings{readOnlyMode}; + settings.nixPath = {}; + return settings; + }()) + , state({}, store, fetchSettings, evalSettings, nullptr) + { + autoArgs = state.buildBindings(0).finish(); + + auto attrs = state.buildBindings(attrCount); + + for (size_t i = 0; i < attrCount; ++i) { + auto name = fmt("pkg%|1$06d|", i); + auto sym = state.symbols.create(name); + auto & v = attrs.alloc(sym); + v.mkInt(i); + } + + attrsValue.mkAttrs(attrs.finish()); + } +}; + +} // namespace + +static void BM_GetDerivationsAttrScan(benchmark::State & state) +{ + const auto attrCount = static_cast(state.range(0)); + GetDerivationsEnv env(attrCount); + + for (auto _ : state) { + PackageInfos drvs; + getDerivations( + env.state, env.attrsValue, /*pathPrefix=*/"", *env.autoArgs, drvs, /*ignoreAssertionFailures=*/true); + benchmark::DoNotOptimize(drvs.size()); + } + + state.SetItemsProcessed(state.iterations() * attrCount); +} + +BENCHMARK(BM_GetDerivationsAttrScan)->Arg(1'000)->Arg(5'000)->Arg(10'000); diff --git a/src/libexpr-tests/meson.build b/src/libexpr-tests/meson.build index c5dafe0de..c5b72851d 100644 --- a/src/libexpr-tests/meson.build +++ b/src/libexpr-tests/meson.build @@ -87,3 +87,33 @@ test( }, protocol : 'gtest', ) + +# Build benchmarks if enabled +if get_option('benchmarks') + gbenchmark = dependency('benchmark', required : true) + + benchmark_sources = files( + 'bench-main.cc', + 'dynamic-attrs-bench.cc', + 'get-drvs-bench.cc', + 'regex-cache-bench.cc', + ) + + benchmark_exe = executable( + 'nix-expr-benchmarks', + benchmark_sources, + config_priv_h, + dependencies : deps_private_subproject + deps_private + deps_other + [ + gbenchmark, + ], + include_directories : include_dirs, + link_args : linker_export_flags, + install : true, + cpp_pch : do_pch ? [ 'pch/precompiled-headers.hh' ] : [], + ) + + benchmark( + 'nix-expr-benchmarks', + benchmark_exe, + ) +endif diff --git a/src/libexpr-tests/meson.options b/src/libexpr-tests/meson.options new file mode 100644 index 000000000..2b3c1af60 --- /dev/null +++ b/src/libexpr-tests/meson.options @@ -0,0 +1,9 @@ +# vim: filetype=meson + +option( + 'benchmarks', + type : 'boolean', + value : false, + description : 'Build benchmarks (requires gbenchmark)', + yield : true, +) diff --git a/src/libexpr-tests/package.nix b/src/libexpr-tests/package.nix index 51d52e935..3af1f52d3 100644 --- a/src/libexpr-tests/package.nix +++ b/src/libexpr-tests/package.nix @@ -33,7 +33,7 @@ mkMesonExecutable (finalAttrs: { ../../.version ./.version ./meson.build - # ./meson.options + ./meson.options (fileset.fileFilter (file: file.hasExt "cc") ./.) (fileset.fileFilter (file: file.hasExt "hh") ./.) ]; diff --git a/src/libexpr-tests/regex-cache-bench.cc b/src/libexpr-tests/regex-cache-bench.cc new file mode 100644 index 000000000..36a350e2e --- /dev/null +++ b/src/libexpr-tests/regex-cache-bench.cc @@ -0,0 +1,45 @@ +#include + +#include "nix/expr/eval.hh" +#include "nix/expr/eval-settings.hh" +#include "nix/fetchers/fetch-settings.hh" +#include "nix/store/store-open.hh" + +using namespace nix; + +static void BM_EvalManyBuiltinsMatchSameRegex(benchmark::State & state) +{ + static constexpr int iterations = 5'000; + + static constexpr std::string_view exprStr = + "builtins.foldl' " + "(acc: _: acc + builtins.length (builtins.match \"a\" \"a\")) " + "0 " + "(builtins.genList (x: x) " + "5000)"; + + for (auto _ : state) { + state.PauseTiming(); + + auto store = openStore("dummy://"); + fetchers::Settings fetchSettings{}; + bool readOnlyMode = true; + EvalSettings evalSettings{readOnlyMode}; + evalSettings.nixPath = {}; + + EvalState st({}, store, fetchSettings, evalSettings, nullptr); + Expr * expr = st.parseExprFromString(std::string(exprStr), st.rootPath(CanonPath::root)); + + Value v; + + state.ResumeTiming(); + + st.eval(expr, v); + st.forceValue(v, noPos); + benchmark::DoNotOptimize(v); + } + + state.SetItemsProcessed(state.iterations() * iterations); +} + +BENCHMARK(BM_EvalManyBuiltinsMatchSameRegex);