De verborgen valkuilen van arithmetic coding: waarom je compressie bits misloopt
De verborgen valkuilen van arithmetic coding: waarom je compressie bits verspilt
Arithmetic coding lijkt een slimme truc. Je zet bitreeksen om in strakke waarschijnlijkheidsbereiken. Voelt goed als je het zelf bouwt. Maar veel standaardversies presteren stilletjes matig. Niet qua snelheid, maar qua compressieverhouding. Precies waar het om draait bij entropy coding.
De klassieke val
Je kent de basisimplementatie vast. Iets als dit:
let mut left: u32 = 0;
let mut right: u32 = u32::MAX;
fn encode_bit(bit: bool, probability: f32) {
let mid = left + ((right - left) as f32 * probability) as u32;
if !bit {
right = mid;
} else {
left = mid + 1;
}
while left >> 24 == right >> 24 {
output_byte((left >> 24) as u8);
left <<= 8;
right = (right << 8) | 0xff;
}
}
Logisch: je houdt een interval [left, right] bij. Elke bit vernauwt het op basis van de kans. Zodra een byte vaststaat, spuug je hem uit en maak je ruimte.
Het euvel? Die code heeft een asymmetrie die in de theorie niet hoort.
Byte-grenzen gooien roet in het eten
In een perfecte wereld met oneindige precisie doen alle intervallen van gelijke lengte hetzelfde. Maar met 32-bit integers niet.
Neem twee gevallen:
- Interval bij
left = 0krimpt nooit onder 2^24 bits. - Bij
left = 2^31 - 1wel tot 2 bits.
Schuldige: de while-loop left >> 24 == right >> 24. Die hangt af van de positie, niet de grootte.
Als je interval een byte-grens raakt, zoals [2^31 - 1, 2^31], wordt het piepklein. Kans van 0.95 op een bit? Wordt een ruwe 50/50-split. Resultaat: meer bits dan nodig volgens de entropietheorie.
Decoder zit ook vol adders
Kijk naar de decoder:
fn decode_bit(probability: f32) -> bool {
let mid = left + ((right - left) as f32 * probability) as u32;
if x <= mid {
right = mid;
bit = false;
} else {
left = mid + 1;
bit = true;
}
while left >> 24 == right >> 24 {
left <<= 8;
right = (right << 8) | 0xff;
x = (x << 8) | (bytes.next().unwrap() as u32);
}
bit
}
Drie variabelen: left, right en x. Wiskundig volstaan lengte (right - left) en relatieve positie (x - left).
Die while-loop dwingt absolute posities af. Gevolg:
- Meer registers nodig
- Moeilijker te optimaliseren
- Code lastiger te snappen
Alleen maar door een check die eigenlijk alleen grootte zou moeten meten.
Hoe los je het op?
Dump bytes op basis van intervalgrootte, niet positie. Offset-onafhankelijk. Klinkt makkelijk, maar je moet de hele loop herzien. Voordeel: betere compressie, simpelere en snellere decoder.
Waarom dit telt bij NameOcean
Bij NameOcean helpen we devs met AI-apps op Vibe Hosting of geoptimaliseerde systemen. Compressie voor cloud storage, API's of big data? Deze basisdingen maken verschil.
Libraries overnemen zonder checks? Een 5-10% betere ratio telt op bij petabytes of miljoenen requests.
Les: 'standaard' code is zelden perfect. Graaf dieper. Check aannames en scheefgroei. Topingenieurs snappen de randgevallen.
Bouw je cloud-apps met hoge eisen? Kleine tweaks stapelen op bij massa-requests. Optimaliseer je stack? Details beslissen.