Browse Source

More speedup and bounds checks removal.

af/merge-core
James Jackson-South 6 years ago
parent
commit
6d56192970
  1. 198
      src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs
  2. 18
      src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs

198
src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs

@ -64,6 +64,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
private int extraBits;
private bool isDisposed;
// TODO: These should be pre-generated array/readonlyspans.
static DeflaterHuffman()
{
// See RFC 1951 3.2.6
@ -114,9 +115,9 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
this.Pending = new DeflaterPendingBuffer(memoryAllocator);
this.literalTree = new Tree(LiteralNumber, 257, 15);
this.distTree = new Tree(DistanceNumber, 1, 15);
this.blTree = new Tree(BitLengthNumber, 4, 7);
this.literalTree = new Tree(memoryAllocator, LiteralNumber, 257, 15);
this.distTree = new Tree(memoryAllocator, DistanceNumber, 1, 15);
this.blTree = new Tree(memoryAllocator, BitLengthNumber, 4, 7);
this.distanceManagedBuffer = memoryAllocator.Allocate<short>(BufferSize);
this.distanceBufferHandle = this.distanceManagedBuffer.Memory.Pin();
@ -135,6 +136,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// <summary>
/// Reset internal state
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public void Reset()
{
this.lastLiteral = 0;
@ -214,6 +216,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// <param name="storedOffset">Index of first byte to write</param>
/// <param name="storedLength">Count of bytes to write</param>
/// <param name="lastBlock">True if this is the last block</param>
[MethodImpl(InliningOptions.ShortMethod)]
public void FlushStoredBlock(byte[] stored, int storedOffset, int storedLength, bool lastBlock)
{
this.Pending.WriteBits((DeflaterConstants.STORED_BLOCK << 1) + (lastBlock ? 1 : 0), 3);
@ -233,7 +236,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// <param name="lastBlock">True if this is the last block</param>
public void FlushBlock(byte[] stored, int storedOffset, int storedLength, bool lastBlock)
{
this.literalTree.Freqs[EofSymbol]++;
this.literalTree.Frequencies[EofSymbol]++;
// Build trees
this.literalTree.BuildTree();
@ -263,13 +266,13 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
ref byte staticLLengthRef = ref MemoryMarshal.GetReference<byte>(StaticLLength);
for (int i = 0; i < LiteralNumber; i++)
{
static_len += this.literalTree.Freqs[i] * Unsafe.Add(ref staticLLengthRef, i);
static_len += this.literalTree.Frequencies[i] * Unsafe.Add(ref staticLLengthRef, i);
}
ref byte staticDLengthRef = ref MemoryMarshal.GetReference<byte>(StaticDLength);
for (int i = 0; i < DistanceNumber; i++)
{
static_len += this.distTree.Freqs[i] * Unsafe.Add(ref staticDLengthRef, i);
static_len += this.distTree.Frequencies[i] * Unsafe.Add(ref staticDLengthRef, i);
}
if (opt_len >= static_len)
@ -319,7 +322,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
this.pinnedDistanceBuffer[this.lastLiteral] = 0;
this.pinnedLiteralBuffer[this.lastLiteral++] = (byte)literal;
this.literalTree.Freqs[literal]++;
this.literalTree.Frequencies[literal]++;
return this.IsFull();
}
@ -336,14 +339,14 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.pinnedLiteralBuffer[this.lastLiteral++] = (byte)(length - 3);
int lc = Lcode(length - 3);
this.literalTree.Freqs[lc]++;
this.literalTree.Frequencies[lc]++;
if (lc >= 265 && lc < 285)
{
this.extraBits += (lc - 261) / 4;
}
int dc = Dcode(distance - 1);
this.distTree.Freqs[dc]++;
this.distTree.Frequencies[dc]++;
if (dc >= 4)
{
this.extraBits += (dc / 2) - 1;
@ -415,33 +418,69 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.distanceManagedBuffer.Dispose();
this.literalBufferHandle.Dispose();
this.literalManagedBuffer.Dispose();
this.literalTree.Dispose();
this.blTree.Dispose();
this.distTree.Dispose();
}
this.Pending = null;
this.literalTree = null;
this.blTree = null;
this.distTree = null;
this.isDisposed = true;
}
}
private sealed class Tree
private sealed class Tree : IDisposable
{
private readonly int minNumCodes;
private short[] codes;
private readonly int[] bitLengthCounts;
private readonly int maxLength;
private bool isDisposed;
public Tree(int elements, int minCodes, int maxLength)
private readonly int elementCount;
private IMemoryOwner<short> codesMemoryOwner;
private MemoryHandle codesMemoryHandle;
private short* codes;
private IMemoryOwner<short> frequenciesMemoryOwner;
private MemoryHandle frequenciesMemoryHandle;
private IManagedByteBuffer lengthsMemoryOwner;
private MemoryHandle lengthsMemoryHandle;
public Tree(MemoryAllocator memoryAllocator, int elements, int minCodes, int maxLength)
{
this.elementCount = elements;
this.minNumCodes = minCodes;
this.maxLength = maxLength;
this.Freqs = new short[elements];
this.frequenciesMemoryOwner = memoryAllocator.Allocate<short>(elements);
this.frequenciesMemoryHandle = this.frequenciesMemoryOwner.Memory.Pin();
this.Frequencies = (short*)this.frequenciesMemoryHandle.Pointer;
this.lengthsMemoryOwner = memoryAllocator.AllocateManagedByteBuffer(elements);
this.lengthsMemoryHandle = this.lengthsMemoryOwner.Memory.Pin();
this.Length = (byte*)this.lengthsMemoryHandle.Pointer;
this.codesMemoryOwner = memoryAllocator.Allocate<short>(elements);
this.codesMemoryHandle = this.codesMemoryOwner.Memory.Pin();
this.codes = (short*)this.codesMemoryHandle.Pointer;
// Maxes out at 15.
this.bitLengthCounts = new int[maxLength];
}
public int NumCodes { get; private set; }
public short[] Freqs { get; }
public short* Frequencies { get; }
public byte[] Length { get; set; }
public byte* Length { get; }
/// <summary>
/// Resets the internal state of the tree
@ -449,13 +488,9 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
[MethodImpl(InliningOptions.ShortMethod)]
public void Reset()
{
for (int i = 0; i < this.Freqs.Length; i++)
{
this.Freqs[i] = 0;
}
this.codes = null;
this.Length = null;
this.frequenciesMemoryOwner.Memory.Span.Clear();
this.lengthsMemoryOwner.Memory.Span.Clear();
this.codesMemoryOwner.Memory.Span.Clear();
}
[MethodImpl(InliningOptions.ShortMethod)]
@ -472,9 +507,9 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
public void CheckEmpty()
{
bool empty = true;
for (int i = 0; i < this.Freqs.Length; i++)
for (int i = 0; i < this.elementCount; i++)
{
empty &= this.Freqs[i] == 0;
empty &= this.Frequencies[i] == 0;
}
if (!empty)
@ -488,10 +523,11 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// </summary>
/// <param name="staticCodes">new codes</param>
/// <param name="staticLengths">length for new codes</param>
public void SetStaticCodes(short[] staticCodes, byte[] staticLengths)
[MethodImpl(InliningOptions.ShortMethod)]
public void SetStaticCodes(ReadOnlySpan<short> staticCodes, ReadOnlySpan<byte> staticLengths)
{
this.codes = staticCodes;
this.Length = staticLengths;
staticCodes.CopyTo(this.codesMemoryOwner.Memory.Span);
staticLengths.CopyTo(this.lengthsMemoryOwner.Memory.Span);
}
/// <summary>
@ -499,15 +535,16 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// </summary>
public void BuildCodes()
{
int[] nextCode = new int[this.maxLength];
int code = 0;
this.codes = new short[this.Freqs.Length];
// Maxes out at 15 * 4
Span<int> nextCode = stackalloc int[this.maxLength];
ref int nextCodeRef = ref MemoryMarshal.GetReference(nextCode);
ref int bitLengthCountsRef = ref MemoryMarshal.GetReference<int>(this.bitLengthCounts);
int code = 0;
for (int bits = 0; bits < this.maxLength; bits++)
{
nextCode[bits] = code;
code += this.bitLengthCounts[bits] << (15 - bits);
Unsafe.Add(ref nextCodeRef, bits) = code;
code += Unsafe.Add(ref bitLengthCountsRef, bits) << (15 - bits);
}
for (int i = 0; i < this.NumCodes; i++)
@ -515,15 +552,15 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
int bits = this.Length[i];
if (bits > 0)
{
this.codes[i] = BitReverse(nextCode[bits - 1]);
nextCode[bits - 1] += 1 << (16 - bits);
this.codes[i] = BitReverse(Unsafe.Add(ref nextCodeRef, bits - 1));
Unsafe.Add(ref nextCodeRef, bits - 1) += 1 << (16 - bits);
}
}
}
public void BuildTree()
{
int numSymbols = this.Freqs.Length;
int numSymbols = this.elementCount;
// heap is a priority queue, sorted by frequency, least frequent
// nodes first. The heap is a binary tree, with the property, that
@ -532,20 +569,23 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
//
// The binary tree is encoded in an array: 0 is root node and
// the nodes 2*n+1, 2*n+2 are the child nodes of node n.
int[] heap = new int[numSymbols];
// Maxes out at 286 * 4
Span<int> heap = stackalloc int[numSymbols];
ref int heapRef = ref MemoryMarshal.GetReference(heap);
int heapLen = 0;
int maxCode = 0;
for (int n = 0; n < numSymbols; n++)
{
int freq = this.Freqs[n];
int freq = this.Frequencies[n];
if (freq != 0)
{
// Insert n into heap
int pos = heapLen++;
int ppos;
while (pos > 0 && this.Freqs[heap[ppos = (pos - 1) / 2]] > freq)
while (pos > 0 && this.Frequencies[Unsafe.Add(ref heapRef, ppos = (pos - 1) / 2)] > freq)
{
heap[pos] = heap[ppos];
Unsafe.Add(ref heapRef, pos) = Unsafe.Add(ref heapRef, ppos);
pos = ppos;
}
@ -561,7 +601,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
// this case, both literals get a 1 bit code.
while (heapLen < 2)
{
heap[heapLen++] = maxCode < 2 ? ++maxCode : 0;
Unsafe.Add(ref heapRef, heapLen++) = maxCode < 2 ? ++maxCode : 0;
}
this.NumCodes = Math.Max(maxCode + 1, this.minNumCodes);
@ -572,10 +612,10 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
int numNodes = numLeafs;
for (int i = 0; i < heapLen; i++)
{
int node = heap[i];
int node = Unsafe.Add(ref heapRef, i);
childs[2 * i] = node;
childs[(2 * i) + 1] = -1;
values[i] = this.Freqs[node] << 8;
values[i] = this.Frequencies[node] << 8;
heap[i] = i;
}
@ -584,7 +624,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
do
{
int first = heap[0];
int last = heap[--heapLen];
int last = Unsafe.Add(ref heapRef, --heapLen);
// Propagate the hole to the leafs of the heap
int ppos = 0;
@ -592,12 +632,12 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
while (path < heapLen)
{
if (path + 1 < heapLen && values[heap[path]] > values[heap[path + 1]])
if (path + 1 < heapLen && values[Unsafe.Add(ref heapRef, path)] > values[Unsafe.Add(ref heapRef, path + 1)])
{
path++;
}
heap[ppos] = heap[path];
Unsafe.Add(ref heapRef, ppos) = Unsafe.Add(ref heapRef, path);
ppos = path;
path = (path * 2) + 1;
}
@ -605,14 +645,14 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
// Now propagate the last element down along path. Normally
// it shouldn't go too deep.
int lastVal = values[last];
while ((path = ppos) > 0 && values[heap[ppos = (path - 1) / 2]] > lastVal)
while ((path = ppos) > 0 && values[Unsafe.Add(ref heapRef, ppos = (path - 1) / 2)] > lastVal)
{
heap[path] = heap[ppos];
Unsafe.Add(ref heapRef, path) = Unsafe.Add(ref heapRef, ppos);
}
heap[path] = last;
Unsafe.Add(ref heapRef, path) = last;
int second = heap[0];
int second = Unsafe.Add(ref heapRef, 0);
// Create a new node father of first and second
last = numNodes++;
@ -627,27 +667,27 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
while (path < heapLen)
{
if (path + 1 < heapLen && values[heap[path]] > values[heap[path + 1]])
if (path + 1 < heapLen && values[Unsafe.Add(ref heapRef, path)] > values[Unsafe.Add(ref heapRef, path + 1)])
{
path++;
}
heap[ppos] = heap[path];
Unsafe.Add(ref heapRef, ppos) = Unsafe.Add(ref heapRef, path);
ppos = path;
path = (ppos * 2) + 1;
}
// Now propagate the new element down along path
while ((path = ppos) > 0 && values[heap[ppos = (path - 1) / 2]] > lastVal)
while ((path = ppos) > 0 && values[Unsafe.Add(ref heapRef, ppos = (path - 1) / 2)] > lastVal)
{
heap[path] = heap[ppos];
Unsafe.Add(ref heapRef, path) = Unsafe.Add(ref heapRef, ppos);
}
heap[path] = last;
Unsafe.Add(ref heapRef, path) = last;
}
while (heapLen > 1);
if (heap[0] != (childs.Length / 2) - 1)
if (Unsafe.Add(ref heapRef, 0) != (childs.Length / 2) - 1)
{
DeflateThrowHelper.ThrowHeapViolated();
}
@ -659,12 +699,13 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// Get encoded length
/// </summary>
/// <returns>Encoded length, the sum of frequencies * lengths</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetEncodedLength()
{
int len = 0;
for (int i = 0; i < this.Freqs.Length; i++)
for (int i = 0; i < this.elementCount; i++)
{
len += this.Freqs[i] * this.Length[i];
len += this.Frequencies[i] * this.Length[i];
}
return len;
@ -697,7 +738,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
min_count = 3;
if (curlen != nextlen)
{
blTree.Freqs[nextlen]++;
blTree.Frequencies[nextlen]++;
count = 0;
}
}
@ -716,19 +757,19 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
if (count < min_count)
{
blTree.Freqs[curlen] += (short)count;
blTree.Frequencies[curlen] += (short)count;
}
else if (curlen != 0)
{
blTree.Freqs[Repeat3To6]++;
blTree.Frequencies[Repeat3To6]++;
}
else if (count <= 10)
{
blTree.Freqs[Repeat3To10]++;
blTree.Frequencies[Repeat3To10]++;
}
else
{
blTree.Freqs[Repeat11To138]++;
blTree.Frequencies[Repeat11To138]++;
}
}
}
@ -805,7 +846,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
private void BuildLength(int[] children)
{
this.Length = new byte[this.Freqs.Length];
int numNodes = children.Length / 2;
int numLeafs = (numNodes + 1) / 2;
int overflow = 0;
@ -894,6 +934,36 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
}
}
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!this.isDisposed)
{
if (disposing)
{
this.frequenciesMemoryHandle.Dispose();
this.frequenciesMemoryOwner.Dispose();
this.lengthsMemoryHandle.Dispose();
this.lengthsMemoryOwner.Dispose();
this.codesMemoryHandle.Dispose();
this.codesMemoryOwner.Dispose();
}
this.frequenciesMemoryOwner = null;
this.lengthsMemoryOwner = null;
this.codesMemoryOwner = null;
this.isDisposed = true;
}
}
}
}
}

