- Optimal is now a struct array, removing 4096 per-encoder object allocations.
     - Encoder literal models are flattened into one pooled contiguous BitEncoder[].
     - Decoder literal models are flattened into one non-pooled contiguous BitDecoder[].
     - Updated async decoder paths to use the flattened model base index.
     - Fixed LzBinTree pooled buffer nullability cleanup.
This commit is contained in:
Adam Hathcock
2026-05-28 06:47:22 +01:00
parent 3ddfaaa899
commit 0518137a1e
4 changed files with 70 additions and 39 deletions

View File

@@ -12,8 +12,8 @@ internal sealed class BinTree : InWindow
private uint _cyclicBufferSize;
private uint _matchMaxLen;
private uint[] _son;
private uint[] _hash;
private uint[] _son = [];
private uint[] _hash = [];
private uint _cutValue = 0xFF;
private uint _hashMask;
@@ -109,7 +109,7 @@ internal sealed class BinTree : InWindow
var cyclicBufferSize = historySize + 1;
if (_cyclicBufferSize != cyclicBufferSize)
{
if (_son is not null)
if (_son.Length != 0)
{
ArrayPool<uint>.Shared.Return(_son);
}
@@ -138,7 +138,7 @@ internal sealed class BinTree : InWindow
}
if (hs != _hashSizeSum)
{
if (_hash is not null)
if (_hash.Length != 0)
{
ArrayPool<uint>.Shared.Return(_hash);
}
@@ -150,15 +150,15 @@ internal sealed class BinTree : InWindow
public override void Dispose()
{
base.Dispose();
if (_son is not null)
if (_son.Length != 0)
{
ArrayPool<uint>.Shared.Return(_son);
_son = null;
_son = [];
}
if (_hash is not null)
if (_hash.Length != 0)
{
ArrayPool<uint>.Shared.Return(_hash);
_hash = null;
_hash = [];
}
}

View File

@@ -72,7 +72,7 @@ public partial class Decoder : IAsyncDisposable
{
symbol =
(symbol << 1)
| await _decoders[symbol]
| await _decoders[_baseIndex + symbol]
.DecodeAsync(rangeDecoder, cancellationToken)
.ConfigureAwait(false);
} while (symbol < 0x100);
@@ -90,7 +90,7 @@ public partial class Decoder : IAsyncDisposable
{
var matchBit = (uint)(matchByte >> 7) & 1;
matchByte <<= 1;
var bit = await _decoders[((1 + matchBit) << 8) + symbol]
var bit = await _decoders[_baseIndex + ((1 + matchBit) << 8) + symbol]
.DecodeAsync(rangeDecoder, cancellationToken)
.ConfigureAwait(false);
symbol = (symbol << 1) | bit;
@@ -100,7 +100,7 @@ public partial class Decoder : IAsyncDisposable
{
symbol =
(symbol << 1)
| await _decoders[symbol]
| await _decoders[_baseIndex + symbol]
.DecodeAsync(rangeDecoder, cancellationToken)
.ConfigureAwait(false);
}

View File

@@ -73,14 +73,19 @@ public partial class Decoder : ICoder, ISetDecoderProperties, IDisposable
private partial struct Decoder2
{
private BitDecoder[] _decoders;
private int _baseIndex;
public void Create() => _decoders = new BitDecoder[0x300];
public void Create(BitDecoder[] decoders, int baseIndex)
{
_decoders = decoders;
_baseIndex = baseIndex;
}
public void Init()
{
for (var i = 0; i < 0x300; i++)
{
_decoders[i].Init();
_decoders[_baseIndex + i].Init();
}
}
@@ -89,7 +94,7 @@ public partial class Decoder : ICoder, ISetDecoderProperties, IDisposable
uint symbol = 1;
do
{
symbol = (symbol << 1) | _decoders[symbol].Decode(rangeDecoder);
symbol = (symbol << 1) | _decoders[_baseIndex + symbol].Decode(rangeDecoder);
} while (symbol < 0x100);
return (byte)symbol;
}
@@ -101,13 +106,15 @@ public partial class Decoder : ICoder, ISetDecoderProperties, IDisposable
{
var matchBit = (uint)(matchByte >> 7) & 1;
matchByte <<= 1;
var bit = _decoders[((1 + matchBit) << 8) + symbol].Decode(rangeDecoder);
var bit = _decoders[_baseIndex + ((1 + matchBit) << 8) + symbol]
.Decode(rangeDecoder);
symbol = (symbol << 1) | bit;
if (matchBit != bit)
{
while (symbol < 0x100)
{
symbol = (symbol << 1) | _decoders[symbol].Decode(rangeDecoder);
symbol =
(symbol << 1) | _decoders[_baseIndex + symbol].Decode(rangeDecoder);
}
break;
}
@@ -117,6 +124,7 @@ public partial class Decoder : ICoder, ISetDecoderProperties, IDisposable
}
private Decoder2[] _coders;
private BitDecoder[] _models;
private int _numPrevBits;
private int _numPosBits;
private uint _posMask;
@@ -131,10 +139,11 @@ public partial class Decoder : ICoder, ISetDecoderProperties, IDisposable
_posMask = ((uint)1 << numPosBits) - 1;
_numPrevBits = numPrevBits;
var numStates = (uint)1 << (_numPrevBits + _numPosBits);
_models = new BitDecoder[numStates * 0x300];
_coders = new Decoder2[numStates];
for (uint i = 0; i < numStates; i++)
{
_coders[i].Create();
_coders[i].Create(_models, checked((int)(i * 0x300)));
}
}

View File

@@ -1,6 +1,7 @@
#nullable disable
using System;
using System.Buffers;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
@@ -86,14 +87,19 @@ internal partial class Encoder : ICoder, ISetCoderProperties, IWriteCoderPropert
public partial struct Encoder2
{
private BitEncoder[] _encoders;
private int _baseIndex;
public void Create() => _encoders = new BitEncoder[0x300];
public void Create(BitEncoder[] encoders, int baseIndex)
{
_encoders = encoders;
_baseIndex = baseIndex;
}
public void Init()
{
for (var i = 0; i < 0x300; i++)
{
_encoders[i].Init();
_encoders[_baseIndex + i].Init();
}
}
@@ -103,7 +109,7 @@ internal partial class Encoder : ICoder, ISetCoderProperties, IWriteCoderPropert
for (var i = 7; i >= 0; i--)
{
var bit = (uint)((symbol >> i) & 1);
_encoders[context].Encode(rangeEncoder, bit);
_encoders[_baseIndex + context].Encode(rangeEncoder, bit);
context = (context << 1) | bit;
}
}
@@ -118,7 +124,7 @@ internal partial class Encoder : ICoder, ISetCoderProperties, IWriteCoderPropert
for (var i = 7; i >= 0; i--)
{
var bit = (uint)((symbol >> i) & 1);
await _encoders[context]
await _encoders[_baseIndex + context]
.EncodeAsync(rangeEncoder, bit, cancellationToken)
.ConfigureAwait(false);
context = (context << 1) | bit;
@@ -139,7 +145,7 @@ internal partial class Encoder : ICoder, ISetCoderProperties, IWriteCoderPropert
state += ((1 + matchBit) << 8);
same = (matchBit == bit);
}
_encoders[state].Encode(rangeEncoder, bit);
_encoders[_baseIndex + state].Encode(rangeEncoder, bit);
context = (context << 1) | bit;
}
}
@@ -163,7 +169,7 @@ internal partial class Encoder : ICoder, ISetCoderProperties, IWriteCoderPropert
state += ((1 + matchBit) << 8);
same = matchBit == bit;
}
await _encoders[state]
await _encoders[_baseIndex + state]
.EncodeAsync(rangeEncoder, bit, cancellationToken)
.ConfigureAwait(false);
context = (context << 1) | bit;
@@ -181,7 +187,8 @@ internal partial class Encoder : ICoder, ISetCoderProperties, IWriteCoderPropert
{
var matchBit = (uint)(matchByte >> i) & 1;
var bit = (uint)(symbol >> i) & 1;
price += _encoders[((1 + matchBit) << 8) + context].GetPrice(bit);
price += _encoders[_baseIndex + ((1 + matchBit) << 8) + context]
.GetPrice(bit);
context = (context << 1) | bit;
if (matchBit != bit)
{
@@ -193,7 +200,7 @@ internal partial class Encoder : ICoder, ISetCoderProperties, IWriteCoderPropert
for (; i >= 0; i--)
{
var bit = (uint)(symbol >> i) & 1;
price += _encoders[context].GetPrice(bit);
price += _encoders[_baseIndex + context].GetPrice(bit);
context = (context << 1) | bit;
}
return price;
@@ -201,6 +208,7 @@ internal partial class Encoder : ICoder, ISetCoderProperties, IWriteCoderPropert
}
private Encoder2[] _coders;
private BitEncoder[] _models;
private int _numPrevBits;
private int _numPosBits;
private uint _posMask;
@@ -215,13 +223,30 @@ internal partial class Encoder : ICoder, ISetCoderProperties, IWriteCoderPropert
_posMask = ((uint)1 << numPosBits) - 1;
_numPrevBits = numPrevBits;
var numStates = (uint)1 << (_numPrevBits + _numPosBits);
if (_models is not null)
{
ArrayPool<BitEncoder>.Shared.Return(_models);
}
_models = ArrayPool<BitEncoder>.Shared.Rent(checked((int)(numStates * 0x300)));
_coders = new Encoder2[numStates];
for (uint i = 0; i < numStates; i++)
{
_coders[i].Create();
_coders[i].Create(_models, checked((int)(i * 0x300)));
}
}
public void Dispose()
{
if (_models is null)
{
return;
}
ArrayPool<BitEncoder>.Shared.Return(_models);
_models = null;
_coders = null;
}
public void Init()
{
var numStates = (uint)1 << (_numPrevBits + _numPosBits);
@@ -423,7 +448,7 @@ internal partial class Encoder : ICoder, ISetCoderProperties, IWriteCoderPropert
private const uint K_NUM_OPTS = 1 << 12;
private class Optimal
private struct Optimal
{
public Base.State _state;
@@ -564,10 +589,6 @@ internal partial class Encoder : ICoder, ISetCoderProperties, IWriteCoderPropert
public Encoder()
{
for (var i = 0; i < K_NUM_OPTS; i++)
{
_optimum[i] = new Optimal();
}
for (var i = 0; i < Base.K_NUM_LEN_TO_POS_STATES; i++)
{
_posSlotEncoder[i] = new BitTreeEncoder(Base.K_NUM_POS_SLOT_BITS);
@@ -576,6 +597,7 @@ internal partial class Encoder : ICoder, ISetCoderProperties, IWriteCoderPropert
public void Dispose()
{
_literalEncoder.Dispose();
_matchFinder?.Dispose();
_matchFinder = null;
}
@@ -865,7 +887,7 @@ internal partial class Encoder : ICoder, ISetCoderProperties, IWriteCoderPropert
do
{
var curAndLenPrice = price + _repMatchLenEncoder.GetPrice(repLen - 2, posState);
var optimum = _optimum[repLen];
ref var optimum = ref _optimum[repLen];
if (curAndLenPrice < optimum._price)
{
optimum._price = curAndLenPrice;
@@ -890,7 +912,7 @@ internal partial class Encoder : ICoder, ISetCoderProperties, IWriteCoderPropert
{
var distance = _matchDistances[offs + 1];
var curAndLenPrice = normalMatchPrice + GetPosLenPrice(distance, len, posState);
var optimum = _optimum[len];
ref var optimum = ref _optimum[len];
if (curAndLenPrice < optimum._price)
{
optimum._price = curAndLenPrice;
@@ -1045,7 +1067,7 @@ internal partial class Encoder : ICoder, ISetCoderProperties, IWriteCoderPropert
.GetSubCoder(position, _matchFinder.GetIndexByte(0 - 2))
.GetPrice(!state.IsCharState(), matchByte, currentByte);
var nextOptimum = _optimum[cur + 1];
ref var nextOptimum = ref _optimum[cur + 1];
var nextIsChar = false;
if (curAnd1Price < nextOptimum._price)
@@ -1111,7 +1133,7 @@ internal partial class Encoder : ICoder, ISetCoderProperties, IWriteCoderPropert
}
var curAndLenPrice =
nextRepMatchPrice + GetRepPrice(0, lenTest2, state2, posStateNext);
var optimum = _optimum[offset];
ref var optimum = ref _optimum[offset];
if (curAndLenPrice < optimum._price)
{
optimum._price = curAndLenPrice;
@@ -1142,7 +1164,7 @@ internal partial class Encoder : ICoder, ISetCoderProperties, IWriteCoderPropert
}
var curAndLenPrice =
repMatchPrice + GetRepPrice(repIndex, lenTest, state, posState);
var optimum = _optimum[cur + lenTest];
ref var optimum = ref _optimum[cur + lenTest];
if (curAndLenPrice < optimum._price)
{
optimum._price = curAndLenPrice;
@@ -1206,7 +1228,7 @@ internal partial class Encoder : ICoder, ISetCoderProperties, IWriteCoderPropert
}
var curAndLenPrice =
nextRepMatchPrice + GetRepPrice(0, lenTest2, state2, posStateNext);
var optimum = _optimum[cur + offset];
ref var optimum = ref _optimum[cur + offset];
if (curAndLenPrice < optimum._price)
{
optimum._price = curAndLenPrice;
@@ -1255,7 +1277,7 @@ internal partial class Encoder : ICoder, ISetCoderProperties, IWriteCoderPropert
var curBack = _matchDistances[offs + 1];
var curAndLenPrice =
normalMatchPrice + GetPosLenPrice(curBack, lenTest, posState);
var optimum = _optimum[cur + lenTest];
ref var optimum = ref _optimum[cur + lenTest];
if (curAndLenPrice < optimum._price)
{
optimum._price = curAndLenPrice;
@@ -1314,7 +1336,7 @@ internal partial class Encoder : ICoder, ISetCoderProperties, IWriteCoderPropert
curAndLenPrice =
nextRepMatchPrice
+ GetRepPrice(0, lenTest2, state2, posStateNext);
optimum = _optimum[cur + offset];
optimum = ref _optimum[cur + offset];
if (curAndLenPrice < optimum._price)
{
optimum._price = curAndLenPrice;