Skip to content

Commit 805ecdc

Browse files
Pool the dictionary buffer when training a Zstandard dictionary
ZstandardDictionary.Train allocated 'new byte[maxDictionarySize]' on every call. Dictionary sizes are typically tens to hundreds of KB (zstd recommends up to ~100 KB, but the API allows more), so each training call paid for a fresh GC allocation that often landed on the LOH. Rent the buffer from ArrayPool<byte>.Shared instead. Create copies the trained slice into an exact-sized array before returning, so the rented buffer can be returned immediately. Use clearArray: true on Return because the trained dictionary is derived from caller-supplied samples and must not linger in the shared pool. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 1547c9a commit 805ecdc

1 file changed

Lines changed: 21 additions & 14 deletions

File tree

src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardDictionary.cs

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -127,23 +127,30 @@ public static ZstandardDictionary Train(ReadOnlySpan<byte> samples, ReadOnlySpan
127127

128128
ArgumentOutOfRangeException.ThrowIfLessThan(maxDictionarySize, 256, nameof(maxDictionarySize));
129129

130-
byte[] dictionaryBuffer = new byte[maxDictionarySize];
131-
132-
nuint dictSize;
133-
134-
unsafe
130+
byte[] dictionaryBuffer = ArrayPool<byte>.Shared.Rent(maxDictionarySize);
131+
try
135132
{
136-
fixed (byte* samplesPtr = &MemoryMarshal.GetReference(samples))
137-
fixed (byte* dictPtr = dictionaryBuffer)
138-
fixed (nuint* lengthsAsNuintPtr = &MemoryMarshal.GetReference(lengthsAsNuint))
133+
nuint dictSize;
134+
135+
unsafe
139136
{
140-
dictSize = Interop.Zstd.ZDICT_trainFromBuffer(
141-
dictPtr, (nuint)maxDictionarySize,
142-
samplesPtr, lengthsAsNuintPtr, (uint)sampleLengths.Length);
137+
fixed (byte* samplesPtr = &MemoryMarshal.GetReference(samples))
138+
fixed (byte* dictPtr = dictionaryBuffer)
139+
fixed (nuint* lengthsAsNuintPtr = &MemoryMarshal.GetReference(lengthsAsNuint))
140+
{
141+
dictSize = Interop.Zstd.ZDICT_trainFromBuffer(
142+
dictPtr, (nuint)maxDictionarySize,
143+
samplesPtr, lengthsAsNuintPtr, (uint)sampleLengths.Length);
144+
}
145+
146+
ZstandardUtils.ThrowIfError(dictSize);
147+
return Create(dictionaryBuffer.AsSpan(0, (int)dictSize));
143148
}
144-
145-
ZstandardUtils.ThrowIfError(dictSize);
146-
return Create(dictionaryBuffer.AsSpan(0, (int)dictSize));
149+
}
150+
finally
151+
{
152+
// Clear before returning: the trained dictionary is derived from caller-supplied samples.
153+
ArrayPool<byte>.Shared.Return(dictionaryBuffer, clearArray: true);
147154
}
148155
}
149156
finally

0 commit comments

Comments
 (0)