18
src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs

@ -15,8 +15,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
private readonly byte[] buffer;
private readonly byte* pinnedBuffer;
private readonly IManagedByteBuffer managedBuffer;
private MemoryHandle handle;
private IManagedByteBuffer bufferMemoryOwner;
private MemoryHandle bufferMemoryHandle;
private int start;
private int end;
@ -29,10 +29,10 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// <param name="memoryAllocator">The memory allocator to use for buffer allocations.</param>
public DeflaterPendingBuffer(MemoryAllocator memoryAllocator)
{
this.managedBuffer = memoryAllocator.AllocateManagedByteBuffer(DeflaterConstants.PENDING_BUF_SIZE);
this.buffer = this.managedBuffer.Array;
this.handle = this.managedBuffer.Memory.Pin();
this.pinnedBuffer = (byte*)this.handle.Pointer;
this.bufferMemoryOwner = memoryAllocator.AllocateManagedByteBuffer(DeflaterConstants.PENDING_BUF_SIZE);
this.buffer = this.bufferMemoryOwner.Array;
this.bufferMemoryHandle = this.bufferMemoryOwner.Memory.Pin();
this.pinnedBuffer = (byte*)this.bufferMemoryHandle.Pointer;
}
/// <summary>
@ -175,10 +175,12 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
if (disposing)
{
this.handle.Dispose();
this.managedBuffer.Dispose();
this.bufferMemoryHandle.Dispose();
this.bufferMemoryOwner.Dispose();
}
this.bufferMemoryOwner = null;
this.isDisposed = true;
}
}

Loading…
Cancel
Save