Skip to content

Commit c0dfd54

Browse files
fzakariaxokdvium
andcommitted
Fix quadratic key disambiguation in lock files
LockFile::toJSON() assigns each node a unique key, disambiguating collisions by appending _2, _3, and so on. The search restarted at 2 on every collision, so the k-th node sharing a base name cost k iterations and the whole pass was O(n^2) in the number of colliding nodes. This is easy to hit in practice. Every flake that depends on many other flakes accumulates one "systems", "flake-compat" or "nixpkgs" node per dependency, and they all share a base name. Because the serialized lock file is passed to call-flake.nix on every evaluation, the cost is paid by `nix eval`, `nix flake metadata` and `nix flake lock` alike, not just when writing the file back out. Track the highest suffix already handed out per base key and resume from there. The insert check is retained, so a name that legitimately appears in the lock file (e.g. an input actually called "systems_2") is still skipped over and the resulting keys are unchanged. Measured on a flake with n inputs that each pull in their own "systems", timing `nix eval` of an output that touches no input: inputs nodes before after speedup 500 1000 0.64s 0.15s 4.3x 1000 2000 0.88s 0.16s 5.5x 2000 4000 3.05s 0.28s 10.9x 4000 8000 11.49s 0.54s 21.3x Lock files produced before and after are byte-identical, verified on the cases above, on a lock file containing pre-existing "systems_2", "systems_3" and "systems_5" inputs, and on an existing real-world flake. Co-authored-by: Sergei Zimmerman <sergei@zimmerman.foo>
1 parent df1878b commit c0dfd54

1 file changed

Lines changed: 9 additions & 2 deletions

File tree

src/libflake/lockfile.cc

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include <boost/unordered/unordered_flat_set.hpp>
2+
#include <boost/unordered/unordered_flat_map.hpp>
23
#include <nlohmann/json.hpp>
34
#include <assert.h>
45
#include <boost/unordered/unordered_flat_set_fwd.hpp>
@@ -187,14 +188,20 @@ std::pair<nlohmann::json, LockFile::KeyMap> LockFile::toJSON() const
187188
KeyMap nodeKeys;
188189
boost::unordered_flat_set<std::string> keys;
189190

191+
/* The next numeric suffix to try for a given base key. Suffixes start at 2, since the
192+
unsuffixed key occupies the _1 slot. Used to amortise name deduplication by memoising
193+
the lower bound of suffixes that definitely cannot be used. */
194+
boost::unordered_flat_map<std::string, int> keySuffixes;
195+
190196
auto dumpNode = [&](this auto & dumpNode, std::string key, ref<const Node> node) -> std::string {
191197
auto k = nodeKeys.find(node);
192198
if (k != nodeKeys.end())
193199
return k->second;
194200

195201
if (!keys.insert(key).second) {
196-
for (int n = 2;; ++n) {
197-
auto k = fmt("%s_%d", key, n);
202+
auto & suffix = keySuffixes.try_emplace(key, 2).first->second;
203+
for (;;) {
204+
auto k = fmt("%s_%d", key, suffix++);
198205
if (keys.insert(k).second) {
199206
key = k;
200207
break;

0 commit comments

Comments
 (0)