Browse Source

Merge pull request #1201 from SixLabors/js/fast-hash

Add Hardware Accelerated Checksums
pull/1203/head
James Jackson-South 6 years ago
committed by GitHub
parent
commit
4002a9759d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 14
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  2. 13
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  3. 326
      src/ImageSharp/Formats/Png/Zlib/Adler32.cs
  4. 70
      src/ImageSharp/Formats/Png/Zlib/Crc32.Lut.cs
  5. 295
      src/ImageSharp/Formats/Png/Zlib/Crc32.cs
  6. 43
      src/ImageSharp/Formats/Png/Zlib/IChecksum.cs
  7. 10
      src/ImageSharp/Formats/Png/Zlib/README.md
  8. 8
      src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs
  9. BIN
      src/ImageSharp/Formats/Png/Zlib/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf
  10. 1
      tests/Directory.Build.targets
  11. 72
      tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs
  12. 72
      tests/ImageSharp.Benchmarks/General/Crc32Benchmark.cs
  13. 1
      tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
  14. 41
      tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs
  15. 41
      tests/ImageSharp.Tests/Formats/Png/Crc32Tests.cs
  16. 18
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs
  17. 1
      tests/ImageSharp.Tests/ImageSharp.Tests.csproj

14
src/ImageSharp/Formats/Png/PngDecoderCore.cs

