1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2024-09-20 23:28:26 -04:00
nix/src/libexpr/attr-set.hh
pennae 8168a4cf4a shrink Attr by 8 bytes on 64bit machines
with position and symbol tables in place we can now shrink Attr by a full
pointer with some simple field reordering. since Attr is a very hot struct this
has substantial impact on memory use, decreasing GC allocations and heap size by
10-15% each. we also get a ~15% performance improvement due to reduced GC
loading.

pure parsing has taken a hit over the branch base because positions are now
slightly more expensive to create, but overall we get a noticeable improvement.

before (on memory-friendliness):

  Benchmark 1: nix search --no-eval-cache --offline ../nixpkgs hello
    Time (mean ± σ):      6.960 s ±  0.028 s    [User: 5.832 s, System: 0.897 s]
    Range (min … max):    6.886 s …  7.005 s    20 runs

  Benchmark 2: nix eval -f ../nixpkgs/pkgs/development/haskell-modules/hackage-packages.nix
    Time (mean ± σ):     328.1 ms ±   1.7 ms    [User: 295.8 ms, System: 32.2 ms]
    Range (min … max):   324.9 ms … 331.2 ms    20 runs

  Benchmark 3: nix eval --raw --impure --expr 'with import <nixpkgs/nixos> {}; system'
    Time (mean ± σ):      2.688 s ±  0.029 s    [User: 2.365 s, System: 0.238 s]
    Range (min … max):    2.642 s …  2.742 s    20 runs

after:

  Benchmark 1: nix search --no-eval-cache --offline ../nixpkgs hello
    Time (mean ± σ):      6.902 s ±  0.039 s    [User: 5.844 s, System: 0.783 s]
    Range (min … max):    6.820 s …  6.956 s    20 runs

  Benchmark 2: nix eval -f ../nixpkgs/pkgs/development/haskell-modules/hackage-packages.nix
    Time (mean ± σ):     330.7 ms ±   2.2 ms    [User: 300.6 ms, System: 30.0 ms]
    Range (min … max):   327.5 ms … 334.5 ms    20 runs

  Benchmark 3: nix eval --raw --impure --expr 'with import <nixpkgs/nixos> {}; system'
    Time (mean ± σ):      2.330 s ±  0.027 s    [User: 2.040 s, System: 0.234 s]
    Range (min … max):    2.272 s …  2.383 s    20 runs
2022-04-21 21:56:34 +02:00

163 lines
4 KiB
C++

#pragma once
#include "nixexpr.hh"
#include "symbol-table.hh"
#include <algorithm>
#include <optional>
namespace nix {
class EvalState;
struct Value;
/* Map one attribute name to its value. */
struct Attr
{
/* the placement of `name` and `pos` in this struct is important.
both of them are uint32 wrappers, they are next to each other
to make sure that Attr has no padding on 64 bit machines. that
way we keep Attr size at two words with no wasted space. */
SymbolIdx name;
PosIdx pos;
Value * value;
Attr(SymbolIdx name, Value * value, PosIdx pos = noPos)
: name(name), pos(pos), value(value) { };
Attr() { };
bool operator < (const Attr & a) const
{
return name < a.name;
}
};
static_assert(sizeof(Attr) == 2 * sizeof(uint32_t) + sizeof(Value *),
"performance of the evaluator is highly sensitive to the size of Attr. "
"avoid introducing any padding into Attr if at all possible, and do not "
"introduce new fields that need not be present for almost every instance.");
/* Bindings contains all the attributes of an attribute set. It is defined
by its size and its capacity, the capacity being the number of Attr
elements allocated after this structure, while the size corresponds to
the number of elements already inserted in this structure. */
class Bindings
{
public:
typedef uint32_t size_t;
PosIdx pos;
private:
size_t size_, capacity_;
Attr attrs[0];
Bindings(size_t capacity) : size_(0), capacity_(capacity) { }
Bindings(const Bindings & bindings) = delete;
public:
size_t size() const { return size_; }
bool empty() const { return !size_; }
typedef Attr * iterator;
void push_back(const Attr & attr)
{
assert(size_ < capacity_);
attrs[size_++] = attr;
}
iterator find(const SymbolIdx & name)
{
Attr key(name, 0);
iterator i = std::lower_bound(begin(), end(), key);
if (i != end() && i->name == name) return i;
return end();
}
Attr * get(const SymbolIdx & name)
{
Attr key(name, 0);
iterator i = std::lower_bound(begin(), end(), key);
if (i != end() && i->name == name) return &*i;
return nullptr;
}
iterator begin() { return &attrs[0]; }
iterator end() { return &attrs[size_]; }
Attr & operator[](size_t pos)
{
return attrs[pos];
}
void sort();
size_t capacity() { return capacity_; }
/* Returns the attributes in lexicographically sorted order. */
std::vector<const Attr *> lexicographicOrder(const SymbolTable & symbols) const
{
std::vector<const Attr *> res;
res.reserve(size_);
for (size_t n = 0; n < size_; n++)
res.emplace_back(&attrs[n]);
std::sort(res.begin(), res.end(), [&](const Attr * a, const Attr * b) {
std::string_view sa = symbols[a->name], sb = symbols[b->name];
return sa < sb;
});
return res;
}
friend class EvalState;
};
/* A wrapper around Bindings that ensures that its always in sorted
order at the end. The only way to consume a BindingsBuilder is to
call finish(), which sorts the bindings. */
class BindingsBuilder
{
Bindings * bindings;
public:
// needed by std::back_inserter
using value_type = Attr;
EvalState & state;
BindingsBuilder(EvalState & state, Bindings * bindings)
: bindings(bindings), state(state)
{ }
void insert(SymbolIdx name, Value * value, PosIdx pos = noPos)
{
insert(Attr(name, value, pos));
}
void insert(const Attr & attr)
{
push_back(attr);
}
void push_back(const Attr & attr)
{
bindings->push_back(attr);
}
Value & alloc(const SymbolIdx & name, PosIdx pos = noPos);
Value & alloc(std::string_view name, PosIdx pos = noPos);
Bindings * finish()
{
bindings->sort();
return bindings;
}
Bindings * alreadySorted()
{
return bindings;
}
};
}