diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..6e7ad36 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +# 2018-03-11 +- Added API for ReadOnlySpan +- Added support for the async calls with cancelation tokens \ No newline at end of file diff --git a/README.md b/README.md index 8e7fa87..c889a74 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ coverage - nuget + nuget platform @@ -74,13 +74,15 @@ byte[] data = Encoding.UTF8.GetBytes("veni vidi vici"); ulong h64_1 = xxHash64.ComputeHash(data, data.Length); ulong h64_2 = xxHash64.ComputeHash(new Span(data), data.Length); -ulong h64_3 = xxHash64.ComputeHash(new MemoryStream(data)); -ulong h64_4 = await xxHash64.ComputeHashAsync(new MemoryStream(data)); +ulong h64_3 = xxHash64.ComputeHash(new ReadOnlySpan(data), data.Length); +ulong h64_4 = xxHash64.ComputeHash(new MemoryStream(data)); +ulong h64_5 = await xxHash64.ComputeHashAsync(new MemoryStream(data)); uint h32_1 = xxHash32.ComputeHash(data, data.Length); uint h32_2 = xxHash32.ComputeHash(new Span(data), data.Length); -uint h32_3 = xxHash32.ComputeHash(new MemoryStream(data)); -uint h32_4 = await xxHash32.ComputeHashAsync(new MemoryStream(data)); +uint h32_3 = xxHash32.ComputeHash(new ReadOnlySpan(data), data.Length); +uint h32_4 = xxHash32.ComputeHash(new MemoryStream(data)); +uint h32_5 = await xxHash32.ComputeHashAsync(new MemoryStream(data)); ``` ---

