Use PowerOfTwoToHighByte for FindFirstEqualByte

This commit is contained in:
Ben Adams 2016-10-03 07:58:24 +01:00
parent 7bd027cb6b
commit 4f7977d440
2 changed files with 31 additions and 74 deletions

View File

@ -4,12 +4,14 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
{ {
public struct MemoryPoolIterator public struct MemoryPoolIterator
{ {
private static readonly ulong _powerOfTwoToHighByte = PowerOfTwoToHighByte();
private static readonly int _vectorSpan = Vector<byte>.Count; private static readonly int _vectorSpan = Vector<byte>.Count;
private MemoryPoolBlock _block; private MemoryPoolBlock _block;
@ -307,7 +309,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
_block = block; _block = block;
var firstEqualByteIndex = FindFirstEqualByte(ref byte0Equals); var firstEqualByteIndex = LocateFirstFoundByte(ref byte0Equals);
var vectorBytesScanned = firstEqualByteIndex + 1; var vectorBytesScanned = firstEqualByteIndex + 1;
if (bytesScanned + vectorBytesScanned > limit) if (bytesScanned + vectorBytesScanned > limit)
@ -413,7 +415,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
_block = block; _block = block;
var firstEqualByteIndex = FindFirstEqualByte(ref byte0Equals); var firstEqualByteIndex = LocateFirstFoundByte(ref byte0Equals);
if (_block == limit.Block && index + firstEqualByteIndex > limit.Index) if (_block == limit.Block && index + firstEqualByteIndex > limit.Index)
{ {
@ -512,11 +514,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
if (!byte0Equals.Equals(Vector<byte>.Zero)) if (!byte0Equals.Equals(Vector<byte>.Zero))
{ {
byte0Index = FindFirstEqualByte(ref byte0Equals); byte0Index = LocateFirstFoundByte(ref byte0Equals);
} }
if (!byte1Equals.Equals(Vector<byte>.Zero)) if (!byte1Equals.Equals(Vector<byte>.Zero))
{ {
byte1Index = FindFirstEqualByte(ref byte1Equals); byte1Index = LocateFirstFoundByte(ref byte1Equals);
} }
if (byte0Index == int.MaxValue && byte1Index == int.MaxValue) if (byte0Index == int.MaxValue && byte1Index == int.MaxValue)
@ -656,15 +658,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
if (!byte0Equals.Equals(Vector<byte>.Zero)) if (!byte0Equals.Equals(Vector<byte>.Zero))
{ {
byte0Index = FindFirstEqualByte(ref byte0Equals); byte0Index = LocateFirstFoundByte(ref byte0Equals);
} }
if (!byte1Equals.Equals(Vector<byte>.Zero)) if (!byte1Equals.Equals(Vector<byte>.Zero))
{ {
byte1Index = FindFirstEqualByte(ref byte1Equals); byte1Index = LocateFirstFoundByte(ref byte1Equals);
} }
if (!byte2Equals.Equals(Vector<byte>.Zero)) if (!byte2Equals.Equals(Vector<byte>.Zero))
{ {
byte2Index = FindFirstEqualByte(ref byte2Equals); byte2Index = LocateFirstFoundByte(ref byte2Equals);
} }
if (byte0Index == int.MaxValue && byte1Index == int.MaxValue && byte2Index == int.MaxValue) if (byte0Index == int.MaxValue && byte1Index == int.MaxValue && byte2Index == int.MaxValue)
@ -761,60 +763,30 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
} }
/// <summary> /// <summary>
/// Find first byte /// Locate the first of the found bytes
/// </summary> /// </summary>
/// <param name="byteEquals"></param > /// <param name="byteEquals"></param >
/// <returns>The first index of the result vector</returns> /// <returns>The first index of the result vector</returns>
/// <exception cref="InvalidOperationException">byteEquals = 0</exception> // Force inlining (64 IL bytes, 91 bytes asm) Issue: https://github.com/dotnet/coreclr/issues/7386
internal static int FindFirstEqualByte(ref Vector<byte> byteEquals) [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static int LocateFirstFoundByte(ref Vector<byte> byteEquals)
{ {
if (!BitConverter.IsLittleEndian) return FindFirstEqualByteSlow(ref byteEquals);
// Quasi-tree search
var vector64 = Vector.AsVectorInt64(byteEquals); var vector64 = Vector.AsVectorInt64(byteEquals);
for (var i = 0; i < Vector<long>.Count; i++) var i = 0;
long longValue = 0;
for (; i < Vector<long>.Count; i++)
{ {
var longValue = vector64[i]; longValue = vector64[i];
if (longValue == 0) continue; if (longValue == 0) continue;
break;
return (i << 3) +
((longValue & 0x00000000ffffffff) > 0
? (longValue & 0x000000000000ffff) > 0
? (longValue & 0x00000000000000ff) > 0 ? 0 : 1
: (longValue & 0x0000000000ff0000) > 0 ? 2 : 3
: (longValue & 0x0000ffff00000000) > 0
? (longValue & 0x000000ff00000000) > 0 ? 4 : 5
: (longValue & 0x00ff000000000000) > 0 ? 6 : 7);
} }
throw new InvalidOperationException();
}
// Internal for testing // Flag least significant power of two bit
internal static int FindFirstEqualByteSlow(ref Vector<byte> byteEquals) var powerOfTwoFlag = (ulong)(longValue & -longValue);
{ // Shift all powers of two into the high byte and extract
// Quasi-tree search var foundByteIndex = (int)((powerOfTwoFlag * _powerOfTwoToHighByte) >> 61);
var vector64 = Vector.AsVectorInt64(byteEquals); // Single LEA instruction with jitted const (using function result)
for (var i = 0; i < Vector<long>.Count; i++) return i * 8 + foundByteIndex;
{
var longValue = vector64[i];
if (longValue == 0) continue;
var shift = i << 1;
var offset = shift << 2;
var vector32 = Vector.AsVectorInt32(byteEquals);
if (vector32[shift] != 0)
{
if (byteEquals[offset] != 0) return offset;
if (byteEquals[offset + 1] != 0) return offset + 1;
if (byteEquals[offset + 2] != 0) return offset + 2;
return offset + 3;
}
if (byteEquals[offset + 4] != 0) return offset + 4;
if (byteEquals[offset + 5] != 0) return offset + 5;
if (byteEquals[offset + 6] != 0) return offset + 6;
return offset + 7;
}
throw new InvalidOperationException();
} }
/// <summary> /// <summary>
@ -1059,5 +1031,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
_block = block; _block = block;
_index = blockIndex; _index = blockIndex;
} }
private static ulong PowerOfTwoToHighByte()
{
return BitConverter.IsLittleEndian ? 0x20406080A0C0E0ul : 0xE0C0A080604020ul;
}
} }
} }

