Skip to content

kernel: cache conversion results within a session - #22309

Open
olympichek wants to merge 1 commit into
rocq-prover:masterfrom
olympichek:conversion-cache-pr
Open

kernel: cache conversion results within a session#22309
olympichek wants to merge 1 commit into
rocq-prover:masterfrom
olympichek:conversion-cache-pr

Conversation

@olympichek

Copy link
Copy Markdown
Contributor

Kernel: cache conversion results within a session

Summary

Kernel conversion runs on the closure machine of kernel/cClosure.ml: the lazy reduction machine that represents a term as a graph of mutable cells (fconstr), each holding a subterm together with its environment, and updates a cell in place with its reduct once it has been forced. Because substitution is delayed in the environment rather than performed on the syntax, substituting a term for a variable makes every occurrence of that variable point at one and the same cell instead of at a copy of it.

Today, conversion does not exploit that sharing. A problem posed on two such terms decomposes into subproblems that confront the same pair of cells once per occurrence, and ccnv retains nothing between those encounters, so each is reduced and compared from scratch. The cost of the check then scales with how many times a shared subterm occurs, not with how large it is.

This PR makes conversion remember. Each cell gets a stable identity, and a table local to one conversion session records, for a pair of cells, whether they were found convertible. A pair that comes back is answered from the table instead of being reduced again.

This is a follow-up for the recently merged #22299, which discharges a pair up front when both sides are the same pointer.

Implementation

  • Stable cell ids. fconstr gains a mutable fid field (0 = unassigned), handed out lazily by CClosure.get_fid. It is preserved by the in-place update the machine performs when it reduces a cell, and reset to 0 on every copy of a cell (lft_fconstr, resubst, zip, mkFApp, and the other places that build fresh cells). All the noise in kernel/cClosure.ml is just this field being added to record literals.
  • Session cache. conv_tab gains an optional conv_cache: a Hashtbl from (fid1, fid2, cv_pb) to a bucket of (lift1, lift2, result) entries, compared with eq_lift. ccnv looks the pair up before calling eqappr, and records the outcome afterwards. Both successes and failures are cached: a hit replays cuniv unchanged or re-raises NotConvertible.
  • Lift normalization. Before keying, outer FLIFT layers are stripped off both sides and folded into the corresponding lift with el_shft, so that cells that differ only by a lift wrapper share cache entries. This mirrors what the existing fast_test pre-check already does.
  • Knobs. The cache is on by default; ROCQ_CONV_CACHE=0 disables it and ROCQ_CONV_CACHE_MAX=<n> caps the number of entries (0 or unset means unlimited). It is unbounded by default because, like clos_tab, the table is session-scoped, so its lifetime bounds its memory.

This PR was made with assistance of Claude Fable 5.

Why it's correct

The cache is only installed for checked conversion (gen_conv, i.e. conv / conv_leq), where the answer is a deterministic function of a fixed environment and universe graph, and no universe constraints are accumulated that would need to be replayed on a hit. Inference-mode conversion (generic_conv, used by unification and evarconv, which backtracks and discards constraints) passes ~use_cache:false and never consults the cache, as does the compare_under-style entry point registered on CClosure.

Two further points:

  • Caching under fid is stable across in-place reduction: update rewrites a cell's term to a reduct of what was there, which is convertible to it, so a previously recorded verdict for that cell remains valid. Copies get a fresh fid, so no unrelated cell can inherit a verdict.
  • Lifts and the conversion problem (CONV vs CUMUL) are part of the key, so a result is never reused at a different de Bruijn offset or in the wrong direction. l2r is fixed for the whole session and so needs no keying.

Performance

The motivating development is Bonak, whose dimension-indexed definitions compare heavily shared closures over and over. Below is compilation of theories/νSet/SSGpd.v, each row adding to the previous one. All rows assume #22272 (refolding of global fixpoints), without which the file is too slow to measure.

