From 4ab6c505f723f647e505185482aa62d50c7bbfa5 Mon Sep 17 00:00:00 2001 From: Jacob Crawley Date: Mon, 24 Mar 2025 16:34:04 +0000 Subject: [PATCH 1/7] SVE microbenchmarks with string operations Microbenchmarking tests on string operations (len, indexof, cmp) to compare the runtimes across scalar, Vector128 and SVE implementations. --- src/benchmarks/micro/sve/StrCmp.cs | 197 +++++++++++++++++++++++++ src/benchmarks/micro/sve/StrIndexOf.cs | 166 +++++++++++++++++++++ src/benchmarks/micro/sve/StrLen.cs | 142 ++++++++++++++++++ 3 files changed, 505 insertions(+) create mode 100644 src/benchmarks/micro/sve/StrCmp.cs create mode 100644 src/benchmarks/micro/sve/StrIndexOf.cs create mode 100644 src/benchmarks/micro/sve/StrLen.cs diff --git a/src/benchmarks/micro/sve/StrCmp.cs b/src/benchmarks/micro/sve/StrCmp.cs new file mode 100644 index 00000000000..d4adb89281d --- /dev/null +++ b/src/benchmarks/micro/sve/StrCmp.cs @@ -0,0 +1,197 @@ +using System; +using System.Numerics; +using System.Linq; +using System.Diagnostics; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.Arm; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Extensions; +using MicroBenchmarks; + +namespace SveBenchmarks +{ + [BenchmarkCategory(Categories.Runtime)] + public class StrCmp + { + [Params(15, 127, 527, 10015)] + public int Size; + + [Params("ChangeArr1", "ChangeArr2", "Zero")] + public string Scenario; + + private byte[] _arr1, _arr2; + + [GlobalSetup] + public virtual void Setup() + { + _arr1 = ValuesGenerator.Array(Size); + _arr2 = ValuesGenerator.Array(Size); + + switch (Scenario) + { + case "ChangeArr1": + // modify arr1 value in the middle of the array + _arr1[Size / 2] = ValuesGenerator.GetNonDefaultValue(); + break; + + case "ChangeArr2": + // modify arr2 value near the end of the array + _arr2[Size - 1] = ValuesGenerator.GetNonDefaultValue(); + break; + + case "Zero": + // keep both arrays equal + break; + } + } + + [Benchmark] + public int ScalarStrCmp() + { + if (_arr1.Length == _arr2.Length) + { + for (int i = 0; i < Size; i++) + { + if (_arr1[i] != _arr2[i] ) + return _arr1[i] - _arr2[i]; + } + + return 0; + } + + Debug.Assert(false, "Different array lengths are not expected"); + return 0; + } + + [Benchmark] + public int Vector128StrCmp() + { + int incr = Vector128.Count; + int i = 0; + + if (_arr1.Length == _arr2.Length) + { + for (; i <= Size - incr; i += incr) + { + Vector128 arr1_vals = Vector128.LoadUnsafe(ref _arr1[i]); + Vector128 arr2_vals = Vector128.LoadUnsafe(ref _arr2[i]); + + bool allEqual = Vector128.EqualsAll(arr1_vals, arr2_vals); + + if (!allEqual) + { + break; + } + } + + // fall back to scalar for remaining values + for (; i < Size; i++) + { + if (_arr1[i] != _arr2[i] ) + return _arr1[i] - _arr2[i]; + } + return 0; + } + + Debug.Assert(false, "Different array lengths are not expected"); + return 0; + } + + + [Benchmark] + public unsafe long SveStrCmp() + { + int i = 0; + int elemsInVector = (int)Sve.Count8BitElements(); + + Vector ptrue = Sve.CreateTrueMaskByte(); + Vector pLoop = (Vector)Sve.CreateWhileLessThanMask8Bit(i, Size); + Vector cmp = Vector.Zero; + Vector arr1_data, arr2_data; + + if (_arr1.Length == _arr2.Length) + { + fixed (byte* arr1_ptr = _arr1, arr2_ptr = _arr2) + { + while (Sve.TestFirstTrue(ptrue, pLoop)) + { + arr1_data = Sve.LoadVector(pLoop, arr1_ptr + i); + arr2_data = Sve.LoadVector(pLoop, arr2_ptr + i); + + // stop if any values arent equal + cmp = Sve.CompareNotEqualTo(arr1_data, arr2_data); + + if (Sve.TestAnyTrue(ptrue, cmp)) + break; + + i += elemsInVector; + + pLoop = (Vector)Sve.CreateWhileLessThanMask8Bit(i, Size); + } + + // create a bitmask to find position of changed value + int mask = 0; + for (int j = 0; j < elemsInVector; j++) + { + // set bits in lanes with non zero elements + if (cmp.GetElement(j) != 0) + mask |= (1 << j); + } + + int zeroCount = BitOperations.TrailingZeroCount(mask); + + if (zeroCount < elemsInVector) + return _arr1[i+zeroCount] - _arr2[i+zeroCount]; + + return 0; + } + } + + Debug.Assert(false, "Different array lengths are not expected"); + return 0; + } + + [Benchmark] + public unsafe long SveStrCmpTail() + { + Vector ptrue = Sve.CreateTrueMaskByte(); + Vector cmp; + Vector arr1_data, arr2_data; + + int i = 0; + int elemsInVector = (int)Sve.Count8BitElements(); + + if (_arr1.Length == _arr2.Length) + { + fixed (byte* arr1_ptr = _arr1, arr2_ptr = _arr2) + { + for (; i <= Size - elemsInVector; i += elemsInVector) + { + arr1_data = Sve.LoadVector(ptrue, arr1_ptr + i); + arr2_data = Sve.LoadVector(ptrue, arr2_ptr + i); + + cmp = Sve.CompareNotEqualTo(arr1_data, arr2_data); + + byte allEqual = (byte)Sve.AddAcross(cmp).ToScalar(); + + if (allEqual > 0) + { + break; + } + } + + for (; i < Size; i++) + { + if (_arr1[i] != _arr2[i] ) + return _arr1[i] - _arr2[i]; + } + + return 0; + } + } + + Debug.Assert(false, "Different array lengths are not expected"); + return 0; + } + } +} \ No newline at end of file diff --git a/src/benchmarks/micro/sve/StrIndexOf.cs b/src/benchmarks/micro/sve/StrIndexOf.cs new file mode 100644 index 00000000000..826966944b2 --- /dev/null +++ b/src/benchmarks/micro/sve/StrIndexOf.cs @@ -0,0 +1,166 @@ +using System; +using System.Numerics; +using System.Linq; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.Arm; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Extensions; +using MicroBenchmarks; + +namespace SveBenchmarks +{ + [BenchmarkCategory(Categories.Runtime)] + public class StrIndexOf + { + [Params(15, 127, 527, 10015)] + public int Size; + + private char[] _array; + private char _searchValue; + + [GlobalSetup] + public virtual void Setup() + { + _array = Enumerable.Range(1, Size) + .Select(i => (char) i) + .ToArray(); + _searchValue = _array[Size / 2]; + } + + [Benchmark] + public int ScalarIndexOf() + { + for (int i = 0; i < _array.Length; i++) + { + if (_array[i] == _searchValue) + { + return i; + } + } + return -1; + } + + [Benchmark] + public unsafe int Vector128IndexOf() + { + int incr = Vector128.Count; + int i = 0; + + + fixed (char* arr_ptr = _array) + { + Vector128 target = Vector128.Create((ushort)_searchValue); + + for (; i <= Size - incr; i += incr) + { + Vector128 vals = Vector128.Load(((ushort*)arr_ptr) + i); + + // Compare each vector value with the target + Vector128 cmp = Vector128.Equals(vals, target); + + ushort cmpSum = Vector128.Sum(cmp); + + if (cmpSum > 0) + { + // find index of matching item + for (int j = 0; j < incr; j++) + { + if (cmp.GetElement(j) == ushort.MaxValue) + { + return i + j; + } + } + } + } + + // Search the remaining values + for (; i < Size; i++) + { + if (_array[i] == _searchValue) + return i; + } + + return -1; + } + } + + [Benchmark] + public unsafe int SveIndexOf() + { + int i = 0; + + fixed (char* arr_ptr = _array) + { + Vector target = new Vector((ushort)_searchValue); + var pLoop = (Vector)Sve.CreateWhileLessThanMask16Bit(i, Size); + + for (; Sve.TestFirstTrue(Sve.CreateTrueMaskUInt16(), pLoop); i += (int)Sve.Count16BitElements()) + { + Vector vals = Sve.LoadVector(pLoop, ((ushort*)arr_ptr) + i); + Vector cmpVec = Sve.CompareEqual(vals, target); + + ushort cmpSum = (ushort)Sve.AddAcross(cmpVec).ToScalar(); + + if (cmpSum > 0) + { + // find index of matching item + for (int j = 0; j < Vector.Count; j++) + { + if (cmpVec.GetElement(j) == 1) + { + return i + j; + } + } + } + + pLoop = (Vector)Sve.CreateWhileLessThanMask16Bit(i, Size); + } + } + + return -1; + + } + + [Benchmark] + public unsafe int SveIndexOfTail() + { + int i = 0; + + fixed (char* arr_ptr = _array) + { + Vector target = new Vector((ushort)_searchValue); + var pLoop = (Vector)Sve.CreateTrueMaskInt16(); + + + for (; (Size - i) > (int)Sve.Count16BitElements(); i += (int)Sve.Count16BitElements()) + { + Vector vals = Sve.LoadVector(pLoop, ((ushort*)arr_ptr) + i); + Vector cmpVec = Sve.CompareEqual(vals, target); + + ushort cmpSum = (ushort)Sve.AddAcross(cmpVec).ToScalar(); + + if (cmpSum > 0) + { + // find index of matching item + for (int j = 0; j < Vector.Count; j++) + { + if (cmpVec.GetElement(j) == 1) + { + return i + j; + } + } + } + } + + for (; i < Size; i++) + { + if (_array[i] == _searchValue) + return i; + } + + return -1; + } + } + + } +} \ No newline at end of file diff --git a/src/benchmarks/micro/sve/StrLen.cs b/src/benchmarks/micro/sve/StrLen.cs new file mode 100644 index 00000000000..6bf13a63bcf --- /dev/null +++ b/src/benchmarks/micro/sve/StrLen.cs @@ -0,0 +1,142 @@ +using System; +using System.Numerics; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.Arm; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Extensions; +using MicroBenchmarks; + +namespace SveBenchmarks +{ + [BenchmarkCategory(Categories.Runtime)] + public class StrLen + { + [Params(15, 127, 527, 10015)] + public int Size; + + private byte[] _array; + private ulong _length; + + [GlobalSetup] + public virtual void Setup() + { + _array = ValuesGenerator.Array(Size + 1); + _length = 0; + + var random = new Random(); + for (int i=0; i < _array.Length; i++) + { + // Replaces any zero elements with a random value + if (_array[i] == 0) + { + _array[i] = (byte)random.Next(1, byte.MaxValue); + } + } + + _array[Size] = 0; // add zero to the end to simulate a terminated string + + } + + [Benchmark] + public unsafe ulong ScalarStrLen() + { + fixed (byte* arr_ptr = _array) + { + if (arr_ptr == null) + return 0; + + byte* ptr = arr_ptr; + + while (*ptr != 0) + { + _length++; + ptr++; + } + } + + return _length; + } + + [Benchmark] + public unsafe ulong Vector128StrLen() + { + Vector128 data = Vector128.Zero; + ulong cmp = 0; + ulong i = 0; + ulong alignOffset = 0; + + fixed(byte* ptr = _array) + { + byte* arr_ptr = ptr; + + // Check for a zero in first 16 bytes + for (i = 0; i < 16; i++) + { + if(arr_ptr[i] == 0) + { + return i; + } + } + + // look for a zero in the next 16 byte block + while (cmp == 0) + { + data = Vector128.Load(arr_ptr + i); + Vector128 min = AdvSimd.Arm64.MinPairwise(data, data); + Vector64 cmpVec = Vector64.Equals(min.GetLower(), Vector64.Zero); + + cmp = cmpVec.AsUInt64().ToScalar(); + + i = i + (ulong)(sizeof(Vector128) / sizeof(byte)); + } + + // once a zero is found, go back one 16-byte block and find location of the zero + i = i - (ulong)(sizeof(Vector128) / sizeof(byte)); + + Vector128 cmpVecLoc = AdvSimd.CompareEqual(data, Vector128.Zero); + + Vector64 shifted = AdvSimd.ShiftRightLogicalNarrowingLower( + cmpVecLoc.AsUInt16(), + 4 + ); + + ulong syncd = shifted.AsUInt64().ToScalar(); + int count = BitOperations.TrailingZeroCount(syncd); + + return i + (ulong)(count / 4) + alignOffset; + } + } + + [Benchmark] + public unsafe ulong SveStrLen() + { + Vector ptrue = Sve.CreateTrueMaskByte(); + Vector cmp, data; + + ulong i = 0; + ulong elemsInVector = Sve.Count8BitElements(); + + Vector pLoop = (Vector)Sve.CreateWhileLessThanMask8Bit((int)i, Size); + + fixed (byte* arr_ptr = _array) + { + while (true) + { + data = Sve.LoadVector(pLoop, arr_ptr + i); + cmp = Sve.CompareEqual(data, Vector.Zero); + + if (Sve.TestAnyTrue(ptrue, cmp)) + break; + else + { + i += elemsInVector; + pLoop = (Vector)Sve.CreateWhileLessThanMask8Bit((int)i, Size); + } + } + + i += Sve.GetActiveElementCount(pLoop, data); + return i; + } + } + } +} \ No newline at end of file From 7ca4047dd697b28a3b3a3da4eb8918b3b79431de Mon Sep 17 00:00:00 2001 From: Jacob Crawley Date: Thu, 1 May 2025 15:25:44 +0100 Subject: [PATCH 2/7] Filtering string benchmarks to only run on aarch64 --- src/benchmarks/micro/sve/StrCmp.cs | 1 + src/benchmarks/micro/sve/StrIndexOf.cs | 1 + src/benchmarks/micro/sve/StrLen.cs | 1 + 3 files changed, 3 insertions(+) diff --git a/src/benchmarks/micro/sve/StrCmp.cs b/src/benchmarks/micro/sve/StrCmp.cs index d4adb89281d..506046fbb29 100644 --- a/src/benchmarks/micro/sve/StrCmp.cs +++ b/src/benchmarks/micro/sve/StrCmp.cs @@ -11,6 +11,7 @@ namespace SveBenchmarks { [BenchmarkCategory(Categories.Runtime)] + [OperatingSystemsArchitectureFilter(allowed: true, System.Runtime.InteropServices.Architecture.Arm64)] public class StrCmp { [Params(15, 127, 527, 10015)] diff --git a/src/benchmarks/micro/sve/StrIndexOf.cs b/src/benchmarks/micro/sve/StrIndexOf.cs index 826966944b2..c2f6239c724 100644 --- a/src/benchmarks/micro/sve/StrIndexOf.cs +++ b/src/benchmarks/micro/sve/StrIndexOf.cs @@ -10,6 +10,7 @@ namespace SveBenchmarks { [BenchmarkCategory(Categories.Runtime)] + [OperatingSystemsArchitectureFilter(allowed: true, System.Runtime.InteropServices.Architecture.Arm64)] public class StrIndexOf { [Params(15, 127, 527, 10015)] diff --git a/src/benchmarks/micro/sve/StrLen.cs b/src/benchmarks/micro/sve/StrLen.cs index 6bf13a63bcf..4bdaa3d8fd6 100644 --- a/src/benchmarks/micro/sve/StrLen.cs +++ b/src/benchmarks/micro/sve/StrLen.cs @@ -9,6 +9,7 @@ namespace SveBenchmarks { [BenchmarkCategory(Categories.Runtime)] + [OperatingSystemsArchitectureFilter(allowed: true, System.Runtime.InteropServices.Architecture.Arm64)] public class StrLen { [Params(15, 127, 527, 10015)] From fd85f63e99c383ec9ca9c076848ef8b89804394f Mon Sep 17 00:00:00 2001 From: Jacob Crawley Date: Thu, 1 May 2025 15:34:26 +0100 Subject: [PATCH 3/7] Add check for SVE support in string benchmarks Include a 'Sve.IsSupported' check on each SVE benchmark so they still run on machines that dont have support for SVE. --- src/benchmarks/micro/sve/StrCmp.cs | 121 +++++++++++++------------ src/benchmarks/micro/sve/StrIndexOf.cs | 87 ++++++++++-------- src/benchmarks/micro/sve/StrLen.cs | 42 +++++---- 3 files changed, 135 insertions(+), 115 deletions(-) diff --git a/src/benchmarks/micro/sve/StrCmp.cs b/src/benchmarks/micro/sve/StrCmp.cs index 506046fbb29..987fe34259b 100644 --- a/src/benchmarks/micro/sve/StrCmp.cs +++ b/src/benchmarks/micro/sve/StrCmp.cs @@ -102,96 +102,105 @@ public int Vector128StrCmp() [Benchmark] public unsafe long SveStrCmp() { - int i = 0; - int elemsInVector = (int)Sve.Count8BitElements(); + if (Sve.IsSupported) + { + int i = 0; + int elemsInVector = (int)Sve.Count8BitElements(); - Vector ptrue = Sve.CreateTrueMaskByte(); - Vector pLoop = (Vector)Sve.CreateWhileLessThanMask8Bit(i, Size); - Vector cmp = Vector.Zero; - Vector arr1_data, arr2_data; + Vector ptrue = Sve.CreateTrueMaskByte(); + Vector pLoop = (Vector)Sve.CreateWhileLessThanMask8Bit(i, Size); + Vector cmp = Vector.Zero; + Vector arr1_data, arr2_data; - if (_arr1.Length == _arr2.Length) - { - fixed (byte* arr1_ptr = _arr1, arr2_ptr = _arr2) + if (_arr1.Length == _arr2.Length) { - while (Sve.TestFirstTrue(ptrue, pLoop)) + fixed (byte* arr1_ptr = _arr1, arr2_ptr = _arr2) { - arr1_data = Sve.LoadVector(pLoop, arr1_ptr + i); - arr2_data = Sve.LoadVector(pLoop, arr2_ptr + i); + while (Sve.TestFirstTrue(ptrue, pLoop)) + { + arr1_data = Sve.LoadVector(pLoop, arr1_ptr + i); + arr2_data = Sve.LoadVector(pLoop, arr2_ptr + i); - // stop if any values arent equal - cmp = Sve.CompareNotEqualTo(arr1_data, arr2_data); + // stop if any values arent equal + cmp = Sve.CompareNotEqualTo(arr1_data, arr2_data); - if (Sve.TestAnyTrue(ptrue, cmp)) - break; + if (Sve.TestAnyTrue(ptrue, cmp)) + break; - i += elemsInVector; + i += elemsInVector; - pLoop = (Vector)Sve.CreateWhileLessThanMask8Bit(i, Size); - } + pLoop = (Vector)Sve.CreateWhileLessThanMask8Bit(i, Size); + } - // create a bitmask to find position of changed value - int mask = 0; - for (int j = 0; j < elemsInVector; j++) - { - // set bits in lanes with non zero elements - if (cmp.GetElement(j) != 0) - mask |= (1 << j); - } + // create a bitmask to find position of changed value + int mask = 0; + for (int j = 0; j < elemsInVector; j++) + { + // set bits in lanes with non zero elements + if (cmp.GetElement(j) != 0) + mask |= (1 << j); + } - int zeroCount = BitOperations.TrailingZeroCount(mask); + int zeroCount = BitOperations.TrailingZeroCount(mask); - if (zeroCount < elemsInVector) - return _arr1[i+zeroCount] - _arr2[i+zeroCount]; + if (zeroCount < elemsInVector) + return _arr1[i+zeroCount] - _arr2[i+zeroCount]; - return 0; + return 0; + } } - } - Debug.Assert(false, "Different array lengths are not expected"); + Debug.Assert(false, "Different array lengths are not expected"); + return 0; + } return 0; } [Benchmark] public unsafe long SveStrCmpTail() { - Vector ptrue = Sve.CreateTrueMaskByte(); - Vector cmp; - Vector arr1_data, arr2_data; + if (Sve.IsSupported) + { + Vector ptrue = Sve.CreateTrueMaskByte(); + Vector cmp; + Vector arr1_data, arr2_data; - int i = 0; - int elemsInVector = (int)Sve.Count8BitElements(); + int i = 0; + int elemsInVector = (int)Sve.Count8BitElements(); - if (_arr1.Length == _arr2.Length) - { - fixed (byte* arr1_ptr = _arr1, arr2_ptr = _arr2) + if (_arr1.Length == _arr2.Length) { - for (; i <= Size - elemsInVector; i += elemsInVector) + fixed (byte* arr1_ptr = _arr1, arr2_ptr = _arr2) { - arr1_data = Sve.LoadVector(ptrue, arr1_ptr + i); - arr2_data = Sve.LoadVector(ptrue, arr2_ptr + i); + for (; i <= Size - elemsInVector; i += elemsInVector) + { + arr1_data = Sve.LoadVector(ptrue, arr1_ptr + i); + arr2_data = Sve.LoadVector(ptrue, arr2_ptr + i); + + cmp = Sve.CompareNotEqualTo(arr1_data, arr2_data); - cmp = Sve.CompareNotEqualTo(arr1_data, arr2_data); + byte allEqual = (byte)Sve.AddAcross(cmp).ToScalar(); - byte allEqual = (byte)Sve.AddAcross(cmp).ToScalar(); + if (allEqual > 0) + { + break; + } + } - if (allEqual > 0) + for (; i < Size; i++) { - break; + if (_arr1[i] != _arr2[i] ) + return _arr1[i] - _arr2[i]; } - } - for (; i < Size; i++) - { - if (_arr1[i] != _arr2[i] ) - return _arr1[i] - _arr2[i]; + return 0; } - - return 0; } + + Debug.Assert(false, "Different array lengths are not expected"); + return 0; } - Debug.Assert(false, "Different array lengths are not expected"); return 0; } } diff --git a/src/benchmarks/micro/sve/StrIndexOf.cs b/src/benchmarks/micro/sve/StrIndexOf.cs index c2f6239c724..7b0480fab3b 100644 --- a/src/benchmarks/micro/sve/StrIndexOf.cs +++ b/src/benchmarks/micro/sve/StrIndexOf.cs @@ -88,79 +88,86 @@ public unsafe int Vector128IndexOf() [Benchmark] public unsafe int SveIndexOf() { - int i = 0; - - fixed (char* arr_ptr = _array) + if (Sve.IsSupported) { - Vector target = new Vector((ushort)_searchValue); - var pLoop = (Vector)Sve.CreateWhileLessThanMask16Bit(i, Size); + int i = 0; - for (; Sve.TestFirstTrue(Sve.CreateTrueMaskUInt16(), pLoop); i += (int)Sve.Count16BitElements()) + fixed (char* arr_ptr = _array) { - Vector vals = Sve.LoadVector(pLoop, ((ushort*)arr_ptr) + i); - Vector cmpVec = Sve.CompareEqual(vals, target); + Vector target = new Vector((ushort)_searchValue); + var pLoop = (Vector)Sve.CreateWhileLessThanMask16Bit(i, Size); - ushort cmpSum = (ushort)Sve.AddAcross(cmpVec).ToScalar(); - - if (cmpSum > 0) + for (; Sve.TestFirstTrue(Sve.CreateTrueMaskUInt16(), pLoop); i += (int)Sve.Count16BitElements()) { - // find index of matching item - for (int j = 0; j < Vector.Count; j++) + Vector vals = Sve.LoadVector(pLoop, ((ushort*)arr_ptr) + i); + Vector cmpVec = Sve.CompareEqual(vals, target); + + ushort cmpSum = (ushort)Sve.AddAcross(cmpVec).ToScalar(); + + if (cmpSum > 0) { - if (cmpVec.GetElement(j) == 1) + // find index of matching item + for (int j = 0; j < Vector.Count; j++) { - return i + j; + if (cmpVec.GetElement(j) == 1) + { + return i + j; + } } } - } - pLoop = (Vector)Sve.CreateWhileLessThanMask16Bit(i, Size); + pLoop = (Vector)Sve.CreateWhileLessThanMask16Bit(i, Size); + } } } return -1; - } [Benchmark] public unsafe int SveIndexOfTail() { - int i = 0; - - fixed (char* arr_ptr = _array) + if (Sve.IsSupported) { - Vector target = new Vector((ushort)_searchValue); - var pLoop = (Vector)Sve.CreateTrueMaskInt16(); + int i = 0; - - for (; (Size - i) > (int)Sve.Count16BitElements(); i += (int)Sve.Count16BitElements()) + fixed (char* arr_ptr = _array) { - Vector vals = Sve.LoadVector(pLoop, ((ushort*)arr_ptr) + i); - Vector cmpVec = Sve.CompareEqual(vals, target); + Vector target = new Vector((ushort)_searchValue); + var pLoop = (Vector)Sve.CreateTrueMaskInt16(); - ushort cmpSum = (ushort)Sve.AddAcross(cmpVec).ToScalar(); - if (cmpSum > 0) + for (; (Size - i) > (int)Sve.Count16BitElements(); i += (int)Sve.Count16BitElements()) { - // find index of matching item - for (int j = 0; j < Vector.Count; j++) + Vector vals = Sve.LoadVector(pLoop, ((ushort*)arr_ptr) + i); + Vector cmpVec = Sve.CompareEqual(vals, target); + + ushort cmpSum = (ushort)Sve.AddAcross(cmpVec).ToScalar(); + + if (cmpSum > 0) { - if (cmpVec.GetElement(j) == 1) + // find index of matching item + for (int j = 0; j < Vector.Count; j++) { - return i + j; + if (cmpVec.GetElement(j) == 1) + { + return i + j; + } } } } - } - for (; i < Size; i++) - { - if (_array[i] == _searchValue) - return i; - } + for (; i < Size; i++) + { + if (_array[i] == _searchValue) + return i; + } - return -1; + return -1; + } } + + return -1; } } diff --git a/src/benchmarks/micro/sve/StrLen.cs b/src/benchmarks/micro/sve/StrLen.cs index 4bdaa3d8fd6..f4171c50adc 100644 --- a/src/benchmarks/micro/sve/StrLen.cs +++ b/src/benchmarks/micro/sve/StrLen.cs @@ -111,33 +111,37 @@ public unsafe ulong Vector128StrLen() [Benchmark] public unsafe ulong SveStrLen() { - Vector ptrue = Sve.CreateTrueMaskByte(); - Vector cmp, data; + if (Sve.IsSupported) + { + Vector ptrue = Sve.CreateTrueMaskByte(); + Vector cmp, data; - ulong i = 0; - ulong elemsInVector = Sve.Count8BitElements(); + ulong i = 0; + ulong elemsInVector = Sve.Count8BitElements(); - Vector pLoop = (Vector)Sve.CreateWhileLessThanMask8Bit((int)i, Size); + Vector pLoop = (Vector)Sve.CreateWhileLessThanMask8Bit((int)i, Size); - fixed (byte* arr_ptr = _array) - { - while (true) + fixed (byte* arr_ptr = _array) { - data = Sve.LoadVector(pLoop, arr_ptr + i); - cmp = Sve.CompareEqual(data, Vector.Zero); - - if (Sve.TestAnyTrue(ptrue, cmp)) - break; - else + while (true) { - i += elemsInVector; - pLoop = (Vector)Sve.CreateWhileLessThanMask8Bit((int)i, Size); + data = Sve.LoadVector(pLoop, arr_ptr + i); + cmp = Sve.CompareEqual(data, Vector.Zero); + + if (Sve.TestAnyTrue(ptrue, cmp)) + break; + else + { + i += elemsInVector; + pLoop = (Vector)Sve.CreateWhileLessThanMask8Bit((int)i, Size); + } } - } - i += Sve.GetActiveElementCount(pLoop, data); - return i; + i += Sve.GetActiveElementCount(pLoop, data); + return i; + } } + return 0; } } } \ No newline at end of file From aca921b33e34e3ff7dfa30e2e62e6def9576b131 Mon Sep 17 00:00:00 2001 From: Jacob Crawley Date: Tue, 6 May 2025 14:02:16 +0100 Subject: [PATCH 4/7] Add filter to benchmarks for SVE support Adds a custom filter to each benchmark class which means it can only be run if SVE is supported on the machine --- src/benchmarks/micro/sve/StrCmp.cs | 11 +++++++++++ src/benchmarks/micro/sve/StrIndexOf.cs | 11 +++++++++++ src/benchmarks/micro/sve/StrLen.cs | 11 +++++++++++ 3 files changed, 33 insertions(+) diff --git a/src/benchmarks/micro/sve/StrCmp.cs b/src/benchmarks/micro/sve/StrCmp.cs index 987fe34259b..ce48b1f38a9 100644 --- a/src/benchmarks/micro/sve/StrCmp.cs +++ b/src/benchmarks/micro/sve/StrCmp.cs @@ -6,14 +6,25 @@ using System.Runtime.Intrinsics.Arm; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Extensions; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Filters; using MicroBenchmarks; namespace SveBenchmarks { [BenchmarkCategory(Categories.Runtime)] [OperatingSystemsArchitectureFilter(allowed: true, System.Runtime.InteropServices.Architecture.Arm64)] + [Config(typeof(Config))] public class StrCmp { + private class Config : ManualConfig + { + public Config() + { + AddFilter(new SimpleFilter(_ => Sve.IsSupported)); + } + } + [Params(15, 127, 527, 10015)] public int Size; diff --git a/src/benchmarks/micro/sve/StrIndexOf.cs b/src/benchmarks/micro/sve/StrIndexOf.cs index 7b0480fab3b..4ff3c44dd09 100644 --- a/src/benchmarks/micro/sve/StrIndexOf.cs +++ b/src/benchmarks/micro/sve/StrIndexOf.cs @@ -5,14 +5,25 @@ using System.Runtime.Intrinsics.Arm; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Extensions; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Filters; using MicroBenchmarks; namespace SveBenchmarks { [BenchmarkCategory(Categories.Runtime)] [OperatingSystemsArchitectureFilter(allowed: true, System.Runtime.InteropServices.Architecture.Arm64)] + [Config(typeof(Config))] public class StrIndexOf { + private class Config : ManualConfig + { + public Config() + { + AddFilter(new SimpleFilter(_ => Sve.IsSupported)); + } + } + [Params(15, 127, 527, 10015)] public int Size; diff --git a/src/benchmarks/micro/sve/StrLen.cs b/src/benchmarks/micro/sve/StrLen.cs index f4171c50adc..a93d887328b 100644 --- a/src/benchmarks/micro/sve/StrLen.cs +++ b/src/benchmarks/micro/sve/StrLen.cs @@ -4,14 +4,25 @@ using System.Runtime.Intrinsics.Arm; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Extensions; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Filters; using MicroBenchmarks; namespace SveBenchmarks { [BenchmarkCategory(Categories.Runtime)] [OperatingSystemsArchitectureFilter(allowed: true, System.Runtime.InteropServices.Architecture.Arm64)] + [Config(typeof(Config))] public class StrLen { + private class Config : ManualConfig + { + public Config() + { + AddFilter(new SimpleFilter(_ => Sve.IsSupported)); + } + } + [Params(15, 127, 527, 10015)] public int Size; From ee149b582fe5e56a49dc527623e6663e179019cf Mon Sep 17 00:00:00 2001 From: Jacob Crawley Date: Tue, 6 May 2025 14:08:16 +0100 Subject: [PATCH 5/7] Naming changes to string benchmarks Addressing upstream comments to renmae 'Scenario' param in StrCmp to 'Modify', and all scalar methos names have been renamed to 'Scalar()'. --- src/benchmarks/micro/sve/StrCmp.cs | 20 ++++++++++---------- src/benchmarks/micro/sve/StrIndexOf.cs | 4 ++-- src/benchmarks/micro/sve/StrLen.cs | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/benchmarks/micro/sve/StrCmp.cs b/src/benchmarks/micro/sve/StrCmp.cs index ce48b1f38a9..85c02322980 100644 --- a/src/benchmarks/micro/sve/StrCmp.cs +++ b/src/benchmarks/micro/sve/StrCmp.cs @@ -28,8 +28,8 @@ public Config() [Params(15, 127, 527, 10015)] public int Size; - [Params("ChangeArr1", "ChangeArr2", "Zero")] - public string Scenario; + [Params("Middle", "End", "None")] + public string Modify; private byte[] _arr1, _arr2; @@ -39,26 +39,26 @@ public virtual void Setup() _arr1 = ValuesGenerator.Array(Size); _arr2 = ValuesGenerator.Array(Size); - switch (Scenario) + switch (Modify) { - case "ChangeArr1": + case "Middle": // modify arr1 value in the middle of the array - _arr1[Size / 2] = ValuesGenerator.GetNonDefaultValue(); + _arr1[Size / 2] += 1; break; - case "ChangeArr2": + case "End": // modify arr2 value near the end of the array - _arr2[Size - 1] = ValuesGenerator.GetNonDefaultValue(); + _arr2[Size - 1] += 1; break; - case "Zero": + case "None": // keep both arrays equal break; } } [Benchmark] - public int ScalarStrCmp() + public int Scalar() { if (_arr1.Length == _arr2.Length) { @@ -168,7 +168,7 @@ public unsafe long SveStrCmp() } [Benchmark] - public unsafe long SveStrCmpTail() + public unsafe long SveTail() { if (Sve.IsSupported) { diff --git a/src/benchmarks/micro/sve/StrIndexOf.cs b/src/benchmarks/micro/sve/StrIndexOf.cs index 4ff3c44dd09..83af214aec3 100644 --- a/src/benchmarks/micro/sve/StrIndexOf.cs +++ b/src/benchmarks/micro/sve/StrIndexOf.cs @@ -40,7 +40,7 @@ public virtual void Setup() } [Benchmark] - public int ScalarIndexOf() + public int Scalar() { for (int i = 0; i < _array.Length; i++) { @@ -136,7 +136,7 @@ public unsafe int SveIndexOf() } [Benchmark] - public unsafe int SveIndexOfTail() + public unsafe int SveTail() { if (Sve.IsSupported) { diff --git a/src/benchmarks/micro/sve/StrLen.cs b/src/benchmarks/micro/sve/StrLen.cs index a93d887328b..9c7172120a0 100644 --- a/src/benchmarks/micro/sve/StrLen.cs +++ b/src/benchmarks/micro/sve/StrLen.cs @@ -50,7 +50,7 @@ public virtual void Setup() } [Benchmark] - public unsafe ulong ScalarStrLen() + public unsafe ulong Scalar() { fixed (byte* arr_ptr = _array) { From 98f535e856f31dc17dd5b9b2115176161478ad1f Mon Sep 17 00:00:00 2001 From: Jacob Crawley Date: Fri, 16 May 2025 10:04:09 +0100 Subject: [PATCH 6/7] Supress SYSLIB5003 warning Change-Id: Ic765f4371c595e8f115b8bab1cd1d22a0ee6e672 --- src/benchmarks/micro/sve/StrCmp.cs | 14 +++++++++----- src/benchmarks/micro/sve/StrIndexOf.cs | 6 +++++- src/benchmarks/micro/sve/StrLen.cs | 12 ++++++++---- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/benchmarks/micro/sve/StrCmp.cs b/src/benchmarks/micro/sve/StrCmp.cs index 85c02322980..4e43c996e4a 100644 --- a/src/benchmarks/micro/sve/StrCmp.cs +++ b/src/benchmarks/micro/sve/StrCmp.cs @@ -1,3 +1,5 @@ +#pragma warning disable SYSLIB5003 + using System; using System.Numerics; using System.Linq; @@ -64,7 +66,7 @@ public int Scalar() { for (int i = 0; i < Size; i++) { - if (_arr1[i] != _arr2[i] ) + if (_arr1[i] != _arr2[i]) return _arr1[i] - _arr2[i]; } @@ -99,7 +101,7 @@ public int Vector128StrCmp() // fall back to scalar for remaining values for (; i < Size; i++) { - if (_arr1[i] != _arr2[i] ) + if (_arr1[i] != _arr2[i]) return _arr1[i] - _arr2[i]; } return 0; @@ -155,7 +157,7 @@ public unsafe long SveStrCmp() int zeroCount = BitOperations.TrailingZeroCount(mask); if (zeroCount < elemsInVector) - return _arr1[i+zeroCount] - _arr2[i+zeroCount]; + return _arr1[i + zeroCount] - _arr2[i + zeroCount]; return 0; } @@ -200,7 +202,7 @@ public unsafe long SveTail() for (; i < Size; i++) { - if (_arr1[i] != _arr2[i] ) + if (_arr1[i] != _arr2[i]) return _arr1[i] - _arr2[i]; } @@ -215,4 +217,6 @@ public unsafe long SveTail() return 0; } } -} \ No newline at end of file +} + +#pragma warning restore SYSLIB5003 \ No newline at end of file diff --git a/src/benchmarks/micro/sve/StrIndexOf.cs b/src/benchmarks/micro/sve/StrIndexOf.cs index 83af214aec3..214d90e6b4f 100644 --- a/src/benchmarks/micro/sve/StrIndexOf.cs +++ b/src/benchmarks/micro/sve/StrIndexOf.cs @@ -1,3 +1,5 @@ +#pragma warning disable SYSLIB5003 + using System; using System.Numerics; using System.Linq; @@ -182,4 +184,6 @@ public unsafe int SveTail() } } -} \ No newline at end of file +} + +#pragma warning restore SYSLIB5003 \ No newline at end of file diff --git a/src/benchmarks/micro/sve/StrLen.cs b/src/benchmarks/micro/sve/StrLen.cs index 9c7172120a0..246bc64d834 100644 --- a/src/benchmarks/micro/sve/StrLen.cs +++ b/src/benchmarks/micro/sve/StrLen.cs @@ -1,3 +1,5 @@ +#pragma warning disable SYSLIB5003 + using System; using System.Numerics; using System.Runtime.Intrinsics; @@ -36,7 +38,7 @@ public virtual void Setup() _length = 0; var random = new Random(); - for (int i=0; i < _array.Length; i++) + for (int i = 0; i < _array.Length; i++) { // Replaces any zero elements with a random value if (_array[i] == 0) @@ -77,14 +79,14 @@ public unsafe ulong Vector128StrLen() ulong i = 0; ulong alignOffset = 0; - fixed(byte* ptr = _array) + fixed (byte* ptr = _array) { byte* arr_ptr = ptr; // Check for a zero in first 16 bytes for (i = 0; i < 16; i++) { - if(arr_ptr[i] == 0) + if (arr_ptr[i] == 0) { return i; } @@ -155,4 +157,6 @@ public unsafe ulong SveStrLen() return 0; } } -} \ No newline at end of file +} + +#pragma warning restore SYSLIB5003 \ No newline at end of file From c77455420e218929981ec5703765e6e8b62a1e57 Mon Sep 17 00:00:00 2001 From: Jacob Crawley Date: Mon, 19 May 2025 14:58:24 +0100 Subject: [PATCH 7/7] Remove sve tests from unsupported versions of .NET (<9.0) Change-Id: Ide2294abe4e4d016dab6ad7da5e3cc61a896f850 --- src/benchmarks/micro/MicroBenchmarks.csproj | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/benchmarks/micro/MicroBenchmarks.csproj b/src/benchmarks/micro/MicroBenchmarks.csproj index 284923c6d7b..b0a5eed121b 100644 --- a/src/benchmarks/micro/MicroBenchmarks.csproj +++ b/src/benchmarks/micro/MicroBenchmarks.csproj @@ -237,6 +237,11 @@ + + + + +