diff --git a/nuget.props b/nuget.props index 1a6385d..62eae8a 100644 --- a/nuget.props +++ b/nuget.props @@ -3,7 +3,7 @@ netstandard2.0 Standart.Hash.xxHash - 1.0.5 + 1.0.6 Standart.Hash.xxHash Standart.Hash.xxHash Alexander Melnik diff --git a/src/Standart.Hash.xxHash.Perf/xxHashBenchmark.cs b/src/Standart.Hash.xxHash.Perf/xxHashBenchmark.cs index 241489d..a94da32 100644 --- a/src/Standart.Hash.xxHash.Perf/xxHashBenchmark.cs +++ b/src/Standart.Hash.xxHash.Perf/xxHashBenchmark.cs @@ -8,7 +8,7 @@ [RPlotExporter, RankColumn] [MinColumn, MaxColumn] [MemoryDiagnoser] - //[DisassemblyDiagnoser(printAsm: true, printSource: true)] + // [DisassemblyDiagnoser(printAsm: true, printSource: true)] public class xxHashBenchmark { const int KB = 1024; @@ -43,6 +43,13 @@ Span span = new Span(data); return xxHash32.ComputeHash(span, span.Length); } + + [Benchmark] + public uint Hash32_ReadOnlySpan() + { + ReadOnlySpan span = new ReadOnlySpan(data); + return xxHash32.ComputeHash(span, span.Length); + } [Benchmark] public uint Hash32_Stream() @@ -71,6 +78,13 @@ return xxHash64.ComputeHash(span, span.Length); } + [Benchmark] + public ulong Hash64_ReadOnlySpan() + { + ReadOnlySpan span = new ReadOnlySpan(data); + return xxHash64.ComputeHash(span, span.Length); + } + [Benchmark] public ulong Hash64_Stream() { diff --git a/src/Standart.Hash.xxHash.Test/xxHash32Test.cs b/src/Standart.Hash.xxHash.Test/xxHash32Test.cs index 61214a8..8996133 100644 --- a/src/Standart.Hash.xxHash.Test/xxHash32Test.cs +++ b/src/Standart.Hash.xxHash.Test/xxHash32Test.cs @@ -2,6 +2,7 @@ { using System; using System.IO; + using System.Threading; using System.Threading.Tasks; using Xunit; @@ -13,14 +14,17 @@ // Arrange byte[] data = {0xde}; Span span = new Span(data); + ReadOnlySpan rspan = new ReadOnlySpan(data); // Act uint hash1 = xxHash32.ComputeHash(data, data.Length); uint hash2 = xxHash32.ComputeHash(span, span.Length); + uint hash3 = xxHash32.ComputeHash(rspan, rspan.Length); // Assert Assert.Equal(hash1, (uint) 0x2330eac0); Assert.Equal(hash2, (uint) 0x2330eac0); + Assert.Equal(hash3, (uint) 0x2330eac0); } [Fact] @@ -29,14 +33,17 @@ // Arrange byte[] data = {0xde, 0x55, 0x47, 0x7f, 0x14}; Span span = new Span(data); + ReadOnlySpan rspan = new ReadOnlySpan(data); // Act uint hash1 = xxHash32.ComputeHash(data, data.Length); uint hash2 = xxHash32.ComputeHash(span, span.Length); + uint hash3 = xxHash32.ComputeHash(rspan, rspan.Length); // Assert Assert.Equal(hash1, (uint) 0x112348ba); Assert.Equal(hash2, (uint) 0x112348ba); + Assert.Equal(hash3, (uint) 0x112348ba); } [Fact] @@ -49,14 +56,17 @@ 0x22, 0x3a, 0x40, 0x96, 0x56, 0xc5, 0xdc, 0xbb }; Span span = new Span(data); + ReadOnlySpan rspan = new ReadOnlySpan(data); // Act uint hash1 = xxHash32.ComputeHash(data, data.Length); uint hash2 = xxHash32.ComputeHash(span, span.Length); + uint hash3 = xxHash32.ComputeHash(rspan, rspan.Length); // Assert Assert.Equal(hash1, (uint) 0xcdf89609); Assert.Equal(hash2, (uint) 0xcdf89609); + Assert.Equal(hash3, (uint) 0xcdf89609); } [Fact] @@ -70,14 +80,17 @@ 0x0e }; Span span = new Span(data); + ReadOnlySpan rspan = new ReadOnlySpan(data); // Act uint hash1 = xxHash32.ComputeHash(data, data.Length); uint hash2 = xxHash32.ComputeHash(span, span.Length); + uint hash3 = xxHash32.ComputeHash(rspan, rspan.Length); // Assert Assert.Equal(hash1, (uint) 0xbca8f924); Assert.Equal(hash2, (uint) 0xbca8f924); + Assert.Equal(hash3, (uint) 0xbca8f924); } [Fact] @@ -91,14 +104,17 @@ 0x0e, 0x59, 0x4d, 0x42, 0xc5 }; Span span = new Span(data); + ReadOnlySpan rspan = new ReadOnlySpan(data); // Act uint hash1 = xxHash32.ComputeHash(data, data.Length); uint hash2 = xxHash32.ComputeHash(span, span.Length); + uint hash3 = xxHash32.ComputeHash(rspan, rspan.Length); // Assert Assert.Equal(hash1, (uint) 0xf4518e14); Assert.Equal(hash2, (uint) 0xf4518e14); + Assert.Equal(hash3, (uint) 0xf4518e14); } [Fact] @@ -113,14 +129,17 @@ 0x1c, 0x2c, 0xc9, 0x38, 0x7d, 0x43, 0x83, 0x11, }; Span span = new Span(data); + ReadOnlySpan rspan = new ReadOnlySpan(data); // Act uint hash1 = xxHash32.ComputeHash(data, data.Length); uint hash2 = xxHash32.ComputeHash(span, span.Length); + uint hash3 = xxHash32.ComputeHash(rspan, rspan.Length); // Assert Assert.Equal(hash1, (uint) 0xf8497daa); Assert.Equal(hash2, (uint) 0xf8497daa); + Assert.Equal(hash3, (uint) 0xf8497daa); } [Fact] @@ -348,6 +367,29 @@ // Assert Assert.Equal(hash, (uint) 0xf8497daa); } + + [Fact] + public async Task Compute_hash32_for_the_async_stream_32_with_cancelation_token() + { + // Arrange + byte[] data = new byte[] + { + 0xde, 0x55, 0x47, 0x7f, 0x14, 0x8f, 0xf1, 0x48, + 0x22, 0x3a, 0x40, 0x96, 0x56, 0xc5, 0xdc, 0xbb, + 0x0e, 0x59, 0x4d, 0x42, 0xc5, 0x07, 0x21, 0x08, + 0x1c, 0x2c, 0xc9, 0x38, 0x7d, 0x43, 0x83, 0x11, + }; + CancellationTokenSource tokenSource = new CancellationTokenSource(); + + // Act + tokenSource.Cancel(); + + // Assert + await Assert.ThrowsAsync(async () => + { + await xxHash32.ComputeHashAsync(new MemoryStream(data), 4096, 0, tokenSource.Token); + }); + } [Fact] public async Task Compute_hash32_for_the_async_random_stream() diff --git a/src/Standart.Hash.xxHash.Test/xxHash64Test.cs b/src/Standart.Hash.xxHash.Test/xxHash64Test.cs index 013159f..b8e7b38 100644 --- a/src/Standart.Hash.xxHash.Test/xxHash64Test.cs +++ b/src/Standart.Hash.xxHash.Test/xxHash64Test.cs @@ -2,6 +2,7 @@ { using System; using System.IO; + using System.Threading; using System.Threading.Tasks; using Xunit; @@ -13,14 +14,17 @@ // Arrange byte[] data = {0x60}; Span span = new Span(data); + ReadOnlySpan rspan = new ReadOnlySpan(data); // Act ulong hash1 = xxHash64.ComputeHash(data, data.Length); - ulong hash2 = xxHash64.ComputeHash(span, data.Length); + ulong hash2 = xxHash64.ComputeHash(span, span.Length); + ulong hash3 = xxHash64.ComputeHash(rspan, rspan.Length); // Assert Assert.Equal(hash1, (ulong) 0xb3e7ca6ca5ba3445); Assert.Equal(hash2, (ulong) 0xb3e7ca6ca5ba3445); + Assert.Equal(hash3, (ulong) 0xb3e7ca6ca5ba3445); } [Fact] @@ -29,14 +33,17 @@ // Arrange byte[] data = {0x60, 0x82, 0x40, 0x77, 0x8a}; Span span = new Span(data); + ReadOnlySpan rspan = new ReadOnlySpan(data); // Act ulong hash1 = xxHash64.ComputeHash(data, data.Length); - ulong hash2 = xxHash64.ComputeHash(span, data.Length); + ulong hash2 = xxHash64.ComputeHash(span, span.Length); + ulong hash3 = xxHash64.ComputeHash(rspan, rspan.Length); // Assert Assert.Equal(hash1, (ulong) 0x917b11ed024938fc); Assert.Equal(hash2, (ulong) 0x917b11ed024938fc); + Assert.Equal(hash3, (ulong) 0x917b11ed024938fc); } [Fact] @@ -49,14 +56,17 @@ 0x85, 0x1f, 0xa6, 0x86, 0x34, }; Span span = new Span(data); + ReadOnlySpan rspan = new ReadOnlySpan(data); // Act ulong hash1 = xxHash64.ComputeHash(data, data.Length); - ulong hash2 = xxHash64.ComputeHash(span, data.Length); + ulong hash2 = xxHash64.ComputeHash(span, span.Length); + ulong hash3 = xxHash64.ComputeHash(rspan, rspan.Length); // Assert Assert.Equal(hash1, (ulong) 0x9d1cb0d181d58bee); Assert.Equal(hash2, (ulong) 0x9d1cb0d181d58bee); + Assert.Equal(hash3, (ulong) 0x9d1cb0d181d58bee); } [Fact] @@ -71,14 +81,17 @@ 0x4b, 0x0f, 0x90, 0x4e, 0xf5, 0x57, 0x21, 0x21, }; Span span = new Span(data); + ReadOnlySpan rspan = new ReadOnlySpan(data); // Act ulong hash1 = xxHash64.ComputeHash(data, data.Length); - ulong hash2 = xxHash64.ComputeHash(span, data.Length); + ulong hash2 = xxHash64.ComputeHash(span, span.Length); + ulong hash3 = xxHash64.ComputeHash(rspan, rspan.Length); // Assert Assert.Equal(hash1, (ulong) 0x9233096b7804e12c); Assert.Equal(hash2, (ulong) 0x9233096b7804e12c); + Assert.Equal(hash3, (ulong) 0x9233096b7804e12c); } [Fact] @@ -97,14 +110,17 @@ 0x7e, 0x28, 0xce, 0x46, 0x85, 0xb7, 0x2b, 0x16, }; Span span = new Span(data); + ReadOnlySpan rspan = new ReadOnlySpan(data); // Act ulong hash1 = xxHash64.ComputeHash(data, data.Length); - ulong hash2 = xxHash64.ComputeHash(span, data.Length); + ulong hash2 = xxHash64.ComputeHash(span, span.Length); + ulong hash3 = xxHash64.ComputeHash(rspan, rspan.Length); // Assert Assert.Equal(hash1, (ulong) 0x4c0a65b1ef9ea060); Assert.Equal(hash2, (ulong) 0x4c0a65b1ef9ea060); + Assert.Equal(hash3, (ulong) 0x4c0a65b1ef9ea060); } [Fact] @@ -192,6 +208,7 @@ Assert.Equal(hash, (ulong) 0x4c0a65b1ef9ea060); } + [Fact] public void Compute_hash64_for_the_random_stream() { @@ -308,6 +325,33 @@ Assert.Equal(hash, (ulong) 0x4c0a65b1ef9ea060); } + [Fact] + public async Task Compute_hash64_for_the_async_stream_64_with_cancelation_token() + { + // Arrange + byte[] data = + { + 0x60, 0x82, 0x40, 0x77, 0x8a, 0x0e, 0xe4, 0xd5, + 0x85, 0x1f, 0xa6, 0x86, 0x34, 0x01, 0xd7, 0xf2, + 0x30, 0x5d, 0x84, 0x54, 0x15, 0xf9, 0xbd, 0x03, + 0x4b, 0x0f, 0x90, 0x4e, 0xf5, 0x57, 0x21, 0x21, + 0xed, 0x8c, 0x19, 0x93, 0xbd, 0x01, 0x12, 0x0c, + 0x20, 0xb0, 0x33, 0x98, 0x4b, 0xe7, 0xc1, 0x0a, + 0x27, 0x6d, 0xb3, 0x5c, 0xc7, 0xc0, 0xd0, 0xa0, + 0x7e, 0x28, 0xce, 0x46, 0x85, 0xb7, 0x2b, 0x16, + }; + CancellationTokenSource tokenSource = new CancellationTokenSource(); + + // Act + tokenSource.Cancel(); + + // Assert + await Assert.ThrowsAsync(async() => + { + await xxHash64.ComputeHashAsync(new MemoryStream(data), 8192, 0, tokenSource.Token); + }); + } + [Fact] public async Task Compute_hash64_for_the_async_random_stream() { diff --git a/src/Standart.Hash.xxHash/xxHash32.Async.cs b/src/Standart.Hash.xxHash/xxHash32.Async.cs index 2dc935d..9f51024 100644 --- a/src/Standart.Hash.xxHash/xxHash32.Async.cs +++ b/src/Standart.Hash.xxHash/xxHash32.Async.cs @@ -3,6 +3,7 @@ using System.Buffers; using System.Diagnostics; using System.IO; + using System.Threading; using System.Threading.Tasks; public static partial class xxHash32 @@ -15,6 +16,20 @@ /// The seed number /// The hash public static async ValueTask ComputeHashAsync(Stream stream, int bufferSize = 4096, uint seed = 0) + { + return await ComputeHashAsync(stream, bufferSize, seed, CancellationToken.None); + } + + + ///