Configuration Time Peak memory
#22272 alone 2139.2s 1.02 GB
+ compare_under fast paths (#22299) 812.6s (2.63x) 1.00 GB
+ conversion cache (this PR) 171.5s (4.74x) 8.13 GB

The cache is worth 4.74x here, 12.5x cumulatively from the first row. It buys that with memory: peak usage goes from 1.00 GB to 8.13 GB, which ROCQ_CONV_CACHE_MAX can bound.

The broader impact is unknown: this is an extreme case, and we have not measured whether other developments gain, are unaffected, or lose to the bookkeeping and the extra memory. This can be tested by the CI benchmark. If the cache does not pay off outside workloads like this one, we are happy to make it off by default behind an explicit option, something like Set Conversion Cache.

Checklist

  • Added changelog (kernel performance improvement, type Changed).

A telescope-heavy kernel check compares the same pair of closures over and
over: beta-substitution shares payload cells across every occurrence of a
variable, so the same two cells meet again at each copy of the term they
were substituted into. The reduction machine has no memory of this and
re-derives each convertibility subproblem from scratch.

Give each fconstr cell a stable id (a new lazily-assigned mutable field,
preserved by the in-place [update] the machine uses for reduction and
reset on every copy), and add a per-session table in `ccnv` mapping a pair
of cell ids plus lift pair and conversion problem to the outcome
(convertible or not). On a hit the outcome is replayed directly; both
successes and failures are cached.

Enabled only for checked conversion, where the result is a deterministic
function of the fixed environment and no universe constraints are
accumulated to replay; inference-mode conversion (which backtracks and
discards constraints) never consults the cache. On by default;
ROCQ_CONV_CACHE=0 disables it, ROCQ_CONV_CACHE_MAX bounds the table.

Cuts the heaviest checked conversion we measured by ~4x.
@olympichek
olympichek requested a review from a team as a code owner July 26, 2026 23:21
@coqbot-app coqbot-app Bot added the needs: full CI The latest GitLab pipeline that ran was a light CI. Say "@coqbot run full ci" to get a full CI. label Jul 26, 2026
@silene

silene commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

I might be missing something, but I do not understand the point of the fid field. Since "it is preserved by the in-place update and reset on every copy", it seems to be exactly the same as the physical identity of an fconstr term. So, you could use the terms themselves as keys. If the fear is that the cache would keep terms alive much longer than needed if they were to be used as keys, then just use ephemerons or weak hash tables, that is their whole point.

@SkySkimmer

Copy link
Copy Markdown
Contributor

How do you hash physical equality to use it as a hashtbl key?

@SkySkimmer

Copy link
Copy Markdown
Contributor

@coqbot run full ci

@coqbot-app coqbot-app Bot removed the needs: full CI The latest GitLab pipeline that ran was a light CI. Say "@coqbot run full ci" to get a full CI. label Jul 27, 2026
@SkySkimmer

Copy link
Copy Markdown
Contributor

@coqbot bench

@coqbot-app

coqbot-app Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

🏁 Bench results:

┌─────────────────────────────────────┬──────────────────────────┬────────────────────────────────────────┬──────────────────────────┐
│                                     │      user time [s]       │            CPU instructions            │  max resident mem [KB]   │
│                                     │                          │                                        │                          │
│            package_name             │   NEW      OLD    PDIFF  │      NEW             OLD        PDIFF  │   NEW      OLD    PDIFF  │
├─────────────────────────────────────┼──────────────────────────┼────────────────────────────────────────┼──────────────────────────┤
│                        coq-bedrock2 │  263.35   360.81  -27.01 │  2017910120866   2922533648850  -30.95 │  926472   870704    6.40 │
│                         coq-unimath │ 1908.64  1955.92   -2.42 │ 14879783427374  15983318090732   -6.90 │ 5133164  1801232  184.98 │
│                           rocq-core │    6.71     6.85   -2.04 │    41538082316     41490555997    0.11 │  447940   448564   -0.14 │
│        coq-fiat-crypto-with-bedrock │ 7071.86  7181.06   -1.52 │ 57019311853138  58795949945068   -3.02 │ 3085016  2827172    9.12 │
│                    coq-fiat-parsers │  270.21   272.67   -0.90 │  2037816998589   2080451441204   -2.05 │ 2034644  2037056   -0.12 │
│ coq-neural-net-interp-computed-lite │  236.35   237.62   -0.53 │  2260491655408   2259990243903    0.02 │  884904   878416    0.74 │
│                 rocq-mathcomp-order │   80.78    81.01   -0.28 │   593853053983    596492110195   -0.44 │ 1639512  1616232    1.44 │
│                 rocq-mathcomp-field │  212.02   212.49   -0.22 │  1540011318832   1552011836280   -0.77 │ 2304236  2314832   -0.46 │
│                  rocq-mathcomp-boot │   39.84    39.82    0.05 │   233926844412    233688564884    0.10 │  665864   665748    0.02 │
│                        rocq-runtime │   76.52    76.40    0.16 │   555131956255    555095468931    0.01 │  495396   495832   -0.09 │
│                rocq-metarocq-common │   41.42    41.35    0.17 │   265419039635    265272372275    0.06 │  905656   919736   -1.53 │
│                        coq-coqprime │   56.12    56.02    0.18 │   382533735158    382481987822    0.01 │  830764   832464   -0.20 │
│  rocq-mathcomp-group-representation │  104.10   103.87    0.22 │   719033582714    725339702862   -0.87 │ 1716716  1715408    0.08 │
│                        coq-compcert │  306.06   305.33    0.24 │  1983919784276   1977523750633    0.32 │ 1204772  1209116   -0.36 │
│                           rocq-elpi │   16.62    16.58    0.24 │   119169605101    119170052345   -0.00 │  462040   461992    0.01 │
│                            coq-hott │  159.29   158.89    0.25 │  1061200622705   1064499460730   -0.31 │  466272   472452   -1.31 │
│                         rocq-stdlib │  240.93   240.30    0.26 │  1493341385697   1490480981853    0.19 │  759252   761256   -0.26 │
│                       coq-fourcolor │ 1354.41  1350.56    0.29 │ 12421039254099  12422025448641   -0.01 │ 1025836  1025868   -0.00 │
│               rocq-mathcomp-algebra │  401.80   400.65    0.29 │  2937830164654   2933043738771    0.16 │ 1532600  1534352   -0.11 │
│                           coq-color │  230.43   229.76    0.29 │  1446048954868   1442043311196    0.28 │ 1164008  1172660   -0.74 │
│                         coq-coqutil │   47.47    47.33    0.30 │   291603160508    291258749112    0.12 │  568060   567852    0.04 │
│               coq-mathcomp-analysis │ 1342.35  1338.07    0.32 │ 10016998484682  10083798683939   -0.66 │ 2131576  2131268    0.01 │
│                    coq-math-classes │   82.81    82.40    0.50 │   492016414912    495514791833   -0.71 │  516556   516356    0.04 │
│              rocq-metarocq-template │   81.99    81.58    0.50 │   557122848349    556064791581    0.19 │ 1119268  1120156   -0.08 │
│          coq-performance-tests-lite │  871.50   866.93    0.53 │  6967555049993   6953244999788    0.21 │ 1615600  1519060    6.36 │
│          rocq-metarocq-translations │   16.68    16.59    0.54 │   113919713095    114472679033   -0.48 │  850232   884520   -3.88 │
│                   coq-iris-examples │  370.88   368.52    0.64 │  2390256905632   2391311403344   -0.04 │ 1077952  1066124    1.11 │
│                      coq-coquelicot │   38.99    38.74    0.65 │   233627125422    232935020024    0.30 │  833740   827476    0.76 │
│                 coq-category-theory │ 1121.83  1113.61    0.74 │  8202800990481   8175418290710    0.33 │ 7296596  6388240   14.22 │
│                       coq-fiat-core │   56.71    56.24    0.84 │   339645668309    338820754977    0.24 │  484204   482228    0.41 │
│                 rocq-metarocq-pcuic │  604.66   599.58    0.85 │  3837697525662   3828307806257    0.25 │ 1693112  1766192   -4.14 │
│           rocq-metarocq-safechecker │  313.82   310.91    0.94 │  2313318994383   2306014518812    0.32 │ 1730772  1727940    0.16 │
│              coq-mathcomp-odd-order │  612.03   606.23    0.96 │  4254003629783   4254959850259   -0.02 │ 2658012  2658156   -0.01 │
│              rocq-mathcomp-solvable │   99.50    98.55    0.96 │   663567406970    663544247004    0.00 │ 1110728  1096100    1.33 │
│                            coq-corn │  648.20   641.69    1.01 │  4296400713963   4310266451584   -0.32 │  650524   621996    4.59 │
│               rocq-metarocq-erasure │  441.62   437.18    1.02 │  2968701274267   2957796320885    0.37 │ 1811712  1744648    3.84 │
│         coq-rewriter-perf-SuperFast │  465.00   460.30    1.02 │  3540938468500   3532143040448    0.25 │ 1273756  1278648   -0.38 │
│                        coq-rewriter │  331.88   328.39    1.06 │  2446620904805   2430248038868    0.67 │ 1494536  1413924    5.70 │
│                 rocq-metarocq-utils │   24.58    24.24    1.40 │   156217520249    155623263766    0.38 │  589720   591844   -0.36 │
│               coq-engine-bench-lite │  129.70   127.67    1.59 │   959914085844    937911845233    2.35 │ 1106088  1100184    0.54 │
│                      rocq-equations │    7.99     7.86    1.65 │    55533762478     55499781763    0.06 │  399628   400736   -0.28 │
│                        rocq-bignums │   25.53    25.10    1.71 │   160313814436    159772268632    0.34 │  463296   461304    0.43 │
│          rocq-mathcomp-finite-group │   27.32    26.65    2.51 │   174769229772    172412833454    1.37 │  613664   571824    7.32 │
│                            coq-core │    2.83     2.72    4.04 │    19016942789     19047380504   -0.16 │   93828    92420    1.52 │
│             rocq-mathcomp-ssreflect │    1.17     1.12    4.46 │     7545232669      7541921622    0.04 │  593844   595900   -0.35 │
│                             coq-vst │  891.52   808.74   10.24 │  5858952164003   6052735352329   -3.20 │ 8246324  2011108  310.04 │
└─────────────────────────────────────┴──────────────────────────┴────────────────────────────────────────┴──────────────────────────┘

🐢 Top 25 slow downs
┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│                                                             TOP 25 SLOW DOWNS                                                              │
│                                                                                                                                            │
│  OLD     NEW     DIFF     %DIFF    Ln                     FILE                                                                             │
├────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ 15.972  51.917  35.9450  225.05%  1209  coq-vst/floyd/Component.v.html                                                                     │
│ 16.059  51.489  35.4300  220.62%  1223  coq-vst/floyd/Component.v.html                                                                     │
│ 15.975  48.456  32.4810  203.32%  1515  coq-vst/floyd/VSU.v.html                                                                           │
│   12.4    38.8  26.3486  212.28%    97  coq-unimath/UniMath/CategoryTheory/Hyperdoctrines/HValuedSets.v.html                               │
│   6.01    30.8  24.7800  412.33%   196  coq-unimath/UniMath/CategoryTheory/Hyperdoctrines/HValuedSets.v.html                               │
│  8.031  26.518  18.4870  230.20%  1509  coq-vst/floyd/Component.v.html                                                                     │
│  8.019  24.017  15.9980  199.50%   783  coq-vst/floyd/Component.v.html                                                                     │
│   2.76    13.5  10.7721  389.83%   160  coq-unimath/UniMath/CategoryTheory/Hyperdoctrines/HValuedSets.v.html                               │
│   7.50    16.7   9.2379  123.16%   136  coq-unimath/UniMath/CategoryTheory/Actegories/Examples/ActionOfEndomorphismsInCATElementary.v.html │
│  2.019   5.018   2.9990  148.54%   243  coq-vst/floyd/subsume_funspec.v.html                                                               │
│   35.3    38.1   2.8415    8.06%   898  coq-fiat-crypto-with-bedrock/src/Bedrock/Secp256k1/JoyeLadder.v.html                               │
│   10.2    13.0   2.7356   26.74%  2300  coq-fiat-crypto-with-bedrock/rupicola/bedrock2/compiler/src/compiler/FlatToRiscvFunctions.v.html   │
│  0.577    3.22   2.6418  458.02%   298  coq-unimath/UniMath/CategoryTheory/Hyperdoctrines/HValuedSets.v.html                               │
│   3.23    5.75   2.5182   77.89%   118  coq-unimath/UniMath/Semantics/LinearLogic/LiftingModel.v.html                                      │
│   2.19    4.03   1.8380   83.84%   265  coq-unimath/UniMath/CategoryTheory/Chains/OmegaContFunctors.v.html                                 │
│   2.33    4.09   1.7583   75.37%     8  coq-engine-bench-lite/coq/PerformanceDemos/repeated_conj.v.html                                    │
│   49.6    51.3   1.7150    3.46%    27  coq-fiat-crypto-with-bedrock/src/Rewriter/Passes/ToFancyWithCasts.v.html                           │
│   5.58    7.20   1.6120   28.86%   217  coq-fiat-crypto-with-bedrock/src/Bedrock/Field/Synthesis/Examples/p224_64_new.v.html               │
│   77.4    79.0   1.5399    1.99%    20  coq-fiat-crypto-with-bedrock/src/Rewriter/Passes/NBE.v.html                                        │
│   42.7    44.0   1.2947    3.03%   221  coq-fiat-crypto-with-bedrock/src/Bedrock/P256/Coord32.v.html                                       │
│    103     104   1.2090    1.18%    22  coq-fiat-crypto-with-bedrock/src/Rewriter/Passes/ArithWithCasts.v.html                             │
│  0.984    2.19   1.2060  122.56%  1346  coq-vst/floyd/Component.v.html                                                                     │
│   31.5    32.6   1.1570    3.68%   255  coq-fiat-crypto-with-bedrock/src/Bedrock/P256/Coord.v.html                                         │
│   61.8    62.9   1.1176    1.81%   857  coq-mathcomp-analysis/theories/lebesgue_integral_theory/lebesgue_integral_differentiation.v.html   │
│   39.8    40.9   1.1055    2.78%   246  coq-category-theory/Construction/DecoratedCospan/Category.v.html                                   │
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
🐇 Top 25 speed ups
┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│                                                              TOP 25 SPEED UPS                                                              │
│                                                                                                                                            │
│  OLD     NEW      DIFF     %DIFF    Ln                     FILE                                                                            │
├────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│   67.8    2.67  -65.1436  -96.06%   608  coq-bedrock2/bedrock2/src/bedrock2Examples/lightbulb.v.html                                       │
│   63.2    2.65  -60.5878  -95.81%   608  coq-fiat-crypto-with-bedrock/rupicola/bedrock2/bedrock2/src/bedrock2Examples/lightbulb.v.html     │
│   50.0  0.0461  -49.9238  -99.91%   376  coq-unimath/UniMath/ModelCategories/Generated/LNWFSMonoidalStructure.v.html                       │
│ 29.205   0.064  -29.1410  -99.78%   147  coq-vst/veric/expr_lemmas4.v.html                                                                 │
│ 28.819   0.041  -28.7780  -99.86%   194  coq-vst/veric/expr_lemmas4.v.html                                                                 │
│   24.6   0.247  -24.3802  -99.00%   550  coq-bedrock2/bedrock2/src/bedrock2Examples/insertionsort.v.html                                   │
│   24.4   0.247  -24.1583  -98.99%   550  coq-fiat-crypto-with-bedrock/rupicola/bedrock2/bedrock2/src/bedrock2Examples/insertionsort.v.html │
│   44.2    20.3  -23.9189  -54.11%   578  coq-fiat-crypto-with-bedrock/rupicola/bedrock2/compiler/src/compiler/MMIO.v.html                  │
│   21.4   0.199  -21.2358  -99.07%   338  coq-unimath/UniMath/ModelCategories/Generated/LNWFSMonoidalStructure.v.html                       │
│   39.5    21.4  -18.0380  -45.69%  1423  coq-fiat-crypto-with-bedrock/rupicola/bedrock2/compiler/src/compiler/FlatToRiscvFunctions.v.html  │
│   18.0   0.239  -17.7681  -98.68%   905  coq-unimath/UniMath/ModelCategories/Generated/LNWFSCocomplete.v.html                              │
│   11.7   0.906  -10.8086  -92.26%   216  coq-fiat-crypto-with-bedrock/src/Fancy/Barrett256.v.html                                          │
│   16.0    6.91   -9.0716  -56.77%   898  coq-fiat-crypto-with-bedrock/src/Bedrock/Secp256k1/JoyeLadder.v.html                              │
│   8.63   0.489   -8.1375  -94.33%   453  coq-unimath/UniMath/SyntheticHomotopyTheory/Circle2.v.html                                        │
│   11.4    6.38   -4.9824  -43.86%   118  coq-fiat-parsers/src/Parsers/Refinement/SharpenedJSON.v.html                                      │
│  4.974     0.1   -4.8740  -97.99%   407  coq-vst/floyd/for_lemmas.v.html                                                                   │
│   19.9    16.1   -3.8482  -19.30%   543  coq-unimath/UniMath/CategoryTheory/Presheaves/SigmaTypes.v.html                                   │
│   3.38   0.121   -3.2558  -96.42%   233  coq-fiat-crypto-with-bedrock/src/Bedrock/P256/RecodeProofs.v.html                                 │
│   2.13  0.0561   -2.0753  -97.37%   243  coq-unimath/UniMath/ModelCategories/Examples.v.html                                               │
│   2.12  0.0538   -2.0703  -97.47%   459  coq-unimath/UniMath/ModelCategories/Examples.v.html                                               │
│   5.23    3.25   -1.9800  -37.89%   210  coq-fiat-crypto-with-bedrock/src/Demo.v.html                                                      │
│   2.11   0.131   -1.9796  -93.81%   323  coq-fiat-crypto-with-bedrock/src/Bedrock/P256/RecodeProofs.v.html                                 │
│   2.03   0.142   -1.8915  -93.03%   348  coq-unimath/UniMath/CategoryTheory/Presheaf.v.html                                                │
│    202     201   -1.4596   -0.72%     8  coq-neural-net-interp-computed-lite/theories/MaxOfTwoNumbersSimpler/Computed/AllLogits.v.html     │
│   42.3    41.0   -1.3708   -3.24%   115  coq-bedrock2/bedrock2/src/bedrock2Examples/full_mul.v.html                                        │
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

@ppedrot

ppedrot commented Jul 27, 2026

Copy link
Copy Markdown
Member

Whoopf, that's a crazy speedup in bedrock2 (cc @JasonGross). For VST there is an increase in total time but a non-trivial reduction in instructions, how come?

@silene

silene commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

The number of instructions is a meaningful metric only if the memory access patterns do not change. Remember that the memory latency grows exponentially with the processor cache level (e.g., 2 cycles for L1, 10 cycles for L2, 50 cycles for L3, 250 cycles for main memory, but depending on the architecture, it can be even steeper). Here, fconstr grows by 33%, and since it is the main data structure of the conversion checker, this will put a severe strain on the cache. If it causes even a minor increase in the number of cache misses, the performance of the conversion checker will plummet. Unfortunately, processor cache misses are not recorded by the bench, so it is hard to be sure whether this is the reason.

@gares

gares commented Jul 28, 2026

Copy link
Copy Markdown
Member

The problem with VST is not only the time. Requiring 8G to compile it seems much worse than the time slowdown. Maybe capping the size of the cache with max_int by default is not a good idea.

@SkySkimmer

Copy link
Copy Markdown
Contributor

I think we could add cache misses to what

perf stat -e instructions:u,cycles:u -o "$prefix.$ncoms.perf" \
records.

@rlepigre-skylabs-ai

Copy link
Copy Markdown
Contributor

The mark only needs 2 bits, so the new fid could probably be packed into the same word to keep the same memory footprint.

@silene

silene commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Note that, even if the fid field is required, my point about ephemerons and weak hashtables still stands. They are designed precisely for this kind of cache. In particular, they let the garbage collector remove the entries that are no longer relevant from the cache when the memory pressure becomes too high. So, no need to put an arbitrary bound on the size of the cache.

By the way, the code strongly depends on the fact that the compiler is not a 32-bit one (so no more jsCoq, which is fine by me).

@coqbot-app coqbot-app Bot added the needs: full CI The latest GitLab pipeline that ran was a light CI. Say "@coqbot run full ci" to get a full CI. label Jul 29, 2026
@rlepigre

Copy link
Copy Markdown
Contributor

Sorry, I pushed here by mistake... I'll revert the push.

@rlepigre
rlepigre force-pushed the conversion-cache-pr branch from 8c14f68 to 739cf01 Compare July 29, 2026 07:42
@gares

gares commented Jul 29, 2026

Copy link
Copy Markdown
Member

They are designed precisely for this kind of cache. In particular, they let the garbage collector remove the entries that are no longer relevant from the cache when the memory pressure becomes too high. So, no need to put an arbitrary bound on the size of the cache.

I totally agree we should try to use weak tables here. But if in the end we don't, please cap the cache size.

@olympichek

Copy link
Copy Markdown
Contributor Author

Note that, even if the fid field is required, my point about ephemerons and weak hashtables still stands. They are designed precisely for this kind of cache. In particular, they let the garbage collector remove the entries that are no longer relevant from the cache when the memory pressure becomes too high. So, no need to put an arbitrary bound on the size of the cache.

I asked Fable to prototype a design with ephemerons, but it appears it doesn't give significant gains (tested on Bonak's SSGpd example): olympichek@62222da

