-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Use optimized hash creation methods on .NET 5+ #67999
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,15 +5,14 @@ | |
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Collections.Immutable; | ||
| using System.Diagnostics; | ||
| using System.IO; | ||
| using System.Security.Cryptography; | ||
| using System.Runtime.InteropServices; | ||
| using System.Security.Cryptography; | ||
| using System.Threading; | ||
| using Microsoft.CodeAnalysis.PooledObjects; | ||
| using Microsoft.CodeAnalysis.Serialization; | ||
| using Roslyn.Utilities; | ||
| using System.Diagnostics; | ||
| using System.Runtime.CompilerServices; | ||
|
|
||
| namespace Microsoft.CodeAnalysis | ||
| { | ||
|
|
@@ -23,47 +22,102 @@ internal partial class Checksum | |
| // https://github.com/dotnet/runtime/blob/f2db6d6093c54e5eeb9db2d8dcbe15b2db92ad8c/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/SHA256.cs#L18-L19 | ||
| private const int SHA256HashSizeBytes = 256 / 8; | ||
|
|
||
| #if NET5_0_OR_GREATER | ||
| private static readonly ObjectPool<IncrementalHash> s_incrementalHashPool = | ||
| new(() => IncrementalHash.CreateHash(HashAlgorithmName.SHA256), size: 20); | ||
| #else | ||
| private static readonly ObjectPool<SHA256> s_incrementalHashPool = | ||
| new(SHA256.Create, size: 20); | ||
| #endif | ||
|
|
||
| #if !NET5_0_OR_GREATER | ||
| // Dedicated pools for the byte[]s we use to create checksums from two or three existing checksums. Sized to | ||
| // exactly the space needed to splat the existing checksum data into the array and then hash it. | ||
|
|
||
| private static readonly ObjectPool<byte[]> s_twoChecksumByteArrayPool = new(() => new byte[HashSize * 2]); | ||
| private static readonly ObjectPool<byte[]> s_threeChecksumByteArrayPool = new(() => new byte[HashSize * 3]); | ||
| #endif | ||
|
|
||
| public static Checksum Create(IEnumerable<string> values) | ||
| { | ||
| #if NET5_0_OR_GREATER | ||
| using var pooledHash = s_incrementalHashPool.GetPooledObject(); | ||
|
|
||
| foreach (var value in values) | ||
| { | ||
| pooledHash.Object.AppendData(MemoryMarshal.AsBytes(value.AsSpan())); | ||
| pooledHash.Object.AppendData(MemoryMarshal.AsBytes("\0".AsSpan())); | ||
| } | ||
|
|
||
| Span<byte> hash = stackalloc byte[SHA256HashSizeBytes]; | ||
| pooledHash.Object.GetHashAndReset(hash); | ||
| return From(hash); | ||
| #else | ||
| using var pooledHash = s_incrementalHashPool.GetPooledObject(); | ||
| using var pooledBuffer = SharedPools.ByteArray.GetPooledObject(); | ||
| var hash = pooledHash.Object; | ||
|
|
||
| hash.Initialize(); | ||
| foreach (var value in values) | ||
| { | ||
| AppendData(hash, pooledBuffer.Object, value); | ||
| AppendData(hash, pooledBuffer.Object, "\0"); | ||
| } | ||
|
|
||
| return From(hash.GetHashAndReset()); | ||
| hash.TransformFinalBlock(Array.Empty<byte>(), 0, 0); | ||
| return From(hash.Hash); | ||
| #endif | ||
| } | ||
|
|
||
| public static Checksum Create(string value) | ||
| { | ||
| #if NET5_0_OR_GREATER | ||
| Span<byte> hash = stackalloc byte[SHA256HashSizeBytes]; | ||
| SHA256.HashData(MemoryMarshal.AsBytes(value.AsSpan()), hash); | ||
| return From(hash); | ||
| #else | ||
| using var pooledHash = s_incrementalHashPool.GetPooledObject(); | ||
| using var pooledBuffer = SharedPools.ByteArray.GetPooledObject(); | ||
| var hash = pooledHash.Object; | ||
| hash.Initialize(); | ||
|
|
||
| AppendData(hash, pooledBuffer.Object, value); | ||
|
|
||
| return From(hash.GetHashAndReset()); | ||
| hash.TransformFinalBlock(Array.Empty<byte>(), 0, 0); | ||
| return From(hash.Hash); | ||
| #endif | ||
| } | ||
|
|
||
| public static Checksum Create(Stream stream) | ||
| { | ||
| #if NET7_0_OR_GREATER | ||
| Span<byte> hash = stackalloc byte[SHA256HashSizeBytes]; | ||
| SHA256.HashData(stream, hash); | ||
| return From(hash); | ||
| #elif NET5_0_OR_GREATER | ||
| using var pooledHash = s_incrementalHashPool.GetPooledObject(); | ||
| Span<byte> buffer = stackalloc byte[SharedPools.ByteBufferSize]; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 4K is a much larger buffer than we typically like to stackalloc. It might be fine for your scenarios, just calling it out. We have a rough guideline of not stackalloc'ing more than 1K.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (Also, keep in mind this whole buffer will be zero'd unless you're using SkipLocalsInit.) |
||
|
|
||
| int bytesRead; | ||
| do | ||
| { | ||
| bytesRead = stream.Read(buffer); | ||
| if (bytesRead > 0) | ||
| { | ||
| pooledHash.Object.AppendData(buffer[..bytesRead]); | ||
| } | ||
| } | ||
| while (bytesRead > 0); | ||
|
|
||
| Span<byte> hash = stackalloc byte[SHA256HashSizeBytes]; | ||
| pooledHash.Object.GetHashAndReset(hash); | ||
| return From(hash); | ||
| #else | ||
| using var pooledHash = s_incrementalHashPool.GetPooledObject(); | ||
| using var pooledBuffer = SharedPools.ByteArray.GetPooledObject(); | ||
|
|
||
| var hash = pooledHash.Object; | ||
| hash.Initialize(); | ||
|
|
||
| var buffer = pooledBuffer.Object; | ||
| var bufferLength = buffer.Length; | ||
|
|
@@ -73,12 +127,13 @@ public static Checksum Create(Stream stream) | |
| bytesRead = stream.Read(buffer, 0, bufferLength); | ||
| if (bytesRead > 0) | ||
| { | ||
| hash.AppendData(buffer, 0, bytesRead); | ||
| hash.TransformBlock(buffer, 0, bytesRead, null, 0); | ||
| } | ||
| } | ||
| while (bytesRead > 0); | ||
|
|
||
| var bytes = hash.GetHashAndReset(); | ||
| hash.TransformFinalBlock(Array.Empty<byte>(), 0, 0); | ||
| var bytes = hash.Hash; | ||
|
|
||
| // if bytes array is bigger than certain size, checksum | ||
| // will truncate it to predetermined size. for more detail, | ||
|
|
@@ -91,6 +146,7 @@ public static Checksum Create(Stream stream) | |
| // hash algorithm used here should remain functionally correct even | ||
| // after the truncation | ||
| return From(bytes); | ||
| #endif | ||
| } | ||
|
|
||
| public static Checksum Create(IObjectWritable @object) | ||
|
|
@@ -124,36 +180,44 @@ public static Checksum Create(Checksum checksum1, Checksum checksum2, Checksum c | |
| #endif | ||
| } | ||
|
|
||
| #if !NET5_0_OR_GREATER | ||
|
|
||
| private static Checksum CreateUsingByteArrays(Checksum checksum1, Checksum checksum2) | ||
| { | ||
| using var hash = s_incrementalHashPool.GetPooledObject(); | ||
| using var bytes = s_twoChecksumByteArrayPool.GetPooledObject(); | ||
|
|
||
| var bytesSpan = bytes.Object.AsSpan(); | ||
| checksum1.WriteTo(bytesSpan); | ||
| checksum2.WriteTo(bytesSpan.Slice(HashSize)); | ||
|
|
||
| hash.Object.AppendData(bytes.Object); | ||
| using var hash = s_incrementalHashPool.GetPooledObject(); | ||
| hash.Object.Initialize(); | ||
|
|
||
| hash.Object.TransformBlock(bytes.Object, 0, bytes.Object.Length, null, 0); | ||
|
|
||
| return From(hash.Object.GetHashAndReset()); | ||
| hash.Object.TransformFinalBlock(Array.Empty<byte>(), 0, 0); | ||
| return From(hash.Object.Hash); | ||
| } | ||
|
|
||
| private static Checksum CreateUsingByteArrays(Checksum checksum1, Checksum checksum2, Checksum checksum3) | ||
| { | ||
| using var hash = s_incrementalHashPool.GetPooledObject(); | ||
| using var bytes = s_threeChecksumByteArrayPool.GetPooledObject(); | ||
|
|
||
| var bytesSpan = bytes.Object.AsSpan(); | ||
| checksum1.WriteTo(bytesSpan); | ||
| checksum2.WriteTo(bytesSpan.Slice(HashSize)); | ||
| checksum3.WriteTo(bytesSpan.Slice(2 * HashSize)); | ||
|
|
||
| hash.Object.AppendData(bytes.Object); | ||
| using var hash = s_incrementalHashPool.GetPooledObject(); | ||
| hash.Object.Initialize(); | ||
|
|
||
| hash.Object.TransformBlock(bytes.Object, 0, bytes.Object.Length, null, 0); | ||
|
|
||
| return From(hash.Object.GetHashAndReset()); | ||
| hash.Object.TransformFinalBlock(Array.Empty<byte>(), 0, 0); | ||
| return From(hash.Object.Hash); | ||
| } | ||
|
|
||
| #if NET | ||
| #else | ||
|
|
||
| // Optimized helpers that do not need to allocate any arrays to combine hashes. | ||
|
|
||
|
|
@@ -234,7 +298,8 @@ public static Checksum Create(ParseOptions value, ISerializerService serializer) | |
| return Create(stream); | ||
| } | ||
|
|
||
| private static void AppendData(IncrementalHash hash, byte[] buffer, string value) | ||
| #if !NET5_0_OR_GREATER | ||
| private static void AppendData(SHA256 hash, byte[] buffer, string value) | ||
| { | ||
| var stringBytes = MemoryMarshal.AsBytes(value.AsSpan()); | ||
| Debug.Assert(stringBytes.Length == value.Length * 2); | ||
|
|
@@ -246,29 +311,11 @@ private static void AppendData(IncrementalHash hash, byte[] buffer, string value | |
| var toCopy = Math.Min(remaining, buffer.Length); | ||
|
|
||
| stringBytes.Slice(index, toCopy).CopyTo(buffer); | ||
| hash.AppendData(buffer, 0, toCopy); | ||
| hash.TransformBlock(buffer, 0, toCopy, null, 0); | ||
|
|
||
| index += toCopy; | ||
| } | ||
| } | ||
|
|
||
| public static class TestAccessor | ||
| { | ||
| public static Checksum CreateUsingByteArrays(Checksum checksum1, Checksum checksum2) | ||
| => Checksum.CreateUsingByteArrays(checksum1, checksum2); | ||
|
|
||
| public static Checksum CreateUsingByteArrays(Checksum checksum1, Checksum checksum2, Checksum checksum3) | ||
| => Checksum.CreateUsingByteArrays(checksum1, checksum2, checksum3); | ||
|
|
||
| #if NET | ||
|
|
||
| public static Checksum CreateUsingSpans(Checksum checksum1, Checksum checksum2) | ||
| => Checksum.CreateUsingSpans(checksum1, checksum2); | ||
|
|
||
| public static Checksum CreateUsingSpans(Checksum checksum1, Checksum checksum2, Checksum checksum3) | ||
| => Checksum.CreateUsingSpans(checksum1, checksum2, checksum3); | ||
|
|
||
| #endif | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.