@ -30,11 +30,6 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary> /// </summary>
private readonly byte[] buffer = new byte[4]; private readonly byte[] buffer = new byte[4];
/// <summary>
/// Reusable CRC for validating chunks.
/// </summary>
private readonly Crc32 crc = new Crc32();
/// <summary> /// <summary>
/// The global configuration. /// The global configuration.
/// </summary> /// </summary>
@ -1159,18 +1154,17 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="chunk">The <see cref="PngChunk"/>.</param> /// <param name="chunk">The <see cref="PngChunk"/>.</param>
private void ValidateChunk(in PngChunk chunk) private void ValidateChunk(in PngChunk chunk)
{ {
uint crc = this.ReadChunkCrc(); uint inputCrc = this.ReadChunkCrc();
if (chunk.IsCritical) if (chunk.IsCritical)
{ {
Span<byte> chunkType = stackalloc byte[4]; Span<byte> chunkType = stackalloc byte[4];
BinaryPrimitives.WriteUInt32BigEndian(chunkType, (uint)chunk.Type); BinaryPrimitives.WriteUInt32BigEndian(chunkType, (uint)chunk.Type);
this.crc.Reset(); uint validCrc = Crc32.Calculate(chunkType);
this.crc.Update(chunkType); validCrc = Crc32.Calculate(validCrc, chunk.Data.GetSpan());
this.crc.Update(chunk.Data.GetSpan());
if (this.crc.Value != crc) if (validCrc != inputCrc)
{ {
string chunkTypeName = Encoding.ASCII.GetString(chunkType); string chunkTypeName = Encoding.ASCII.GetString(chunkType);
PngThrowHelper.ThrowInvalidChunkCrc(chunkTypeName); PngThrowHelper.ThrowInvalidChunkCrc(chunkTypeName);

13
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -47,11 +47,6 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary> /// </summary>
private readonly byte[] chunkDataBuffer = new byte[16]; private readonly byte[] chunkDataBuffer = new byte[16];
/// <summary>
/// Reusable CRC for validating chunks.
/// </summary>
private readonly Crc32 crc = new Crc32();
/// <summary> /// <summary>
/// The encoder options /// The encoder options
/// </summary> /// </summary>
@ -1120,18 +1115,16 @@ namespace SixLabors.ImageSharp.Formats.Png
stream.Write(this.buffer, 0, 8); stream.Write(this.buffer, 0, 8);
this.crc.Reset(); uint crc = Crc32.Calculate(this.buffer.AsSpan(4, 4)); // Write the type buffer
this.crc.Update(this.buffer.AsSpan(4, 4)); // Write the type buffer
if (data != null && length > 0) if (data != null && length > 0)
{ {
stream.Write(data, offset, length); stream.Write(data, offset, length);
this.crc.Update(data.AsSpan(offset, length)); crc = Crc32.Calculate(crc, data.AsSpan(offset, length));
} }
BinaryPrimitives.WriteUInt32BigEndian(this.buffer, (uint)this.crc.Value); BinaryPrimitives.WriteUInt32BigEndian(this.buffer, crc);
stream.Write(this.buffer, 0, 4); // write the crc stream.Write(this.buffer, 0, 4); // write the crc
} }

326
src/ImageSharp/Formats/Png/Zlib/Adler32.cs

@ -3,146 +3,258 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; #if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
#pragma warning disable IDE0007 // Use implicit type
namespace SixLabors.ImageSharp.Formats.Png.Zlib namespace SixLabors.ImageSharp.Formats.Png.Zlib
{ {
/// <summary> /// <summary>
/// Computes Adler32 checksum for a stream of data. An Adler32 /// Calculates the 32 bit Adler checksum of a given buffer according to
/// checksum is not as reliable as a CRC32 checksum, but a lot faster to /// RFC 1950. ZLIB Compressed Data Format Specification version 3.3)
/// compute.
/// </summary> /// </summary>
/// <remarks> internal static class Adler32
/// The specification for Adler32 may be found in RFC 1950.
/// ZLIB Compressed Data Format Specification version 3.3)
///
///
/// From that document:
///
/// "ADLER32 (Adler-32 checksum)
/// This contains a checksum value of the uncompressed data
/// (excluding any dictionary data) computed according to Adler-32
/// algorithm. This algorithm is a 32-bit extension and improvement
/// of the Fletcher algorithm, used in the ITU-T X.224 / ISO 8073
/// standard.
///
/// Adler-32 is composed of two sums accumulated per byte: s1 is
/// the sum of all bytes, s2 is the sum of all s1 values. Both sums
/// are done modulo 65521. s1 is initialized to 1, s2 to zero. The
/// Adler-32 checksum is stored as s2*65536 + s1 in most-
/// significant-byte first (network) order."
///
/// "8.2. The Adler-32 algorithm
///
/// The Adler-32 algorithm is much faster than the CRC32 algorithm yet
/// still provides an extremely low probability of undetected errors.
///
/// The modulo on unsigned long accumulators can be delayed for 5552
/// bytes, so the modulo operation time is negligible. If the bytes
/// are a, b, c, the second sum is 3a + 2b + c + 3, and so is position
/// and order sensitive, unlike the first sum, which is just a
/// checksum. That 65521 is prime is important to avoid a possible
/// large class of two-byte errors that leave the check unchanged.
/// (The Fletcher checksum uses 255, which is not prime and which also
/// makes the Fletcher check insensitive to single byte changes 0 -
/// 255.)
///
/// The sum s1 is initialized to 1 instead of zero to make the length
/// of the sequence part of s2, so that the length does not have to be
/// checked separately. (Any sequence of zeroes has a Fletcher
/// checksum of zero.)"
/// </remarks>
/// <see cref="ZlibInflateStream"/>
/// <see cref="ZlibDeflateStream"/>
internal sealed class Adler32 : IChecksum
{ {
/// <summary> /// <summary>
/// largest prime smaller than 65536 /// The default initial seed value of a Adler32 checksum calculation.
/// </summary> /// </summary>
private const uint Base = 65521; public const uint SeedValue = 1U;
/// <summary> // Largest prime smaller than 65536
/// The checksum calculated to far. private const uint BASE = 65521;
/// </summary>
private uint checksum;
/// <summary> // NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1
/// Initializes a new instance of the <see cref="Adler32"/> class. private const uint NMAX = 5552;
/// The checksum starts off with a value of 1.
/// </summary>
public Adler32()
{
this.Reset();
}
/// <inheritdoc/> #if SUPPORTS_RUNTIME_INTRINSICS
public long Value private const int MinBufferSize = 64;
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.checksum;
}
/// <inheritdoc/> // The C# compiler emits this as a compile-time constant embedded in the PE file.
[MethodImpl(MethodImplOptions.AggressiveInlining)] private static ReadOnlySpan<byte> Tap1Tap2 => new byte[]
public void Reset()
{ {
this.checksum = 1; 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, // tap1
} 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 // tap2
};
#endif
/// <summary>
/// Calculates the Adler32 checksum with the bytes taken from the span.
/// </summary>
/// <param name="buffer">The readonly span of bytes.</param>
/// <returns>The <see cref="uint"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static uint Calculate(ReadOnlySpan<byte> buffer)
=> Calculate(SeedValue, buffer);
/// <summary> /// <summary>
/// Updates the checksum with a byte value. /// Calculates the Adler32 checksum with the bytes taken from the span and seed.
/// </summary> /// </summary>
/// <param name="value"> /// <param name="adler">The input Adler32 value.</param>
/// The data value to add. The high byte of the int is ignored. /// <param name="buffer">The readonly span of bytes.</param>
/// </param> /// <returns>The <see cref="uint"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
public void Update(int value) public static uint Calculate(uint adler, ReadOnlySpan<byte> buffer)
{ {
// We could make a length 1 byte array and call update again, but I if (buffer.IsEmpty)
// would rather not have that overhead {
uint s1 = this.checksum & 0xFFFF; return SeedValue;
uint s2 = this.checksum >> 16; }
s1 = (s1 + ((uint)value & 0xFF)) % Base; #if SUPPORTS_RUNTIME_INTRINSICS
s2 = (s1 + s2) % Base; if (Sse3.IsSupported && buffer.Length >= MinBufferSize)
{
return CalculateSse(adler, buffer);
}
this.checksum = (s2 << 16) + s1; return CalculateScalar(adler, buffer);
#else
return CalculateScalar(adler, buffer);
#endif
} }
/// <inheritdoc/> // Based on https://github.com/chromium/chromium/blob/master/third_party/zlib/adler32_simd.c
[MethodImpl(MethodImplOptions.AggressiveInlining)] #if SUPPORTS_RUNTIME_INTRINSICS
public void Update(ReadOnlySpan<byte> data) [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
private static unsafe uint CalculateSse(uint adler, ReadOnlySpan<byte> buffer)
{ {
ref byte dataRef = ref MemoryMarshal.GetReference(data); uint s1 = adler & 0xFFFF;
uint s1 = this.checksum & 0xFFFF; uint s2 = (adler >> 16) & 0xFFFF;
uint s2 = this.checksum >> 16;
int count = data.Length; // Process the data in blocks.
int offset = 0; const int BLOCK_SIZE = 1 << 5;
while (count > 0) uint length = (uint)buffer.Length;
uint blocks = length / BLOCK_SIZE;
length -= blocks * BLOCK_SIZE;
int index = 0;
fixed (byte* bufferPtr = buffer)
fixed (byte* tapPtr = Tap1Tap2)
{ {
// We can defer the modulo operation: index += (int)blocks * BLOCK_SIZE;
// s1 maximally grows from 65521 to 65521 + 255 * 3800 var localBufferPtr = bufferPtr;
// s2 maximally grows by 3800 * median(s1) = 2090079800 < 2^31
int n = 3800; // _mm_setr_epi8 on x86
if (n > count) Vector128<sbyte> tap1 = Sse2.LoadVector128((sbyte*)tapPtr);
Vector128<sbyte> tap2 = Sse2.LoadVector128((sbyte*)(tapPtr + 0x10));
Vector128<byte> zero = Vector128<byte>.Zero;
var ones = Vector128.Create((short)1);
while (blocks > 0)
{ {
n = count; uint n = NMAX / BLOCK_SIZE; /* The NMAX constraint. */
if (n > blocks)
{
n = blocks;
}
blocks -= n;
// Process n blocks of data. At most NMAX data bytes can be
// processed before s2 must be reduced modulo BASE.
Vector128<uint> v_ps = Vector128.CreateScalar(s1 * n);
Vector128<uint> v_s2 = Vector128.CreateScalar(s2);
Vector128<uint> v_s1 = Vector128<uint>.Zero;
do
{
// Load 32 input bytes.
Vector128<byte> bytes1 = Sse3.LoadDquVector128(localBufferPtr);
Vector128<byte> bytes2 = Sse3.LoadDquVector128(localBufferPtr + 0x10);
// Add previous block byte sum to v_ps.
v_ps = Sse2.Add(v_ps, v_s1);
// Horizontally add the bytes for s1, multiply-adds the
// bytes by [ 32, 31, 30, ... ] for s2.
v_s1 = Sse2.Add(v_s1, Sse2.SumAbsoluteDifferences(bytes1, zero).AsUInt32());
Vector128<short> mad1 = Ssse3.MultiplyAddAdjacent(bytes1, tap1);
v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad1, ones).AsUInt32());
v_s1 = Sse2.Add(v_s1, Sse2.SumAbsoluteDifferences(bytes2, zero).AsUInt32());
Vector128<short> mad2 = Ssse3.MultiplyAddAdjacent(bytes2, tap2);
v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad2, ones).AsUInt32());
localBufferPtr += BLOCK_SIZE;
}
while (--n > 0);
v_s2 = Sse2.Add(v_s2, Sse2.ShiftLeftLogical(v_ps, 5));
// Sum epi32 ints v_s1(s2) and accumulate in s1(s2).
const byte S2301 = 0b1011_0001; // A B C D -> B A D C
const byte S1032 = 0b0100_1110; // A B C D -> C D A B
v_s1 = Sse2.Add(v_s1, Sse2.Shuffle(v_s1, S1032));
s1 += v_s1.ToScalar();
v_s2 = Sse2.Add(v_s2, Sse2.Shuffle(v_s2, S2301));
v_s2 = Sse2.Add(v_s2, Sse2.Shuffle(v_s2, S1032));
s2 = v_s2.ToScalar();
// Reduce.
s1 %= BASE;
s2 %= BASE;
} }
count -= n; if (length > 0)
while (--n >= 0)
{ {
s1 += Unsafe.Add(ref dataRef, offset++); if (length >= 16)
s2 += s1; {
s2 += s1 += localBufferPtr[0];
s2 += s1 += localBufferPtr[1];
s2 += s1 += localBufferPtr[2];
s2 += s1 += localBufferPtr[3];
s2 += s1 += localBufferPtr[4];
s2 += s1 += localBufferPtr[5];
s2 += s1 += localBufferPtr[6];
s2 += s1 += localBufferPtr[7];
s2 += s1 += localBufferPtr[8];
s2 += s1 += localBufferPtr[9];
s2 += s1 += localBufferPtr[10];
s2 += s1 += localBufferPtr[11];
s2 += s1 += localBufferPtr[12];
s2 += s1 += localBufferPtr[13];
s2 += s1 += localBufferPtr[14];
s2 += s1 += localBufferPtr[15];
localBufferPtr += 16;
length -= 16;
}
while (length-- > 0)
{
s2 += s1 += *localBufferPtr++;
}
if (s1 >= BASE)
{
s1 -= BASE;
}
s2 %= BASE;
} }
s1 %= Base; return s1 | (s2 << 16);
s2 %= Base;
} }
}
#endif
this.checksum = (s2 << 16) | s1; [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
private static unsafe uint CalculateScalar(uint adler, ReadOnlySpan<byte> buffer)
{
uint s1 = adler & 0xFFFF;
uint s2 = (adler >> 16) & 0xFFFF;
uint k;
fixed (byte* bufferPtr = buffer)
{
var localBufferPtr = bufferPtr;
uint length = (uint)buffer.Length;
while (length > 0)
{
k = length < NMAX ? length : NMAX;
length -= k;
while (k >= 16)
{
s2 += s1 += localBufferPtr[0];
s2 += s1 += localBufferPtr[1];
s2 += s1 += localBufferPtr[2];
s2 += s1 += localBufferPtr[3];
s2 += s1 += localBufferPtr[4];
s2 += s1 += localBufferPtr[5];
s2 += s1 += localBufferPtr[6];
s2 += s1 += localBufferPtr[7];
s2 += s1 += localBufferPtr[8];
s2 += s1 += localBufferPtr[9];
s2 += s1 += localBufferPtr[10];
s2 += s1 += localBufferPtr[11];
s2 += s1 += localBufferPtr[12];
s2 += s1 += localBufferPtr[13];
s2 += s1 += localBufferPtr[14];
s2 += s1 += localBufferPtr[15];
localBufferPtr += 16;
k -= 16;
}
while (k-- > 0)
{
s2 += s1 += *localBufferPtr++;
}
s1 %= BASE;
s2 %= BASE;
}
return (s2 << 16) | s1;
}
} }
} }
} }