@gares

gares commented Jul 31, 2026

Copy link
Copy Markdown
Member

Can you ask the AI to find a good default for the cache size running multiple time that example, push the commit here and run again the benchmark on all our bench suite?

@silene

silene commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Moreover, what is the expected length of the lists in the cache buckets? Unless it is small (e.g., 4), it would make sense to use hash tables as the buckets rather than lists. Also, since CONV implies CUMUL (am I getting it right?), the cache does not need to hold both conversion results (and can use one in place of the other).

@olympichek

Copy link
Copy Markdown
Contributor Author

Can you ask the AI to find a good default for the cache size running multiple time that example, push the commit here and run again the benchmark on all our bench suite?

I was wondering whether it is worth it before submitting a PR.

I decided not to do this, as other existing session-scoped tables in Rocq's kernel don't have such a cap: clos_tab (the reduction machine's constant-unfolding memo table that every conversion session allocates) and HConstr.Tbl (the hash-consing table) both don't have a cap.

Note that it can also depend on how exactly the cap semantics works. There might be multiple ways to do it:

  1. Freeze: once the entry count reaches the cap, stop recording new results, but keep probing the table on every conversion. The table stops growing, yet every ccnv call still pays the lookup bookkeeping (id fetch, lift normalization, hashing) against entries that were recorded before saturation — so sessions whose hits come from late entries lose them while still paying full probe cost.
  2. Deactivate: on reaching the cap, switch the cache off for the rest of the session — no more probes, no more inserts — and drop the table to reclaim its memory. A saturated session degrades to the uncached behavior, keeping only the speedup already obtained before saturation; sessions that never reach the cap are unaffected.
  3. Reset-and-refill: on reaching the cap, empty the table and keep caching into the fresh one. The table never holds more than the cap's worth of entries, and comparisons that repeat close together in time still hit; only repeats that span a reset boundary are lost.

The way it is currently implemented in this PR is freeze, and it appears it doesn't behave well on Bonak's SSGpd.v:

ROCQ_CONV_CACHE_MAX wall vs uncapped peak RSS
unlimited 164 s 7.30 GB
2^26 (67M) 164 s 1.00x 7.30 GB
2^25 (33.5M) 175 s 1.06x 7.26 GB
2^24 (16.8M) 914 s 5.6x 7.95 GB
2^23 (8.4M) 1571 s 9.6x 6.01 GB
cache disabled 835 s 5.1x 1.03 GB

I will continue investigating it..

@olympichek

Copy link
Copy Markdown
Contributor Author

Moreover, what is the expected length of the lists in the cache buckets?

Yes, they are expected to be small, most of the occurrences are just a single element. Measured on Bonak's SSGpd.v: the mean bucket length is 1.06 entries, 97.6% of buckets hold exactly one entry, 99.98% hold at most 15.

it would make sense to use hash tables as the buckets rather than lists.

I am not sure if hash table would be better here, it appears hash tables have higher overhead than lists.

However, I have an experimental patch that replaces the whole structure with buckets with just a pair of integers:
olympichek@6df31ce. It reduces memory consumption on Bonak's SSGpd.v by almost ~2x (8.13 GB -> 4.49 Gb).

Also, since CONV implies CUMUL (am I getting it right?), the cache does not need to hold both conversion results (and can use one in place of the other).

The thing is, we cache not only successes, but also failures of conversion problem. CONV success implies CUMUL success, for failures it is the other way around - CUMUL failure implies CONV failure.

However, it appears caching only CONV is enough in practice. Counting on SSGpd.v: of 105.68M probes only 9,438 (0.009%) are CUMUL.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs: full CI The latest GitLab pipeline that ran was a light CI. Say "@coqbot run full ci" to get a full CI.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants