Skip to content
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

Vectorize Convert.ToBase64String #71795

Merged
merged 17 commits into from
Jul 10, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@

namespace System.Buffers.Text
{
public static partial class Base64
#if SYSTEM_PRIVATE_CORELIB
public
#else
internal
#endif
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
static partial class Base64
{
[Conditional("DEBUG")]
private static unsafe void AssertRead<TVector>(byte* src, byte* srcStart, int srcLength)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ namespace System.Buffers.Text
// AVX2 version based on https://github.com/aklomp/base64/tree/e516d769a2a432c08404f1981e73b431566057be/lib/arch/avx2
// Vector128 version based on https://github.com/aklomp/base64/tree/e516d769a2a432c08404f1981e73b431566057be/lib/arch/ssse3

public static partial class Base64
#if SYSTEM_PRIVATE_CORELIB
public
#else
internal
#endif
static partial class Base64
{
/// <summary>
/// Decode the span of UTF-8 encoded text represented as base64 into binary data.
Expand Down Expand Up @@ -477,22 +482,6 @@ private static unsafe void Avx2Decode(ref byte* srcBytes, ref byte* destBytes, b
destBytes = dest;
}

// This can be replaced once https://github.com/dotnet/runtime/issues/63331 is implemented.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128<byte> SimdShuffle(Vector128<byte> left, Vector128<byte> right, Vector128<byte> mask8F)
{
Debug.Assert((Ssse3.IsSupported || AdvSimd.Arm64.IsSupported) && BitConverter.IsLittleEndian);

if (Ssse3.IsSupported)
{
return Ssse3.Shuffle(left, right);
}
else
{
return AdvSimd.Arm64.VectorTableLookup(left, Vector128.BitwiseAnd(right, mask8F));
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe void Vector128Decode(ref byte* srcBytes, ref byte* destBytes, byte* srcEnd, int sourceLength, int destLength, byte* srcStart, byte* destStart)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
Expand All @@ -15,8 +16,29 @@ namespace System.Buffers.Text
/// <summary>
/// Convert between binary data and UTF-8 encoded text that is represented in base 64.
/// </summary>
public static partial class Base64
#if SYSTEM_PRIVATE_CORELIB
public
#else
internal
#endif
static partial class Base64
{
// This can be replaced once https://github.com/dotnet/runtime/issues/63331 is implemented.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128<byte> SimdShuffle(Vector128<byte> left, Vector128<byte> right, Vector128<byte> mask8F)
{
Debug.Assert((Ssse3.IsSupported || AdvSimd.Arm64.IsSupported) && BitConverter.IsLittleEndian);

if (Ssse3.IsSupported)
{
return Ssse3.Shuffle(left, right);
}
else
{
return AdvSimd.Arm64.VectorTableLookup(left, Vector128.BitwiseAnd(right, mask8F));
}
}
stephentoub marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Encode the span of binary data into UTF-8 encoded text represented as base64.
/// </summary>
Expand Down
6 changes: 3 additions & 3 deletions src/libraries/System.Memory/src/System.Memory.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@
<Compile Include="System\Buffers\SequenceReader.cs" />
<Compile Include="System\Buffers\SequenceReader.Search.cs" />
<Compile Include="System\Buffers\SequenceReaderExtensions.Binary.cs" />
<Compile Include="System\Buffers\Text\Base64.cs" />
<Compile Include="System\Buffers\Text\Base64Decoder.cs" />
<Compile Include="System\Buffers\Text\Base64Encoder.cs" />
<Compile Include="$(CommonPath)System\Buffers\Text\Base64.cs" />
<Compile Include="$(CommonPath)System\Buffers\Text\Base64Decoder.cs" />
<Compile Include="$(CommonPath)System\Buffers\Text\Base64Encoder.cs" />
<Compile Include="System\Runtime\InteropServices\SequenceMarshal.cs" />
<Compile Include="System\Text\EncodingExtensions.cs" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,8 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Configuration\Assemblies\AssemblyVersionCompatibility.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Context.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Convert.Base64.cs" />
<Compile Include="$(CommonPath)System\Buffers\Text\Base64.cs" />
<Compile Include="$(CommonPath)System\Buffers\Text\Base64Encoder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Convert.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\CoreLib.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\CurrentSystemTimeZone.cs" />
Expand Down
22 changes: 21 additions & 1 deletion src/libraries/System.Private.CoreLib/src/System/Convert.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Buffers;
using System.Buffers.Text;
using System.Text;

namespace System
{
Expand Down Expand Up @@ -2336,7 +2339,24 @@ public static string ToBase64String(ReadOnlySpan<byte> bytes, Base64FormattingOp
}

bool insertLineBreaks = (options == Base64FormattingOptions.InsertLineBreaks);
string result = string.FastAllocateString(ToBase64_CalculateAndValidateOutputLength(bytes.Length, insertLineBreaks));
int outputLength = ToBase64_CalculateAndValidateOutputLength(bytes.Length, insertLineBreaks);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@EgorBo, do we need something similar to this change for Convert.ToBase64CharArray?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stephentoub makes sense!


if (!insertLineBreaks && bytes.Length >= 64)
{
// For large inputs it's faster to allocate a temp buffer and call UTF8 version
// which is then extended to UTF8 via Latin1.GetString (base64 is always ASCI)
[MethodImpl(MethodImplOptions.NoInlining)]
static string ToBase64StringLargeInputs(ReadOnlySpan<byte> data, int outputLen)
{
Span<byte> utf8buffer = outputLen <= 256 ? stackalloc byte[256] : new byte[outputLen];
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
OperationStatus status = Base64.EncodeToUtf8(data, utf8buffer, out int _, out int _);
Debug.Assert(status == OperationStatus.Done);
return Encoding.Latin1.GetString(utf8buffer);
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
}
return ToBase64StringLargeInputs(bytes, outputLength);
}

string result = string.FastAllocateString(outputLength);

unsafe
{
Expand Down