-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] Lazy attribute names #4154
base: master
Are you sure you want to change the base?
Conversation
This is a version of Expr::eval that doesn't necessarily have to evaluate the value into a non-thunk, while returning a specific attribute The evalAttr of expressions that evaluate to a subexpression should call evalAttr on that subexpression, therefore bubbling up any potential thunks
0a7ccf2
to
6d7d037
Compare
Cleaned up the commits into smallish changes, undoing some smarts along the way, which unexpectedly fixed a bug I was encountering with this, nice! I also did some proper performance measurement, and the results are not as bad as I thought (presumably the smarts I undid were also causing the worse performance). I wrote a little measurement and plotting script to do this:
#!/usr/bin/env bash
set -euo pipefail
nixCommand() {
#nix-instantiate '<nixpkgs/nixos>' \
# --arg configuration '<nixpkgs/nixos/modules/profiles/demo.nix>' \
# -A vm
nix-instantiate ~/src/nixpkgs -A firefox
}
run() {
local stats=$(mktemp)
PATH=$PWD/bin:$PATH NIX_SHOW_STATS=1 NIX_SHOW_STATS_PATH="$stats" nixCommand >/dev/null 2>/dev/null
jq -r '.cpuTime' "$stats"
rm "$stats"
}
measure() {
local name=$1
local duration=$2
local dest="times/$name"
echo "Making sure binary is up-to-date"
nix-shell --run "make install -j8"
echo "Clearing any previous results in $dest"
mkdir -p "$(dirname "$dest")"
> "$dest"
echo "Warming up with a single run"
run >/dev/null
epochStart=$(date +%s)
epochEnd=$(( epochStart + duration ))
echo "Measuring for at least $duration seconds"
while now=$(date +%s) && [[ "$now" -le "$epochEnd" ]]; do
result=$(run)
echo "Measured $result seconds, writing to file. $(( epochEnd - now )) seconds left"
echo "$result" >> "$dest"
done
}
collectdata() {
for f in times/*; do
jq --arg name "$(basename "$f")" '[ ., inputs ] | map({ "CPU Time" : ., "Version" : $name })' -R "$f"
done | jq -s '. | map(.[])'
}
plot() {
collectdata > "data.json"
# Last nixpkgs version where vega_lite wasn't broken
vegaLite=$(nix-build --no-out-link https://github.com/NixOS/nixpkgs/archive/e1773ee0bb99e6785e2e06c0931cc8ffa9341d2a.tar.gz -A nodePackages.vega-lite)
"$vegaLite/bin/vl2svg" plot.json plot.svg
xdg-open plot.svg
}
case "$1" in
plot)
plot
;;
*)
measure "$1" "$2"
esac
{
"$schema": "https://vega.github.io/schema/vega-lite/v4.json",
"description": "Nix performance on `nix-instantiate '<nixpkgs/nixos>' --arg configuration '<nixpkgs/nixos/modules/profiles/demo.nix>' -A vm`",
"data": {"url": "data.json"},
"mark": {
"type": "boxplot",
"extent": "min-max"
},
"encoding": {
"x": {"field": "Version", "type": "nominal"},
"color": {"field": "Version", "type": "nominal", "legend": null},
"y": {
"field": "CPU Time",
"type": "quantitative",
"scale": {"zero": false}
}
}
} To use:
With Note how this is only a tiny bit slower, from ~0.62 to ~0.64! And I do believe some things can be optimized still, so I hope to be able to improve performance with this PR in the end :) |
6d7d037
to
fd71421
Compare
This introduces an expression base class that can be used for lazy binary operations, along with a value type for storing partial results of such expressions
This makes the // operator lazy in attribute names, only evaluates what is necessary to get a specific attribute.
So as to not increase the sizeof(Value) from 24 bytes to 40 bytes
fd71421
to
152048d
Compare
I think this is needed so that any variables on the sides get updated properly Without this, bin/nix-instantiate ~/src/nixpkgs/nixos --arg configuration ~/src/nixpkgs/nixos/modules/profiles/demo.nix -A vm fails with a segfault Not sure why this doesn't happen with the commit that makes // lazy though
The previous implementation relied on uninitialized memory not being a certain value for it to work
Since it throws inf rec when it doesn't have to sometimes
This pull request has been mentioned on NixOS Discourse. There might be relevant details there: |
This time it also works with lazy binops The previous optimizations for prevention of allocation had to be undone Code still needs cleanup, but it should be sound
cfd86dc
to
f99249d
Compare
With the previous commit, passing left/right is now unnecessary
5d50971
to
d1d9f1e
Compare
Every evaluation can now pass a handler, which is called once the resulting value is either a tAttrs or a tLazyAttrs There are two handlers: - One for weak head normal form (changes tLazyAttrs into tAttrs) - One for getting an attribute (lazily gets attributes from tLazyAttrs, strictly from tAttrs)
I made some good progress this week! I had to pretty much redesign this feature again, in order to deduplicate some function definitions i previously duplicated. I also fixed the infinite recursion detection, so that works again now. I think I have reached the final design of this, it's looking very promising now. Unfortunately I'm now pretty sure that this does cause an evaluation slow-down of about 5% in the end. So I think it's best to make this an opt-in feature instead. So the next thing I'll work on for this PR is introducing a new primop, While it would be possible to create a We could also consider making this the default again in the future in case the ~5% overhead can be removed. |
By using bitfields to encode the left/right blackhole for infinite recursion detection
…me allocs" This reverts commit 4a2c47a.
ddfe9a1
to
c3bbb4d
Compare
This pull request has been mentioned on NixOS Discourse. There might be relevant details there: https://discourse.nixos.org/t/tweag-nix-dev-update-15/13975/1 |
This pull request has been mentioned on NixOS Discourse. There might be relevant details there: https://discourse.nixos.org/t/specifics-of-set-laziness/16837/2 |
This pull request has been mentioned on NixOS Discourse. There might be relevant details there: https://discourse.nixos.org/t/specifics-of-set-laziness/16837/4 |
This pull request has been mentioned on NixOS Discourse. There might be relevant details there: https://discourse.nixos.org/t/nixlang-how-do-you-find-all-uses-of-a-declaration/18369/13 |
Does this fix the following too?
|
@axelkar Yes that would work in the current state of this PR (however, see below) Regarding the state of this PR, it should be changed to only expose this functionality under a |
@infinisil currently but even |
@ggPeti It does not |
This pull request has been mentioned on NixOS Discourse. There might be relevant details there: https://discourse.nixos.org/t/is-it-possible-for-us-to-remove-builtins-functionargs/51960/4 |
This is an initial prototype of lazy attribute names, allowing things like:
See #4090 for more info.
This is a work-in-progress. I opened this PR to keep track of what needs to be done still and how far I've gotten, and maybe get some help with it too. In particular, the main problem right now is that this currently makes Nix about 40% slower.
Ping @edolstra
Todo
Figure out why it's slower. ForIt's not anymore!NIX_SHOW_STATS=1 nix-instantiate '<nixpkgs>' -A firefox
, this branch has better numbers in all categories, except for bytes allocated forValue
's since those are now 40 instead of 24 bytes (will have to fix that), and the CPU time (that's what's 40% slower). I doubt these extra bytes are the reason though, but this doesn't make it easy to debug.maybeThunk
inExprSelect::{eval,evalAttr}
, not sure why that's needed. Maybe that's even the thing that causes worse performance -> Didn't cause the worse performance, but now gotten rid with 48511eaExpr*::evalAttr
methodsExprOpHasAttr
,builtins.{get,has}Attr
usingevalValueAttr
evalAttr
support for primops, to allow primops to be lazy in attribute namesevalValueAttr
(this might be tricky, example:let a = {} // a; in a.foo
)findAlongAttrPath
usingevalValueAttr
(makes it do looks up lazy), similar for other attribute-traversing functions, find thosetLazyBinOp
Value
pointers are nulled so the values get picked up by the GCSplit all theNot needed anymoreevalAttr
-related methods into a separate file,eval.cc
is too big.This code should also be generic enough that it should allow lazy lists and strings in the future (e.g. being able to do
builtins.elemAt ([ 0 ] ++ throw "") 0
). This could be done in a future PR once this is merged.And for future reference, previous (and later) worse attempts of implementing this are in my branches lazy-attr-names and lazy-attr-names-v2 lazy-attr-names-v4