PgColumnarFsstHelpsCompressed runs two full block compressions per verdict and pfrees both, using them only for their lengths. On data the codec cannot compress, every byte of that work is discarded, and it is measurable: lz4 costs +24.8% write CPU on the incompressible shape.
From #890's phase 1 (@jdatcmd's measurement), filed separately because #890 closes as "change nothing about the default" and this is a cost that stands whatever the default is.
Verified in source
src/columnar_encoding.c, PgColumnarFsstHelpsCompressed:
PgColumnarCompressValueStream(corpus, corpusLen, compressionType,
compressionLevel, &plainComp, &plainCompLen, ...);
PgColumnarCompressValueStream(codes, codesLen, compressionType,
compressionLevel, &codesComp, &codesCompLen, ...);
helps = (((uint64) codesCompLen + tableLen) * 100
< (uint64) plainCompLen * (uint64) (100 - pgcolumnar_fsst_min_gain_percent));
pfree(codes);
if (plainComp) pfree(plainComp);
if (codesComp) pfree(codesComp);
Two compressions, both freed, only plainCompLen and codesCompLen used.
Measured
write cost ratio, incompressible shape
lz4 W = 1.248 <- against a NET COST threshold of 1.25 in #890's rule
zstd W = 0.92
lz4 misses #890's pre-registered NET COST threshold by 0.002. It is not the default, so it did not bear on that decision — but a user who sets compression = lz4 on high-entropy text pays it.
The zstd arm being cheaper than none is the same mechanism from the other side and is explained by the sibling issue: with compression = none the function returns true unconditionally and FSST is paid for every vector with no decision to make, so none is not doing less work.
Why fsst_verdict_reuse does not already cover it
It was proposed as the explanation and measured to be zero:
none (FSST always kept, every chunk) 13,001,225,971
zstd, verdict_reuse=16 (default) 11,982,999,646
zstd, verdict_reuse=1 (decide every chunk) 11,982,901,432 identical
zstd, min_gain=0 (force FSST kept) 11,984,172,254 identical
reuse=1 against reuse=16 differs by 0.0008%, so amortising the verdict is not where the cost is. The cost is inside each verdict.
Suggested shapes, cheapest first
- Sample rather than compress the whole stream. The verdict needs a length ratio, not the bytes. Compressing a bounded prefix would give the same decision for a fraction of the work, at the cost of being an estimate — which it already is, since the verdict is reused across up to 16 chunks by default.
- Short-circuit on a cheap incompressibility estimate before paying either compression.
- Reuse one of the two compressions where the chosen branch needs it again downstream, instead of freeing both. Worth checking whether the winning stream is recompressed immediately afterwards; if so this is pure duplicate work and the fix is plumbing rather than estimation.
I have not measured which of these pays, and the fixture that would show it is the one in #890's write-side table.
Related
#1074 — the same function's keep test never evaluates FSST stored uncompressed. If that candidate is added, it is computable without a compression at all, which may make ordering the comparisons cheapest-first worthwhile.
PgColumnarFsstHelpsCompressedruns two full block compressions per verdict andpfrees both, using them only for their lengths. On data the codec cannot compress, every byte of that work is discarded, and it is measurable: lz4 costs +24.8% write CPU on the incompressible shape.From #890's phase 1 (@jdatcmd's measurement), filed separately because #890 closes as "change nothing about the default" and this is a cost that stands whatever the default is.
Verified in source
src/columnar_encoding.c,PgColumnarFsstHelpsCompressed:Two compressions, both freed, only
plainCompLenandcodesCompLenused.Measured
lz4misses #890's pre-registered NET COST threshold by 0.002. It is not the default, so it did not bear on that decision — but a user who setscompression = lz4on high-entropy text pays it.The
zstdarm being cheaper thannoneis the same mechanism from the other side and is explained by the sibling issue: withcompression = nonethe function returnstrueunconditionally and FSST is paid for every vector with no decision to make, sononeis not doing less work.Why
fsst_verdict_reusedoes not already cover itIt was proposed as the explanation and measured to be zero:
reuse=1againstreuse=16differs by 0.0008%, so amortising the verdict is not where the cost is. The cost is inside each verdict.Suggested shapes, cheapest first
I have not measured which of these pays, and the fixture that would show it is the one in #890's write-side table.
Related
#1074 — the same function's keep test never evaluates FSST stored uncompressed. If that candidate is added, it is computable without a compression at all, which may make ordering the comparisons cheapest-first worthwhile.