70
src/ImageSharp/Formats/Png/Zlib/Crc32.Lut.cs

@ -0,0 +1,70 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the GNU Affero General Public License, Version 3.
namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
/// <content>
/// Contains precalulated tables for scalar calculations.
/// </content>
internal static partial class Crc32
{
/// <summary>
/// The table of all possible eight bit values for fast scalar lookup.
/// </summary>
private static readonly uint[] CrcTable =
{
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419,
0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4,
0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07,
0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE,
0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856,
0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9,
0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4,
0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3,
0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A,
0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599,
0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190,
0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F,
0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E,
0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED,
0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950,
0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3,
0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A,
0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5,
0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010,
0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17,
0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6,
0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615,
0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344,
0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB,
0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A,
0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1,
0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C,
0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF,
0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE,
0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31,
0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C,
0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B,
0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242,
0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1,
0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278,
0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7,
0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66,
0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605,
0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8,
0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B,
0x2D02EF8D
};
}
}

295
src/ImageSharp/Formats/Png/Zlib/Crc32.cs

@ -4,151 +4,212 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
namespace SixLabors.ImageSharp.Formats.Png.Zlib namespace SixLabors.ImageSharp.Formats.Png.Zlib
{ {
/// <summary> /// <summary>
/// Generate a table for a byte-wise 32-bit CRC calculation on the polynomial: /// Calculates the 32 bit Cyclic Redundancy Check (CRC) checksum of a given buffer
/// x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1. /// according to the IEEE 802.3 specification.
/// </summary> /// </summary>
/// <remarks> internal static partial class Crc32
/// <para>
/// Polynomials over GF(2) are represented in binary, one bit per coefficient,
/// with the lowest powers in the most significant bit. Then adding polynomials
/// is just exclusive-or, and multiplying a polynomial by x is a right shift by
/// one. If we call the above polynomial p, and represent a byte as the
/// polynomial q, also with the lowest power in the most significant bit (so the
/// byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p,
/// where a mod b means the remainder after dividing a by b.
/// </para>
/// <para>
/// This calculation is done using the shift-register method of multiplying and
/// taking the remainder. The register is initialized to zero, and for each
/// incoming bit, x^32 is added mod p to the register if the bit is a one (where
/// x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by
/// x (which is shifting right by one and adding x^32 mod p if the bit shifted
/// out is a one). We start with the highest power (least significant bit) of
/// q and repeat for all eight bits of q.
/// </para>
/// <para>
/// The table is simply the CRC of all possible eight bit values. This is all
/// the information needed to generate CRC's on data a byte at a time for all
/// combinations of CRC register values and incoming bytes.
/// </para>
/// </remarks>
internal sealed class Crc32 : IChecksum
{ {
/// <summary> /// <summary>
/// The cycle redundancy check seed /// The default initial seed value of a Crc32 checksum calculation.
/// </summary> /// </summary>
private const uint CrcSeed = 0xFFFFFFFF; public const uint SeedValue = 0U;
/// <summary> #if SUPPORTS_RUNTIME_INTRINSICS
/// The table of all possible eight bit values for fast lookup. private const int MinBufferSize = 64;
/// </summary> private const int ChunksizeMask = 15;
private static readonly uint[] CrcTable =
// Definitions of the bit-reflected domain constants k1, k2, k3, etc and
// the CRC32+Barrett polynomials given at the end of the paper.
private static readonly ulong[] K05Poly =
{ {
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x0154442bd4, 0x01c6e41596, // k1, k2
0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0x01751997d0, 0x00ccaa009e, // k3, k4
0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x0163cd6124, 0x0000000000, // k5, k0
0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x01db710641, 0x01f7011641 // polynomial
0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856,
0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9,
0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4,
0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3,
0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A,
0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599,
0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190,
0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F,
0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E,
0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED,
0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950,
0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3,
0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A,
0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5,
0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010,
0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17,
0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6,
0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615,
0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344,
0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB,
0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A,
0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1,
0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C,
0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF,
0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE,
0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31,
0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C,
0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B,
0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242,
0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1,
0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278,
0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7,
0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66,
0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605,
0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8,
0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B,
0x2D02EF8D
}; };
#endif
/// <summary> /// <summary>
/// The data checksum so far. /// Calculates the CRC checksum with the bytes taken from the span.
/// </summary> /// </summary>
private uint crc; /// <param name="buffer">The readonly span of bytes.</param>
/// <returns>The <see cref="uint"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static uint Calculate(ReadOnlySpan<byte> buffer)
=> Calculate(SeedValue, buffer);
/// <inheritdoc/> /// <summary>
public long Value /// Calculates the CRC checksum with the bytes taken from the span and seed.
/// </summary>
/// <param name="crc">The input CRC value.</param>
/// <param name="buffer">The readonly span of bytes.</param>
/// <returns>The <see cref="uint"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static uint Calculate(uint crc, ReadOnlySpan<byte> buffer)
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] if (buffer.IsEmpty)
get => this.crc; {
return SeedValue;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] #if SUPPORTS_RUNTIME_INTRINSICS
set => this.crc = (uint)value; if (Sse41.IsSupported && Pclmulqdq.IsSupported && buffer.Length >= MinBufferSize)
{
return ~CalculateSse(~crc, buffer);
}
else
{
return ~CalculateScalar(~crc, buffer);
}
#else
return ~CalculateScalar(~crc, buffer);
#endif
} }
/// <inheritdoc/> #if SUPPORTS_RUNTIME_INTRINSICS
[MethodImpl(MethodImplOptions.AggressiveInlining)] // Based on https://github.com/chromium/chromium/blob/master/third_party/zlib/crc32_simd.c
public void Reset() [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
private static unsafe uint CalculateSse(uint crc, ReadOnlySpan<byte> buffer)
{ {
this.crc = 0; int chunksize = buffer.Length & ~ChunksizeMask;
} int length = chunksize;
/// <summary> fixed (byte* bufferPtr = buffer)
/// Updates the checksum with the given value. fixed (ulong* k05PolyPtr = K05Poly)
/// </summary> {
/// <param name="value">The byte is taken as the lower 8 bits of value.</param> byte* localBufferPtr = bufferPtr;
[MethodImpl(MethodImplOptions.AggressiveInlining)] ulong* localK05PolyPtr = k05PolyPtr;
public void Update(int value)
{ // There's at least one block of 64.
this.crc ^= CrcSeed; Vector128<ulong> x1 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x00));
this.crc = CrcTable[(this.crc ^ value) & 0xFF] ^ (this.crc >> 8); Vector128<ulong> x2 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x10));
this.crc ^= CrcSeed; Vector128<ulong> x3 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x20));
Vector128<ulong> x4 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x30));
Vector128<ulong> x5;
x1 = Sse2.Xor(x1, Sse2.ConvertScalarToVector128UInt32(crc).AsUInt64());
// k1, k2
Vector128<ulong> x0 = Sse2.LoadVector128(localK05PolyPtr + 0x0);
localBufferPtr += 64;
length -= 64;
// Parallel fold blocks of 64, if any.
while (length >= 64)
{
x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00);
Vector128<ulong> x6 = Pclmulqdq.CarrylessMultiply(x2, x0, 0x00);
Vector128<ulong> x7 = Pclmulqdq.CarrylessMultiply(x3, x0, 0x00);
Vector128<ulong> x8 = Pclmulqdq.CarrylessMultiply(x4, x0, 0x00);
x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11);
x2 = Pclmulqdq.CarrylessMultiply(x2, x0, 0x11);
x3 = Pclmulqdq.CarrylessMultiply(x3, x0, 0x11);
x4 = Pclmulqdq.CarrylessMultiply(x4, x0, 0x11);
Vector128<ulong> y5 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x00));
Vector128<ulong> y6 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x10));
Vector128<ulong> y7 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x20));
Vector128<ulong> y8 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x30));
x1 = Sse2.Xor(x1, x5);
x2 = Sse2.Xor(x2, x6);
x3 = Sse2.Xor(x3, x7);
x4 = Sse2.Xor(x4, x8);
x1 = Sse2.Xor(x1, y5);
x2 = Sse2.Xor(x2, y6);
x3 = Sse2.Xor(x3, y7);
x4 = Sse2.Xor(x4, y8);
localBufferPtr += 64;
length -= 64;
}
// Fold into 128-bits.
// k3, k4
x0 = Sse2.LoadVector128(k05PolyPtr + 0x2);
x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00);
x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11);
x1 = Sse2.Xor(x1, x2);
x1 = Sse2.Xor(x1, x5);
x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00);
x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11);
x1 = Sse2.Xor(x1, x3);
x1 = Sse2.Xor(x1, x5);
x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00);
x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11);
x1 = Sse2.Xor(x1, x4);
x1 = Sse2.Xor(x1, x5);
// Single fold blocks of 16, if any.
while (length >= 16)
{
x2 = Sse2.LoadVector128((ulong*)localBufferPtr);
x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00);
x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11);
x1 = Sse2.Xor(x1, x2);
x1 = Sse2.Xor(x1, x5);
localBufferPtr += 16;
length -= 16;
}
// Fold 128 - bits to 64 - bits.
x2 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x10);
x3 = Vector128.Create(~0, 0, ~0, 0).AsUInt64(); // _mm_setr_epi32 on x86
x1 = Sse2.ShiftRightLogical128BitLane(x1, 8);
x1 = Sse2.Xor(x1, x2);
// k5, k0
x0 = Sse2.LoadScalarVector128(localK05PolyPtr + 0x4);
x2 = Sse2.ShiftRightLogical128BitLane(x1, 4);
x1 = Sse2.And(x1, x3);
x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00);
x1 = Sse2.Xor(x1, x2);
// Barret reduce to 32-bits.
// polynomial
x0 = Sse2.LoadVector128(localK05PolyPtr + 0x6);
x2 = Sse2.And(x1, x3);
x2 = Pclmulqdq.CarrylessMultiply(x2, x0, 0x10);
x2 = Sse2.And(x2, x3);
x2 = Pclmulqdq.CarrylessMultiply(x2, x0, 0x00);
x1 = Sse2.Xor(x1, x2);
crc = (uint)Sse41.Extract(x1.AsInt32(), 1);
return buffer.Length - chunksize == 0 ? crc : CalculateScalar(crc, buffer.Slice(chunksize));
}
} }
#endif
/// <inheritdoc/> [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint CalculateScalar(uint crc, ReadOnlySpan<byte> buffer)
public void Update(ReadOnlySpan<byte> data)
{ {
this.crc ^= CrcSeed;
ref uint crcTableRef = ref MemoryMarshal.GetReference(CrcTable.AsSpan()); ref uint crcTableRef = ref MemoryMarshal.GetReference(CrcTable.AsSpan());
for (int i = 0; i < data.Length; i++) ref byte bufferRef = ref MemoryMarshal.GetReference(buffer);
for (int i = 0; i < buffer.Length; i++)
{ {
this.crc = Unsafe.Add(ref crcTableRef, (int)((this.crc ^ data[i]) & 0xFF)) ^ (this.crc >> 8); crc = Unsafe.Add(ref crcTableRef, (int)((crc ^ Unsafe.Add(ref bufferRef, i)) & 0xFF)) ^ (crc >> 8);
} }
this.crc ^= CrcSeed; return crc;
} }
} }
} }