+ /// Compute xxHash for the async stream + /// + /// The stream of data + /// The buffer size + /// The seed number + /// The cancellation token + /// The hash + public static async ValueTask ComputeHashAsync(Stream stream, int bufferSize, uint seed, CancellationToken cancellationToken) { Debug.Assert(stream != null); Debug.Assert(bufferSize > 16); @@ -35,8 +50,14 @@ try { // Read flow of bytes - while ((readBytes = await stream.ReadAsync(buffer, offset, bufferSize).ConfigureAwait(false)) > 0) + while ((readBytes = await stream.ReadAsync(buffer, offset, bufferSize, cancellationToken).ConfigureAwait(false)) > 0) { + // Exit if the operation is canceled + if (cancellationToken.IsCancellationRequested) + { + return await Task.FromCanceled(cancellationToken); + } + length = length + readBytes; offset = offset + readBytes; diff --git a/src/Standart.Hash.xxHash/xxHash32.Span.cs b/src/Standart.Hash.xxHash/xxHash32.Span.cs index a59fc50..1e7114d 100644 --- a/src/Standart.Hash.xxHash/xxHash32.Span.cs +++ b/src/Standart.Hash.xxHash/xxHash32.Span.cs @@ -3,6 +3,8 @@ using System.Diagnostics; namespace Standart.Hash.xxHash { + using System.Runtime.InteropServices; + public static partial class xxHash32 { /// @@ -18,7 +20,26 @@ namespace Standart.Hash.xxHash Debug.Assert(length >= 0); Debug.Assert(length <= data.Length); - fixed (byte* pData = &data[0]) + fixed (byte* pData = &MemoryMarshal.GetReference(data)) + { + return UnsafeComputeHash(pData, length, seed); + } + } + + /// + /// Compute xxHash for the data byte span + /// + /// The source of data + /// The length of the data for hashing + /// The seed number + /// hash + public static unsafe uint ComputeHash(ReadOnlySpan data, int length, uint seed = 0) + { + Debug.Assert(data != null); + Debug.Assert(length >= 0); + Debug.Assert(length <= data.Length); + + fixed (byte* pData = &MemoryMarshal.GetReference(data)) { return UnsafeComputeHash(pData, length, seed); } diff --git a/src/Standart.Hash.xxHash/xxHash64.Async.cs b/src/Standart.Hash.xxHash/xxHash64.Async.cs index 3a3b5db..896611c 100644 --- a/src/Standart.Hash.xxHash/xxHash64.Async.cs +++ b/src/Standart.Hash.xxHash/xxHash64.Async.cs @@ -3,6 +3,7 @@ using System.Buffers; using System.Diagnostics; using System.IO; + using System.Threading; using System.Threading.Tasks; public static partial class xxHash64 @@ -15,6 +16,19 @@ /// The seed number /// The hash public static async ValueTask ComputeHashAsync(Stream stream, int bufferSize = 8192, ulong seed = 0) + { + return await ComputeHashAsync(stream, bufferSize, seed, CancellationToken.None); + } + + /// + /// Compute xxHash for the async stream + /// + /// The stream of data + /// The buffer size + /// The seed number + /// The cancelation token + /// The hash + public static async ValueTask ComputeHashAsync(Stream stream, int bufferSize, ulong seed, CancellationToken cancellationToken) { Debug.Assert(stream != null); Debug.Assert(bufferSize > 32); @@ -35,8 +49,14 @@ try { // Read flow of bytes - while ((readBytes = await stream.ReadAsync(buffer, offset, bufferSize).ConfigureAwait(false)) > 0) - { + while ((readBytes = await stream.ReadAsync(buffer, offset, bufferSize, cancellationToken).ConfigureAwait(false)) > 0) + { + // Exit if the operation is canceled + if (cancellationToken.IsCancellationRequested) + { + return await Task.FromCanceled(cancellationToken); + } + length = length + readBytes; offset = offset + readBytes; diff --git a/src/Standart.Hash.xxHash/xxHash64.Span.cs b/src/Standart.Hash.xxHash/xxHash64.Span.cs index f393a8c..d8dbf0e 100644 --- a/src/Standart.Hash.xxHash/xxHash64.Span.cs +++ b/src/Standart.Hash.xxHash/xxHash64.Span.cs @@ -2,6 +2,7 @@ { using System; using System.Diagnostics; + using System.Runtime.InteropServices; public static partial class xxHash64 { @@ -18,7 +19,26 @@ Debug.Assert(length >= 0); Debug.Assert(length <= data.Length); - fixed (byte* pData = &data[0]) + fixed (byte* pData = &MemoryMarshal.GetReference(data)) + { + return UnsafeComputeHash(pData, length, seed); + } + } + + /// + /// Compute xxHash for the data byte span + /// + /// The source of data + /// The length of the data for hashing + /// The seed number + /// hash + public static unsafe ulong ComputeHash(ReadOnlySpan data, int length, ulong seed = 0) + { + Debug.Assert(data != null); + Debug.Assert(length >= 0); + Debug.Assert(length <= data.Length); + + fixed (byte* pData = &MemoryMarshal.GetReference(data)) { return UnsafeComputeHash(pData, length, seed); }