1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-12-24 09:50:55 +01:00

Merge pull request #14827 from Zaczero/zaczero/libexpr

libexpr: add nix-expr-benchmarks, add regex optimizations
This commit is contained in:
Sergei Zimmerman 2025-12-19 21:24:05 +00:00 committed by GitHub
commit 132a93625b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 258 additions and 15 deletions

View file

@ -0,0 +1,14 @@
#include <benchmark/benchmark.h>
#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;
}

View file

@ -0,0 +1,55 @@
#include <benchmark/benchmark.h>
#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<size_t>(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);

View file

@ -0,0 +1,64 @@
#include <benchmark/benchmark.h>
#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> 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<size_t>(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);

View file

@ -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

View file

@ -0,0 +1,9 @@
# vim: filetype=meson
option(
'benchmarks',
type : 'boolean',
value : false,
description : 'Build benchmarks (requires gbenchmark)',
yield : true,
)

View file

@ -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") ./.)
];

View file

@ -0,0 +1,45 @@
#include <benchmark/benchmark.h>
#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);

View file

@ -367,7 +367,26 @@ static std::string addToPath(const std::string & s1, std::string_view s2)
return s1.empty() ? std::string(s2) : s1 + "." + s2;
}
static std::regex attrRegex("[A-Za-z_][A-Za-z0-9-_+]*");
static bool isAttrPathComponent(std::string_view symbol)
{
if (symbol.empty())
return false;
/* [A-Za-z_] */
unsigned char first = symbol[0];
if (!((first >= 'A' && first <= 'Z') || (first >= 'a' && first <= 'z') || first == '_'))
return false;
/* [A-Za-z0-9-_+]* */
for (unsigned char c : symbol.substr(1)) {
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_'
|| c == '+')
continue;
return false;
}
return true;
}
static void getDerivations(
EvalState & state,
@ -400,7 +419,7 @@ static void getDerivations(
std::string_view symbol{state.symbols[i->name]};
try {
debug("evaluating attribute '%1%'", symbol);
if (!std::regex_match(symbol.begin(), symbol.end(), attrRegex))
if (!isAttrPathComponent(symbol))
continue;
std::string pathPrefix2 = addToPath(pathPrefix, symbol);
if (combineChannels)

View file

@ -4709,21 +4709,28 @@ static RegisterPrimOp primop_convertHash({
struct RegexCache
{
boost::concurrent_flat_map<std::string, std::regex, StringViewHash, std::equal_to<>> cache;
std::regex get(std::string_view re)
struct Entry
{
std::regex regex;
/* No std::regex constructor overload from std::string_view, but can be constructed
from a pointer + size or an iterator range. */
ref<const std::regex> regex;
Entry(const char * s, size_t count)
: regex(make_ref<const std::regex>(s, count, std::regex::extended))
{
}
};
boost::concurrent_flat_map<std::string, Entry, StringViewHash, std::equal_to<>> cache;
ref<const std::regex> get(std::string_view re)
{
std::optional<ref<const std::regex>> regex;
cache.try_emplace_and_cvisit(
re,
/*s=*/re.data(),
/*count=*/re.size(),
std::regex::extended,
[&regex](const auto & kv) { regex = kv.second; },
[&regex](const auto & kv) { regex = kv.second; });
return regex;
[&regex](const auto & kv) { regex = kv.second.regex; },
[&regex](const auto & kv) { regex = kv.second.regex; });
return *regex;
}
};
@ -4745,7 +4752,7 @@ void prim_match(EvalState & state, const PosIdx pos, Value ** args, Value & v)
state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.match");
std::cmatch match;
if (!std::regex_match(str.begin(), str.end(), match, regex)) {
if (!std::regex_match(str.begin(), str.end(), match, *regex)) {
v.mkNull();
return;
}
@ -4818,7 +4825,7 @@ void prim_split(EvalState & state, const PosIdx pos, Value ** args, Value & v)
const auto str =
state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.split");
auto begin = std::cregex_iterator(str.begin(), str.end(), regex);
auto begin = std::cregex_iterator(str.begin(), str.end(), *regex);
auto end = std::cregex_iterator();
// Any matches results are surrounded by non-matching results.