43
src/ImageSharp/Formats/Png/Zlib/IChecksum.cs

@ -1,43 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the GNU Affero General Public License, Version 3.
using System;
namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
/// <summary>
/// Interface to compute a data checksum used by checked input/output streams.
/// A data checksum can be updated by one byte or with a byte array. After each
/// update the value of the current checksum can be returned by calling
/// <code>Value</code>. The complete checksum object can also be reset
/// so it can be used again with new data.
/// </summary>
internal interface IChecksum
{
/// <summary>
/// Gets the data checksum computed so far.
/// </summary>
long Value { get; }
/// <summary>
/// Resets the data checksum as if no update was ever called.
/// </summary>
void Reset();
/// <summary>
/// Adds one byte to the data checksum.
/// </summary>
/// <param name = "value">
/// The data value to add. The high byte of the integer is ignored.
/// </param>
void Update(int value);
/// <summary>
/// Updates the data checksum with the bytes taken from the span.
/// </summary>
/// <param name="data">
/// buffer an array of bytes
/// </param>
void Update(ReadOnlySpan<byte> data);
}
}

10
src/ImageSharp/Formats/Png/Zlib/README.md

@ -1,5 +1,11 @@
Deflatestream implementation adapted from DeflateStream implementation adapted from
https://github.com/icsharpcode/SharpZipLib https://github.com/icsharpcode/SharpZipLib
LIcensed under MIT Licensed under MIT
Crc32 and Adler32 SIMD implementation adapted from
https://github.com/chromium/chromium
Licensed under BSD 3-Clause "New" or "Revised" License

8
src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs

@ -3,6 +3,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Png.Zlib namespace SixLabors.ImageSharp.Formats.Png.Zlib
@ -20,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// <summary> /// <summary>
/// Computes the checksum for the data stream. /// Computes the checksum for the data stream.
/// </summary> /// </summary>
private readonly Adler32 adler32 = new Adler32(); private uint adler = Adler32.SeedValue;
/// <summary> /// <summary>
/// A value indicating whether this instance of the given entity has been disposed. /// A value indicating whether this instance of the given entity has been disposed.
@ -133,10 +134,11 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
public override void SetLength(long value) => throw new NotSupportedException(); public override void SetLength(long value) => throw new NotSupportedException();
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public override void Write(byte[] buffer, int offset, int count) public override void Write(byte[] buffer, int offset, int count)
{ {
this.deflateStream.Write(buffer, offset, count); this.deflateStream.Write(buffer, offset, count);
this.adler32.Update(buffer.AsSpan(offset, count)); this.adler = Adler32.Calculate(this.adler, buffer.AsSpan(offset, count));
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -153,7 +155,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.deflateStream.Dispose(); this.deflateStream.Dispose();
// Add the crc // Add the crc
uint crc = (uint)this.adler32.Value; uint crc = this.adler;
this.rawStream.WriteByte((byte)((crc >> 24) & 0xFF)); this.rawStream.WriteByte((byte)((crc >> 24) & 0xFF));
this.rawStream.WriteByte((byte)((crc >> 16) & 0xFF)); this.rawStream.WriteByte((byte)((crc >> 16) & 0xFF));
this.rawStream.WriteByte((byte)((crc >> 8) & 0xFF)); this.rawStream.WriteByte((byte)((crc >> 8) & 0xFF));

BIN
src/ImageSharp/Formats/Png/Zlib/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf

Binary file not shown.

1
tests/Directory.Build.targets

@ -34,6 +34,7 @@
<PackageReference Update="Microsoft.NET.Test.Sdk" Version="16.5.0-preview-20200116-01" /> <PackageReference Update="Microsoft.NET.Test.Sdk" Version="16.5.0-preview-20200116-01" />
<PackageReference Update="Moq" Version="4.10.0" /> <PackageReference Update="Moq" Version="4.10.0" />
<PackageReference Update="Pfim" Version="0.9.1" /> <PackageReference Update="Pfim" Version="0.9.1" />
<PackageReference Update="SharpZipLib" Version="1.2.0" />
<PackageReference Update="System.Drawing.Common" Version="4.7.0" /> <PackageReference Update="System.Drawing.Common" Version="4.7.0" />
<PackageReference Update="xunit" Version="2.4.1" /> <PackageReference Update="xunit" Version="2.4.1" />
<PackageReference Update="xunit.runner.visualstudio" Version="2.4.1" /> <PackageReference Update="xunit.runner.visualstudio" Version="2.4.1" />

72
tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs

@ -0,0 +1,72 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the GNU Affero General Public License, Version 3.
using System;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Png.Zlib;
using SharpAdler32 = ICSharpCode.SharpZipLib.Checksum.Adler32;
namespace SixLabors.ImageSharp.Benchmarks.General
{
[Config(typeof(Config.ShortClr))]
public class Adler32Benchmark
{
private byte[] data;
private readonly SharpAdler32 adler = new SharpAdler32();
[Params(1024, 2048, 4096)]
public int Count { get; set; }
[GlobalSetup]
public void SetUp()
{
this.data = new byte[this.Count];
new Random(1).NextBytes(this.data);
}
[Benchmark(Baseline = true)]
public long SharpZipLibCalculate()
{
this.adler.Reset();
this.adler.Update(this.data);
return this.adler.Value;
}
[Benchmark]
public uint SixLaborsCalculate()
{
return Adler32.Calculate(this.data);
}
}
// ########## 17/05/2020 ##########
//
// | Method | Runtime | Count | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
// |--------------------- |-------------- |------ |------------:|------------:|----------:|------:|--------:|------:|------:|------:|----------:|
// | SharpZipLibCalculate | .NET 4.7.2 | 1024 | 793.18 ns | 775.66 ns | 42.516 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET 4.7.2 | 1024 | 384.86 ns | 15.64 ns | 0.857 ns | 0.49 | 0.03 | - | - | - | - |
// | | | | | | | | | | | | |
// | SharpZipLibCalculate | .NET Core 2.1 | 1024 | 790.31 ns | 353.34 ns | 19.368 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET Core 2.1 | 1024 | 465.28 ns | 652.41 ns | 35.761 ns | 0.59 | 0.03 | - | - | - | - |
// | | | | | | | | | | | | |
// | SharpZipLibCalculate | .NET Core 3.1 | 1024 | 877.25 ns | 97.89 ns | 5.365 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET Core 3.1 | 1024 | 45.60 ns | 13.28 ns | 0.728 ns | 0.05 | 0.00 | - | - | - | - |
// | | | | | | | | | | | | |
// | SharpZipLibCalculate | .NET 4.7.2 | 2048 | 1,537.04 ns | 428.44 ns | 23.484 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET 4.7.2 | 2048 | 849.76 ns | 1,066.34 ns | 58.450 ns | 0.55 | 0.04 | - | - | - | - |
// | | | | | | | | | | | | |
// | SharpZipLibCalculate | .NET Core 2.1 | 2048 | 1,616.97 ns | 276.70 ns | 15.167 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET Core 2.1 | 2048 | 790.77 ns | 691.71 ns | 37.915 ns | 0.49 | 0.03 | - | - | - | - |
// | | | | | | | | | | | | |
// | SharpZipLibCalculate | .NET Core 3.1 | 2048 | 1,735.11 ns | 1,374.22 ns | 75.325 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET Core 3.1 | 2048 | 87.80 ns | 56.84 ns | 3.116 ns | 0.05 | 0.00 | - | - | - | - |
// | | | | | | | | | | | | |
// | SharpZipLibCalculate | .NET 4.7.2 | 4096 | 3,054.53 ns | 796.41 ns | 43.654 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET 4.7.2 | 4096 | 1,538.90 ns | 487.02 ns | 26.695 ns | 0.50 | 0.01 | - | - | - | - |
// | | | | | | | | | | | | |
// | SharpZipLibCalculate | .NET Core 2.1 | 4096 | 3,223.48 ns | 32.32 ns | 1.771 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET Core 2.1 | 4096 | 1,547.60 ns | 309.72 ns | 16.977 ns | 0.48 | 0.01 | - | - | - | - |
// | | | | | | | | | | | | |
// | SharpZipLibCalculate | .NET Core 3.1 | 4096 | 3,672.33 ns | 1,095.81 ns | 60.065 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET Core 3.1 | 4096 | 159.44 ns | 36.31 ns | 1.990 ns | 0.04 | 0.00 | - | - | - | - |
}

72
tests/ImageSharp.Benchmarks/General/Crc32Benchmark.cs

@ -0,0 +1,72 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the GNU Affero General Public License, Version 3.
using System;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Png.Zlib;
using SharpCrc32 = ICSharpCode.SharpZipLib.Checksum.Crc32;
namespace SixLabors.ImageSharp.Benchmarks.General
{
[Config(typeof(Config.ShortClr))]
public class Crc32Benchmark
{
private byte[] data;
private readonly SharpCrc32 crc = new SharpCrc32();
[Params(1024, 2048, 4096)]
public int Count { get; set; }
[GlobalSetup]
public void SetUp()
{
this.data = new byte[this.Count];
new Random(1).NextBytes(this.data);
}
[Benchmark(Baseline = true)]
public long SharpZipLibCalculate()
{
this.crc.Reset();
this.crc.Update(this.data);
return this.crc.Value;
}
[Benchmark]
public long SixLaborsCalculate()
{
return Crc32.Calculate(this.data);
}
}
// ########## 17/05/2020 ##########
//
// | Method | Runtime | Count | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
// |--------------------- |-------------- |------ |-------------:|-------------:|-----------:|------:|--------:|------:|------:|------:|----------:|
// | SharpZipLibCalculate | .NET 4.7.2 | 1024 | 2,797.77 ns | 278.697 ns | 15.276 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET 4.7.2 | 1024 | 2,275.56 ns | 216.100 ns | 11.845 ns | 0.81 | 0.01 | - | - | - | - |
// | | | | | | | | | | | | |
// | SharpZipLibCalculate | .NET Core 2.1 | 1024 | 2,923.43 ns | 2,656.882 ns | 145.633 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET Core 2.1 | 1024 | 2,257.79 ns | 75.081 ns | 4.115 ns | 0.77 | 0.04 | - | - | - | - |
// | | | | | | | | | | | | |
// | SharpZipLibCalculate | .NET Core 3.1 | 1024 | 2,764.14 ns | 86.281 ns | 4.729 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET Core 3.1 | 1024 | 49.32 ns | 1.813 ns | 0.099 ns | 0.02 | 0.00 | - | - | - | - |
// | | | | | | | | | | | | |
// | SharpZipLibCalculate | .NET 4.7.2 | 2048 | 5,603.71 ns | 427.240 ns | 23.418 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET 4.7.2 | 2048 | 4,525.02 ns | 33.931 ns | 1.860 ns | 0.81 | 0.00 | - | - | - | - |
// | | | | | | | | | | | | |
// | SharpZipLibCalculate | .NET Core 2.1 | 2048 | 5,563.32 ns | 49.337 ns | 2.704 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET Core 2.1 | 2048 | 4,519.61 ns | 29.837 ns | 1.635 ns | 0.81 | 0.00 | - | - | - | - |
// | | | | | | | | | | | | |
// | SharpZipLibCalculate | .NET Core 3.1 | 2048 | 5,543.37 ns | 518.551 ns | 28.424 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET Core 3.1 | 2048 | 89.07 ns | 3.312 ns | 0.182 ns | 0.02 | 0.00 | - | - | - | - |
// | | | | | | | | | | | | |
// | SharpZipLibCalculate | .NET 4.7.2 | 4096 | 11,396.95 ns | 373.450 ns | 20.470 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET 4.7.2 | 4096 | 9,070.35 ns | 271.083 ns | 14.859 ns | 0.80 | 0.00 | - | - | - | - |
// | | | | | | | | | | | | |
// | SharpZipLibCalculate | .NET Core 2.1 | 4096 | 11,127.81 ns | 239.177 ns | 13.110 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET Core 2.1 | 4096 | 9,050.46 ns | 230.916 ns | 12.657 ns | 0.81 | 0.00 | - | - | - | - |
// | | | | | | | | | | | | |
// | SharpZipLibCalculate | .NET Core 3.1 | 4096 | 11,098.62 ns | 687.978 ns | 37.710 ns | 1.00 | 0.00 | - | - | - | - |
// | SixLaborsCalculate | .NET Core 3.1 | 4096 | 168.11 ns | 3.633 ns | 0.199 ns | 0.02 | 0.00 | - | - | - | - |
}

1
tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj

@ -25,6 +25,7 @@
<PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Condition="'$(OS)' == 'Windows_NT'" /> <PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Condition="'$(OS)' == 'Windows_NT'" />
<PackageReference Include="Colourful" /> <PackageReference Include="Colourful" />
<PackageReference Include="Pfim" /> <PackageReference Include="Pfim" />
<PackageReference Include="SharpZipLib" />
<PackageReference Include="System.Drawing.Common" /> <PackageReference Include="System.Drawing.Common" />
</ItemGroup> </ItemGroup>

41
tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs

@ -0,0 +1,41 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the GNU Affero General Public License, Version 3.
using System;
using SixLabors.ImageSharp.Formats.Png.Zlib;
using Xunit;
using SharpAdler32 = ICSharpCode.SharpZipLib.Checksum.Adler32;
namespace SixLabors.ImageSharp.Tests.Formats.Png
{
public class Adler32Tests
{
[Theory]
[InlineData(0)]
[InlineData(8)]
[InlineData(215)]
[InlineData(1024)]
[InlineData(1024 + 15)]
[InlineData(2034)]
[InlineData(4096)]
public void MatchesReference(int length)
{
var data = GetBuffer(length);
var adler = new SharpAdler32();
adler.Update(data);
long expected = adler.Value;
long actual = Adler32.Calculate(data);
Assert.Equal(expected, actual);
}
private static byte[] GetBuffer(int length)
{
var data = new byte[length];
new Random(1).NextBytes(data);
return data;
}
}
}

41
tests/ImageSharp.Tests/Formats/Png/Crc32Tests.cs

@ -0,0 +1,41 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the GNU Affero General Public License, Version 3.
using System;
using SixLabors.ImageSharp.Formats.Png.Zlib;
using Xunit;
using SharpCrc32 = ICSharpCode.SharpZipLib.Checksum.Crc32;
namespace SixLabors.ImageSharp.Tests.Formats.Png
{
public class Crc32Tests
{
[Theory]
[InlineData(0)]
[InlineData(8)]
[InlineData(215)]
[InlineData(1024)]
[InlineData(1024 + 15)]
[InlineData(2034)]
[InlineData(4096)]
public void MatchesReference(int length)
{
var data = GetBuffer(length);
var crc = new SharpCrc32();
crc.Update(data);
long expected = crc.Value;
long actual = Crc32.Calculate(data);
Assert.Equal(expected, actual);
}
private static byte[] GetBuffer(int length)
{
var data = new byte[length];
new Random(1).NextBytes(data);
return data;
}
}
}

18
tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the GNU Affero General Public License, Version 3. // Licensed under the GNU Affero General Public License, Version 3.
using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.IO; using System.IO;
using System.Text; using System.Text;
@ -16,6 +17,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
{ {
public partial class PngDecoderTests public partial class PngDecoderTests
{ {
// Represents ASCII string of "123456789"
private readonly byte[] check = { 49, 50, 51, 52, 53, 54, 55, 56, 57 };
// Contains the png marker, IHDR and pHYs chunks of a 1x1 pixel 32bit png 1 a single black pixel. // Contains the png marker, IHDR and pHYs chunks of a 1x1 pixel 32bit png 1 a single black pixel.
private static readonly byte[] Raw1X1PngIhdrAndpHYs = private static readonly byte[] Raw1X1PngIhdrAndpHYs =
{ {
@ -79,20 +83,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
} }
} }
[Fact]
public void CalculateCrc_Works()
{
// arrange
var data = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 };
var crc = new Crc32();
// act
crc.Update(data);
// assert
Assert.Equal(0x88AA689F, crc.Value);
}
private static string GetChunkTypeName(uint value) private static string GetChunkTypeName(uint value)
{ {
var data = new byte[4]; var data = new byte[4];

1
tests/ImageSharp.Tests/ImageSharp.Tests.csproj

@ -21,6 +21,7 @@
<PackageReference Include="Magick.NET-Q16-AnyCPU" /> <PackageReference Include="Magick.NET-Q16-AnyCPU" />
<PackageReference Include="Microsoft.DotNet.RemoteExecutor" /> <PackageReference Include="Microsoft.DotNet.RemoteExecutor" />
<PackageReference Include="Moq" /> <PackageReference Include="Moq" />
<PackageReference Include="SharpZipLib" />
<PackageReference Include="System.Drawing.Common" /> <PackageReference Include="System.Drawing.Common" />
</ItemGroup> </ItemGroup>

Loading…
Cancel
Save