Last active
February 13, 2020 21:46
-
-
Save benaadams/21d3bd5207314c075cc559a97f924526 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Buffers; | |
using System.Diagnostics; | |
using System.Runtime.CompilerServices; | |
using System.Runtime.InteropServices; | |
using System.Runtime.Intrinsics; | |
using System.Runtime.Intrinsics.X86; | |
using System.Text; | |
using nint = System.Int64; | |
public class Program | |
{ | |
static void Main() | |
{ | |
Console.WriteLine("Input : The quick brown fox jumped over the lazy dog"); | |
Console.WriteLine( | |
Conversion.AsciiToString( | |
Encoding.ASCII.GetBytes( | |
"Output: The quick brown fox jumped over the lazy dog"))); | |
for (var i = 0; i < 127; i++) | |
{ | |
var s = string.Create(i, i, (span, c) => { | |
for (int i = 0; i < span.Length; i++) | |
{ | |
span[i] = (char)i; | |
} | |
}); | |
var b = Encoding.ASCII.GetBytes(s); | |
if (Conversion.AsciiToString(b) != s) | |
{ | |
Console.WriteLine($"Fail at length {i}"); | |
return; | |
}; | |
} | |
Console.WriteLine("Success for lengths 0 to 127 of ascending ascii chars"); | |
} | |
} | |
public class Conversion | |
{ | |
private readonly static SpanAction<char, ReadOnlyMemory<byte>> s_asciiToString | |
= new Conversion().AsciiToString; // Create an instance for a faster delegate | |
public static string AsciiToString(ReadOnlyMemory<byte> input) | |
=> string.Create(input.Length, input, s_asciiToString); | |
// Delegates to instance methods are faster than ones to static methods | |
private void AsciiToString(Span<char> output, ReadOnlyMemory<byte> memory) | |
{ | |
// This is unsafe as non-ascii characters will _not_ be handled correctly | |
var input = memory.Span; | |
Debug.Assert(output.Length == input.Length); | |
ref var inputStart = ref MemoryMarshal.GetReference(input); | |
ref var outputStart = ref MemoryMarshal.GetReference(output); | |
nint offset = 0; | |
nint length = input.Length - Vector256<byte>.Count; | |
if (Avx2.IsSupported && length >= 0) | |
{ | |
Vector256<byte> asciiVector, asciiVectorLow, asciiVectorHigh; | |
Vector256<byte> zeroVector = Vector256<byte>.Zero; | |
// First time this checks again against 0, however we will move into final widen if it fails. | |
while (length > offset) | |
{ | |
asciiVector = Unsafe.ReadUnaligned<Vector256<byte>>(ref Unsafe.Add(ref inputStart, (IntPtr)offset)); | |
asciiVectorLow = Avx2.Permute4x64(asciiVector.AsUInt64(), 0xd4).AsByte(); | |
Unsafe.WriteUnaligned( | |
ref Unsafe.As<char, byte>( | |
ref Unsafe.Add(ref outputStart, (IntPtr)offset)), | |
Avx2.UnpackLow(asciiVectorLow, zeroVector)); | |
asciiVectorHigh = Avx2.Permute4x64(asciiVector.AsUInt64(), 0xe8).AsByte(); | |
Unsafe.WriteUnaligned( | |
ref Unsafe.As<char, byte>( | |
ref Unsafe.Add(ref outputStart, (IntPtr)(offset + Vector256<ushort>.Count))), | |
Avx2.UnpackHigh(asciiVectorHigh, zeroVector)); | |
offset += Vector256<byte>.Count; | |
} | |
// Do final widen vector size from end, which may overlap slightly with the final loop iteration. | |
offset = input.Length - Vector256<byte>.Count; | |
asciiVector = Unsafe.ReadUnaligned<Vector256<byte>>(ref Unsafe.Add(ref inputStart, (IntPtr)offset)); | |
asciiVectorLow = Avx2.Permute4x64(asciiVector.AsUInt64(), 0xd4).AsByte(); | |
Unsafe.WriteUnaligned( | |
ref Unsafe.As<char, byte>( | |
ref Unsafe.Add(ref outputStart, (IntPtr)offset)), | |
Avx2.UnpackLow(asciiVectorLow, zeroVector)); | |
asciiVectorHigh = Avx2.Permute4x64(asciiVector.AsUInt64(), 0xe8).AsByte(); | |
Unsafe.WriteUnaligned( | |
ref Unsafe.As<char, byte>( | |
ref Unsafe.Add(ref outputStart, (IntPtr)(offset + Vector256<ushort>.Count))), | |
Avx2.UnpackHigh(asciiVectorHigh, zeroVector)); | |
return; // All done | |
} | |
length = input.Length - Vector128<byte>.Count; | |
if (Sse2.IsSupported && length >= 0) | |
{ | |
Vector128<byte> asciiVector; | |
Vector128<byte> zeroVector = Vector128<byte>.Zero; | |
// First time this checks again against 0, however we will move into final widen if it fails. | |
while (length > offset) | |
{ | |
asciiVector = Unsafe.ReadUnaligned<Vector128<byte>>(ref Unsafe.Add(ref inputStart, (IntPtr)offset)); | |
Unsafe.WriteUnaligned( | |
ref Unsafe.As<char, byte>( | |
ref Unsafe.Add(ref outputStart, (IntPtr)offset)), | |
Sse2.UnpackLow(asciiVector, zeroVector)); | |
Unsafe.WriteUnaligned( | |
ref Unsafe.As<char, byte>( | |
ref Unsafe.Add(ref outputStart, (IntPtr)(offset + Vector128<ushort>.Count))), | |
Sse2.UnpackHigh(asciiVector, zeroVector)); | |
offset += Vector128<byte>.Count; | |
} | |
// Do final widen vector size from end, which may overlap slightly with the final loop iteration. | |
offset = input.Length - Vector128<byte>.Count; | |
asciiVector = Unsafe.ReadUnaligned<Vector128<byte>>(ref Unsafe.Add(ref inputStart, (IntPtr)offset)); | |
Unsafe.WriteUnaligned( | |
ref Unsafe.As<char, byte>( | |
ref Unsafe.Add(ref outputStart, (IntPtr)offset)), | |
Sse2.UnpackLow(asciiVector, zeroVector)); | |
Unsafe.WriteUnaligned( | |
ref Unsafe.As<char, byte>( | |
ref Unsafe.Add(ref outputStart, (IntPtr)(offset + Vector128<ushort>.Count))), | |
Sse2.UnpackHigh(asciiVector, zeroVector)); | |
return; // All done | |
} | |
// The reset is exersise for the reader to improve | |
for (int i = 0; i < input.Length; i++) | |
{ | |
Unsafe.WriteUnaligned(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref outputStart, (IntPtr)i)), (char)input[i]); | |
} | |
} | |
} |
Author
benaadams
commented
Feb 11, 2020
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment