diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index d6ba088958..b6943e5ac1 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -30,11 +30,6 @@ internal sealed class PngDecoderCore /// private readonly byte[] buffer = new byte[4]; - /// - /// Reusable CRC for validating chunks. - /// - private readonly Crc32 crc = new Crc32(); - /// /// The global configuration. /// @@ -1159,18 +1154,17 @@ private bool TryReadChunk(out PngChunk chunk) /// The . private void ValidateChunk(in PngChunk chunk) { - uint crc = this.ReadChunkCrc(); + uint inputCrc = this.ReadChunkCrc(); if (chunk.IsCritical) { Span chunkType = stackalloc byte[4]; BinaryPrimitives.WriteUInt32BigEndian(chunkType, (uint)chunk.Type); - this.crc.Reset(); - this.crc.Update(chunkType); - this.crc.Update(chunk.Data.GetSpan()); + uint validCrc = Crc32.Calculate(chunkType); + validCrc = Crc32.Calculate(validCrc, chunk.Data.GetSpan()); - if (this.crc.Value != crc) + if (validCrc != inputCrc) { string chunkTypeName = Encoding.ASCII.GetString(chunkType); PngThrowHelper.ThrowInvalidChunkCrc(chunkTypeName); diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 6c30550c2a..a25f51fe16 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -47,11 +47,6 @@ internal sealed class PngEncoderCore : IDisposable /// private readonly byte[] chunkDataBuffer = new byte[16]; - /// - /// Reusable CRC for validating chunks. - /// - private readonly Crc32 crc = new Crc32(); - /// /// The encoder options /// @@ -1120,18 +1115,16 @@ private void WriteChunk(Stream stream, PngChunkType type, byte[] data, int offse stream.Write(this.buffer, 0, 8); - this.crc.Reset(); - - this.crc.Update(this.buffer.AsSpan(4, 4)); // Write the type buffer + uint crc = Crc32.Calculate(this.buffer.AsSpan(4, 4)); // Write the type buffer if (data != null && length > 0) { 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 } diff --git a/src/ImageSharp/Formats/Png/Zlib/Adler32.cs b/src/ImageSharp/Formats/Png/Zlib/Adler32.cs index 3a5a37ce20..3d41c6b821 100644 --- a/src/ImageSharp/Formats/Png/Zlib/Adler32.cs +++ b/src/ImageSharp/Formats/Png/Zlib/Adler32.cs @@ -3,146 +3,258 @@ using System; 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 { /// - /// Computes Adler32 checksum for a stream of data. An Adler32 - /// checksum is not as reliable as a CRC32 checksum, but a lot faster to - /// compute. + /// Calculates the 32 bit Adler checksum of a given buffer according to + /// RFC 1950. ZLIB Compressed Data Format Specification version 3.3) /// - /// - /// 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.)" - /// - /// - /// - internal sealed class Adler32 : IChecksum + internal static class Adler32 { /// - /// largest prime smaller than 65536 + /// The default initial seed value of a Adler32 checksum calculation. /// - private const uint Base = 65521; + public const uint SeedValue = 1U; - /// - /// The checksum calculated to far. - /// - private uint checksum; + // Largest prime smaller than 65536 + private const uint BASE = 65521; - /// - /// Initializes a new instance of the class. - /// The checksum starts off with a value of 1. - /// - public Adler32() - { - this.Reset(); - } + // NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 + private const uint NMAX = 5552; - /// - public long Value - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.checksum; - } +#if SUPPORTS_RUNTIME_INTRINSICS + private const int MinBufferSize = 64; - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Reset() + // The C# compiler emits this as a compile-time constant embedded in the PE file. + private static ReadOnlySpan Tap1Tap2 => new byte[] { - 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 + + /// + /// Calculates the Adler32 checksum with the bytes taken from the span. + /// + /// The readonly span of bytes. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static uint Calculate(ReadOnlySpan buffer) + => Calculate(SeedValue, buffer); /// - /// Updates the checksum with a byte value. + /// Calculates the Adler32 checksum with the bytes taken from the span and seed. /// - /// - /// The data value to add. The high byte of the int is ignored. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Update(int value) + /// The input Adler32 value. + /// The readonly span of bytes. + /// The . + [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] + public static uint Calculate(uint adler, ReadOnlySpan buffer) { - // We could make a length 1 byte array and call update again, but I - // would rather not have that overhead - uint s1 = this.checksum & 0xFFFF; - uint s2 = this.checksum >> 16; + if (buffer.IsEmpty) + { + return SeedValue; + } - s1 = (s1 + ((uint)value & 0xFF)) % Base; - s2 = (s1 + s2) % Base; +#if SUPPORTS_RUNTIME_INTRINSICS + 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 } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Update(ReadOnlySpan data) + // Based on https://github.com/chromium/chromium/blob/master/third_party/zlib/adler32_simd.c +#if SUPPORTS_RUNTIME_INTRINSICS + [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] + private static unsafe uint CalculateSse(uint adler, ReadOnlySpan buffer) { - ref byte dataRef = ref MemoryMarshal.GetReference(data); - uint s1 = this.checksum & 0xFFFF; - uint s2 = this.checksum >> 16; + uint s1 = adler & 0xFFFF; + uint s2 = (adler >> 16) & 0xFFFF; - int count = data.Length; - int offset = 0; + // Process the data in blocks. + 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: - // s1 maximally grows from 65521 to 65521 + 255 * 3800 - // s2 maximally grows by 3800 * median(s1) = 2090079800 < 2^31 - int n = 3800; - if (n > count) + index += (int)blocks * BLOCK_SIZE; + var localBufferPtr = bufferPtr; + + // _mm_setr_epi8 on x86 + Vector128 tap1 = Sse2.LoadVector128((sbyte*)tapPtr); + Vector128 tap2 = Sse2.LoadVector128((sbyte*)(tapPtr + 0x10)); + Vector128 zero = Vector128.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 v_ps = Vector128.CreateScalar(s1 * n); + Vector128 v_s2 = Vector128.CreateScalar(s2); + Vector128 v_s1 = Vector128.Zero; + + do + { + // Load 32 input bytes. + Vector128 bytes1 = Sse3.LoadDquVector128(localBufferPtr); + Vector128 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 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 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; - while (--n >= 0) + if (length > 0) { - s1 += Unsafe.Add(ref dataRef, offset++); - s2 += s1; + if (length >= 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; + length -= 16; + } + + while (length-- > 0) + { + s2 += s1 += *localBufferPtr++; + } + + if (s1 >= BASE) + { + s1 -= BASE; + } + + s2 %= BASE; } - s1 %= Base; - s2 %= Base; + return s1 | (s2 << 16); } + } +#endif - this.checksum = (s2 << 16) | s1; + [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] + private static unsafe uint CalculateScalar(uint adler, ReadOnlySpan 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; + } } } } diff --git a/src/ImageSharp/Formats/Png/Zlib/Crc32.Lut.cs b/src/ImageSharp/Formats/Png/Zlib/Crc32.Lut.cs new file mode 100644 index 0000000000..77fb9f8250 --- /dev/null +++ b/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 +{ + /// + /// Contains precalulated tables for scalar calculations. + /// + internal static partial class Crc32 + { + /// + /// The table of all possible eight bit values for fast scalar lookup. + /// + 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 + }; + } +} diff --git a/src/ImageSharp/Formats/Png/Zlib/Crc32.cs b/src/ImageSharp/Formats/Png/Zlib/Crc32.cs index bc3811b5dc..b25c042e19 100644 --- a/src/ImageSharp/Formats/Png/Zlib/Crc32.cs +++ b/src/ImageSharp/Formats/Png/Zlib/Crc32.cs @@ -4,151 +4,212 @@ using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif namespace SixLabors.ImageSharp.Formats.Png.Zlib { /// - /// Generate a table for a byte-wise 32-bit CRC calculation on the polynomial: - /// 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. + /// Calculates the 32 bit Cyclic Redundancy Check (CRC) checksum of a given buffer + /// according to the IEEE 802.3 specification. /// - /// - /// - /// 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. - /// - /// - /// 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. - /// - /// - /// 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. - /// - /// - internal sealed class Crc32 : IChecksum + internal static partial class Crc32 { /// - /// The cycle redundancy check seed + /// The default initial seed value of a Crc32 checksum calculation. /// - private const uint CrcSeed = 0xFFFFFFFF; + public const uint SeedValue = 0U; - /// - /// The table of all possible eight bit values for fast lookup. - /// - private static readonly uint[] CrcTable = +#if SUPPORTS_RUNTIME_INTRINSICS + private const int MinBufferSize = 64; + private const int ChunksizeMask = 15; + + // 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, - 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 + 0x0154442bd4, 0x01c6e41596, // k1, k2 + 0x01751997d0, 0x00ccaa009e, // k3, k4 + 0x0163cd6124, 0x0000000000, // k5, k0 + 0x01db710641, 0x01f7011641 // polynomial }; +#endif /// - /// The data checksum so far. + /// Calculates the CRC checksum with the bytes taken from the span. /// - private uint crc; + /// The readonly span of bytes. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static uint Calculate(ReadOnlySpan buffer) + => Calculate(SeedValue, buffer); - /// - public long Value + /// + /// Calculates the CRC checksum with the bytes taken from the span and seed. + /// + /// The input CRC value. + /// The readonly span of bytes. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static uint Calculate(uint crc, ReadOnlySpan buffer) { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.crc; + if (buffer.IsEmpty) + { + return SeedValue; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set => this.crc = (uint)value; +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse41.IsSupported && Pclmulqdq.IsSupported && buffer.Length >= MinBufferSize) + { + return ~CalculateSse(~crc, buffer); + } + else + { + return ~CalculateScalar(~crc, buffer); + } +#else + return ~CalculateScalar(~crc, buffer); +#endif } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Reset() +#if SUPPORTS_RUNTIME_INTRINSICS + // Based on https://github.com/chromium/chromium/blob/master/third_party/zlib/crc32_simd.c + [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] + private static unsafe uint CalculateSse(uint crc, ReadOnlySpan buffer) { - this.crc = 0; - } + int chunksize = buffer.Length & ~ChunksizeMask; + int length = chunksize; - /// - /// Updates the checksum with the given value. - /// - /// The byte is taken as the lower 8 bits of value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Update(int value) - { - this.crc ^= CrcSeed; - this.crc = CrcTable[(this.crc ^ value) & 0xFF] ^ (this.crc >> 8); - this.crc ^= CrcSeed; + fixed (byte* bufferPtr = buffer) + fixed (ulong* k05PolyPtr = K05Poly) + { + byte* localBufferPtr = bufferPtr; + ulong* localK05PolyPtr = k05PolyPtr; + + // There's at least one block of 64. + Vector128 x1 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x00)); + Vector128 x2 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x10)); + Vector128 x3 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x20)); + Vector128 x4 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x30)); + Vector128 x5; + + x1 = Sse2.Xor(x1, Sse2.ConvertScalarToVector128UInt32(crc).AsUInt64()); + + // k1, k2 + Vector128 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 x6 = Pclmulqdq.CarrylessMultiply(x2, x0, 0x00); + Vector128 x7 = Pclmulqdq.CarrylessMultiply(x3, x0, 0x00); + Vector128 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 y5 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x00)); + Vector128 y6 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x10)); + Vector128 y7 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x20)); + Vector128 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 - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Update(ReadOnlySpan data) + [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] + private static uint CalculateScalar(uint crc, ReadOnlySpan buffer) { - this.crc ^= CrcSeed; - 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; } } } diff --git a/src/ImageSharp/Formats/Png/Zlib/IChecksum.cs b/src/ImageSharp/Formats/Png/Zlib/IChecksum.cs deleted file mode 100644 index 018508295c..0000000000 --- a/src/ImageSharp/Formats/Png/Zlib/IChecksum.cs +++ /dev/null @@ -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 -{ - /// - /// 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 - /// Value. The complete checksum object can also be reset - /// so it can be used again with new data. - /// - internal interface IChecksum - { - /// - /// Gets the data checksum computed so far. - /// - long Value { get; } - - /// - /// Resets the data checksum as if no update was ever called. - /// - void Reset(); - - /// - /// Adds one byte to the data checksum. - /// - /// - /// The data value to add. The high byte of the integer is ignored. - /// - void Update(int value); - - /// - /// Updates the data checksum with the bytes taken from the span. - /// - /// - /// buffer an array of bytes - /// - void Update(ReadOnlySpan data); - } -} diff --git a/src/ImageSharp/Formats/Png/Zlib/README.md b/src/ImageSharp/Formats/Png/Zlib/README.md index 59f75d05f6..3875f98841 100644 --- a/src/ImageSharp/Formats/Png/Zlib/README.md +++ b/src/ImageSharp/Formats/Png/Zlib/README.md @@ -1,5 +1,11 @@ -Deflatestream implementation adapted from +DeflateStream implementation adapted from 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 diff --git a/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs b/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs index 4f757e4c92..3257fa8846 100644 --- a/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs +++ b/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Png.Zlib @@ -20,7 +21,7 @@ internal sealed class ZlibDeflateStream : Stream /// /// Computes the checksum for the data stream. /// - private readonly Adler32 adler32 = new Adler32(); + private uint adler = Adler32.SeedValue; /// /// A value indicating whether this instance of the given entity has been disposed. @@ -133,10 +134,11 @@ public override long Position public override void SetLength(long value) => throw new NotSupportedException(); /// + [MethodImpl(InliningOptions.ShortMethod)] public override void Write(byte[] buffer, int offset, int count) { this.deflateStream.Write(buffer, offset, count); - this.adler32.Update(buffer.AsSpan(offset, count)); + this.adler = Adler32.Calculate(this.adler, buffer.AsSpan(offset, count)); } /// @@ -153,7 +155,7 @@ protected override void Dispose(bool disposing) this.deflateStream.Dispose(); // 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 >> 16) & 0xFF)); this.rawStream.WriteByte((byte)((crc >> 8) & 0xFF)); diff --git a/src/ImageSharp/Formats/Png/Zlib/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf b/src/ImageSharp/Formats/Png/Zlib/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf new file mode 100644 index 0000000000..d0eca86b33 Binary files /dev/null and b/src/ImageSharp/Formats/Png/Zlib/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf differ diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets index df153c08bb..6428ef32ff 100644 --- a/tests/Directory.Build.targets +++ b/tests/Directory.Build.targets @@ -34,6 +34,7 @@ + diff --git a/tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs b/tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs new file mode 100644 index 0000000000..37144bd949 --- /dev/null +++ b/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 | - | - | - | - | +} diff --git a/tests/ImageSharp.Benchmarks/General/Crc32Benchmark.cs b/tests/ImageSharp.Benchmarks/General/Crc32Benchmark.cs new file mode 100644 index 0000000000..7f85d5aad1 --- /dev/null +++ b/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 | - | - | - | - | +} diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index f380d0a6a9..8c848fd049 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -25,6 +25,7 @@ + diff --git a/tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs b/tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs new file mode 100644 index 0000000000..a0d4030c3f --- /dev/null +++ b/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; + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Png/Crc32Tests.cs b/tests/ImageSharp.Tests/Formats/Png/Crc32Tests.cs new file mode 100644 index 0000000000..b40e210e3e --- /dev/null +++ b/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; + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs index afc6e05a6b..52393a7f1c 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the GNU Affero General Public License, Version 3. +using System; using System.Buffers.Binary; using System.IO; using System.Text; @@ -16,6 +17,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png { 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. private static readonly byte[] Raw1X1PngIhdrAndpHYs = { @@ -79,20 +83,6 @@ public void Decode_IncorrectCRCForCriticalChunk_ExceptionIsThrown(uint chunkType } } - [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) { var data = new byte[4]; diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index fdb280ca99..98f8e95745 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -21,6 +21,7 @@ +