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:
commit
132a93625b
9 changed files with 258 additions and 15 deletions
14
src/libexpr-tests/bench-main.cc
Normal file
14
src/libexpr-tests/bench-main.cc
Normal 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;
|
||||
}
|
||||
55
src/libexpr-tests/dynamic-attrs-bench.cc
Normal file
55
src/libexpr-tests/dynamic-attrs-bench.cc
Normal 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);
|
||||
64
src/libexpr-tests/get-drvs-bench.cc
Normal file
64
src/libexpr-tests/get-drvs-bench.cc
Normal 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);
|
||||
|
|
@ -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
src/libexpr-tests/meson.options
Normal file
9
src/libexpr-tests/meson.options
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# vim: filetype=meson
|
||||
|
||||
option(
|
||||
'benchmarks',
|
||||
type : 'boolean',
|
||||
value : false,
|
||||
description : 'Build benchmarks (requires gbenchmark)',
|
||||
yield : true,
|
||||
)
|
||||
|
|
@ -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") ./.)
|
||||
];
|
||||
|
|
|
|||
45
src/libexpr-tests/regex-cache-bench.cc
Normal file
45
src/libexpr-tests/regex-cache-bench.cc
Normal 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);
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
[®ex](const auto & kv) { regex = kv.second; },
|
||||
[®ex](const auto & kv) { regex = kv.second; });
|
||||
return regex;
|
||||
[®ex](const auto & kv) { regex = kv.second.regex; },
|
||||
[®ex](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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue