\Drupal\strata\Codec\Dictionary DictionaryTrainer

Builds a dictionary for one realm out of what that realm actually writes.

A dictionary is what makes small frames compress at all. A 16 KiB frame has almost no history to match against, so the same field names and the same JSON scaffolding are re-encoded in every frame; a dictionary supplies that history up front. Measured through the shipped codec: 4.87x without and 5.86x with, on the reference corpus at 16 KiB frames.

Two ways to build one, and which wins depends on the corpus, so both are built and scored. Concatenating samples produces a raw content dictionary, which zstd matches against verbatim; the zstd binary's trainer produces a compact entropy-oriented one. On 200 near-identical entity payloads the raw dictionary measured 11.64x against the trainer's 8.42x; on 400 deliberately mixed payloads the trainer measured 5.42x against 4.96x. Neither is generally better, so a run compresses a held-out slice with each candidate and keeps whichever actually won. Nothing here assumes.

No PHP extension exposes zstd's training API - ext-zstd ships the dictionary compress and decompress calls and nothing else - so the trained candidate needs the binary and is simply absent when it is not installed.

Summary

Methods
Properties
Constants
__construct
train
baseline
canTrain
No public properties found
MIN_SAMPLES
MAX_BYTES
BUILD_SHARE
SCORE_LEVEL
TRAIN_TIMEOUT
No protected methods found
No protected properties found
No protected constants found
candidates
concatenate
trainWithBinary
measure
resolveBinary
temporaryDirectory
removeDirectory
codec
binary
No private constants found

Constant

MIN_SAMPLES

MIN_SAMPLES = 32

Fewest samples worth building a dictionary from.

Below this the dictionary is longer than what it would save and describes one document rather than a realm.

MAX_BYTES

MAX_BYTES = 112640

Largest dictionary produced.

110 KiB, zstd's own recommended ceiling. A dictionary is fetched once per process and then held, so its size costs memory rather than requests, but a dictionary approaching the frame size stops being a summary of the realm.

BUILD_SHARE

BUILD_SHARE = 0.5

Share of the samples the raw candidate is built from.

The rest are held back for scoring, so a candidate is never scored on the bytes it contains - which would flatter the raw candidate enormously and pick it every time.

SCORE_LEVEL

SCORE_LEVEL = 19

Compression level candidates are scored at.

The compaction level, because that is where a dictionary is used: at 422.9 MB/s against 105.1 MB/s, loading one per frame is too expensive for the flush path.

TRAIN_TIMEOUT

TRAIN_TIMEOUT = 30

Seconds the trainer will wait for the binary.

Properties

$binary

$binary : ?string

Type

string|null

Methods

__construct()

__construct(\Drupal\strata\Codec\CompressionCodecInterface  $codec, string|null  $binary = null) : mixed

Constructs a trainer.

Parameters

\Drupal\strata\Codec\CompressionCodecInterface $codec

The codec candidates are scored through, so the score is what this host will actually get.

string|null $binary

Path to the zstd binary, or NULL to look for it on the PATH.

Returns

mixed —

train()

train(list  $samples) : array{bytes: string, source: string, ratio: float, samples: int}|null

Builds the best dictionary the samples support.

Parameters

list $samples

Representative payloads from one realm.

Returns

array{bytes: string, source: string, ratio: float, samples: int}|null —

The winning candidate with the ratio it measured, or NULL when there is too little data or no candidate beat compressing without a dictionary at all.

baseline()

baseline(list  $samples) : float

What a corpus compresses to without any dictionary.

The number a candidate has to beat, and the reason a realm whose payloads have nothing in common ends up with no dictionary rather than a useless one.

Parameters

list $samples

Payloads to score against.

Returns

float —

The ratio.

canTrain()

canTrain() : bool

Whether a trained candidate can be built on this host.

Returns

bool —

TRUE when the zstd binary answers.

candidates()

candidates(list  $build) : array<string,string>

Every candidate dictionary the samples support.

Parameters

list $build

The samples a candidate may be built from.

Returns

array

Source keyed to dictionary bytes.

concatenate()

concatenate(list  $build) : string

The raw content candidate: samples end to end, newest first, up to the ceiling.

Newest first because a dictionary's tail is what zstd matches most cheaply, and the samples a realm is writing now are the ones the next frames will look like.

Parameters

list $build

The samples.

Returns

string —

The dictionary bytes, empty when there is nothing to concatenate.

trainWithBinary()

trainWithBinary(list  $build) : string|null

The trained candidate, from the zstd binary.

Parameters

list $build

The samples.

Returns

string|null —

The dictionary bytes, or NULL when the binary is absent or refused.

measure()

measure(list  $samples, string|null  $dictionary) : float

The ratio a corpus compresses at, with a dictionary or without one.

Parameters

list $samples

Payloads.

string|null $dictionary

Dictionary bytes, or NULL.

Returns

float —

Raw bytes over compressed bytes, 1.0 when nothing could be measured.

resolveBinary()

resolveBinary() : string|null

The zstd binary, if there is one.

Returns

string|null —

Its path, or NULL.

temporaryDirectory()

temporaryDirectory() : string|null

A directory to write samples into.

Returns

string|null —

The path, or NULL when one could not be made.

removeDirectory()

removeDirectory(string  $path) : void

Removes the sample directory.

Parameters

string $path

The directory.

Returns

void —