View File

@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
for (int i = 0; i < Vector<byte>.Count; i++) for (int i = 0; i < Vector<byte>.Count; i++)
{ {
Vector<byte> vector = new Vector<byte>(bytes); Vector<byte> vector = new Vector<byte>(bytes);
Assert.Equal(i, MemoryPoolIterator.FindFirstEqualByte(ref vector)); Assert.Equal(i, MemoryPoolIterator.LocateFirstFoundByte(ref vector));
bytes[i] = 0; bytes[i] = 0;
} }
@ -40,27 +40,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
{ {
bytes[i] = 1; bytes[i] = 1;
Vector<byte> vector = new Vector<byte>(bytes); Vector<byte> vector = new Vector<byte>(bytes);
Assert.Equal(i, MemoryPoolIterator.FindFirstEqualByte(ref vector)); Assert.Equal(i, MemoryPoolIterator.LocateFirstFoundByte(ref vector));
bytes[i] = 0;
}
}
[Fact]
public void TestFindFirstEqualByteSlow()
{
var bytes = Enumerable.Repeat<byte>(0xff, Vector<byte>.Count).ToArray();
for (int i = 0; i < Vector<byte>.Count; i++)
{
Vector<byte> vector = new Vector<byte>(bytes);
Assert.Equal(i, MemoryPoolIterator.FindFirstEqualByteSlow(ref vector));
bytes[i] = 0;
}
for (int i = 0; i < Vector<byte>.Count; i++)
{
bytes[i] = 1;
Vector<byte> vector = new Vector<byte>(bytes);
Assert.Equal(i, MemoryPoolIterator.FindFirstEqualByteSlow(ref vector));
bytes[i] = 0; bytes[i] = 0;
} }
} }