Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/libexpr-tests/bench-main.cc
Original file line number Diff line number Diff line change
@@ -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;
}
55 changes: 55 additions & 0 deletions src/libexpr-tests/dynamic-attrs-bench.cc
Original file line number Diff line number Diff line change
@@ -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);
64 changes: 64 additions & 0 deletions src/libexpr-tests/get-drvs-bench.cc
Original file line number Diff line number Diff line change
@@ -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);
30 changes: 30 additions & 0 deletions src/libexpr-tests/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -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
9 changes: 9 additions & 0 deletions src/libexpr-tests/meson.options
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# vim: filetype=meson

option(
'benchmarks',
type : 'boolean',
value : false,
description : 'Build benchmarks (requires gbenchmark)',
yield : true,
)
2 changes: 1 addition & 1 deletion src/libexpr-tests/package.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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") ./.)
];
Expand Down
45 changes: 45 additions & 0 deletions src/libexpr-tests/regex-cache-bench.cc
Original file line number Diff line number Diff line change
@@ -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);
23 changes: 21 additions & 2 deletions src/libexpr/get-drvs.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down
29 changes: 18 additions & 11 deletions src/libexpr/primops.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4709,21 +4709,28 @@ static RegisterPrimOp primop_convertHash({

struct RegexCache
{
boost::concurrent_flat_map<std::string, std::regex, StringViewHash, std::equal_to<>> cache;
struct Entry
{
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;

std::regex get(std::string_view re)
ref<const std::regex> get(std::string_view re)
{
std::regex regex;
/* No std::regex constructor overload from std::string_view, but can be constructed
from a pointer + size or an iterator range. */
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;
}
};

Expand All @@ -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;
}
Expand Down Expand Up @@ -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.
Expand Down
Loading