From 13ba98ad6c2fc69ca6719fdc1a0f537ebb5f4fd5 Mon Sep 17 00:00:00 2001 From: Yat Long Poon Date: Mon, 6 Oct 2025 15:04:50 +0100 Subject: [PATCH 1/4] Add ScatterStore and GatherLoad to SVE microbenchmark --- src/benchmarks/micro/sve/GatherLoad.cs | 104 +++++++++++++++++++++++ src/benchmarks/micro/sve/ScatterStore.cs | 98 +++++++++++++++++++++ 2 files changed, 202 insertions(+) create mode 100644 src/benchmarks/micro/sve/GatherLoad.cs create mode 100644 src/benchmarks/micro/sve/ScatterStore.cs diff --git a/src/benchmarks/micro/sve/GatherLoad.cs b/src/benchmarks/micro/sve/GatherLoad.cs new file mode 100644 index 00000000000..858f42e81f0 --- /dev/null +++ b/src/benchmarks/micro/sve/GatherLoad.cs @@ -0,0 +1,104 @@ +using System; +using System.Diagnostics; +using System.Numerics; +using System.Runtime.Intrinsics; +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 GatherLoad + { + private class Config : ManualConfig + { + public Config() + { + AddFilter(new SimpleFilter(_ => Sve.IsSupported)); + } + } + + [Params(15, 127, 527, 10015)] + public int Size; + + private uint[] _objects; + private uint[] _indices; + private ulong _result; + + [GlobalSetup] + public virtual void Setup() + { + _objects = ValuesGenerator.Array(Size); + _indices = ValuesGenerator.Array(Size); + for (int i = 0; i < Size; i++) + { + _indices[i] %= (uint)Size; + } + } + + [GlobalCleanup] + public virtual void Verify() + { + ulong current = _result; + Setup(); + Scalar(); + ulong scalar = _result; + // Check that the result is the same as the scalar result. + Debug.Assert(current == scalar); + } + + // The following algorithms are adapted from the Arm simd-loops repository: + // https://gitlab.arm.com/architecture/simd-loops/-/blob/main/loops/loop_023.c + + [Benchmark] + public unsafe void Scalar() + { + fixed (uint* objects = _objects, indices = _indices) + { + ulong res = 0; + for (int i = 0; i < Size; i++) + { + res += objects[indices[i]]; + } + _result = res; + } + } + + [Benchmark] + public unsafe void SveGatherLoad() + { + fixed (uint* objects = _objects, indices = _indices) + { + int i = 0; + int cntw = (int)Sve.Count32BitElements(); + + Vector ones = Vector.One; + Vector resVec = Vector.Zero; + Vector pTrue = Sve.CreateTrueMaskUInt32(); + Vector pLoop = Sve.CreateWhileLessThanMask32Bit(0, Size); + while (Sve.TestFirstTrue(pTrue, pLoop)) + { + // Load indices + Vector idxVec = Sve.LoadVector(pLoop, indices + i); + // Gather elements from objects using indices. + Vector objVec = Sve.GatherVector(pLoop, objects, idxVec); + // Add results to resVec. + resVec = Sve.Add(resVec, objVec); + + i += cntw; + pLoop = Sve.CreateWhileLessThanMask32Bit(i, Size); + } + // Add up all elements in resVec. + ulong res = (ulong)Sve.AddAcross(resVec).ToScalar(); + _result = res; + } + } + + } +} diff --git a/src/benchmarks/micro/sve/ScatterStore.cs b/src/benchmarks/micro/sve/ScatterStore.cs new file mode 100644 index 00000000000..563bfb165b8 --- /dev/null +++ b/src/benchmarks/micro/sve/ScatterStore.cs @@ -0,0 +1,98 @@ +using System; +using System.Diagnostics; +using System.Numerics; +using System.Runtime.Intrinsics; +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 ScatterStore + { + private class Config : ManualConfig + { + public Config() + { + AddFilter(new SimpleFilter(_ => Sve.IsSupported)); + } + } + + [Params(15, 127, 527, 10015)] + public int Size; + + private uint[] _objects; + private uint[] _indices; + + [GlobalSetup] + public virtual void Setup() + { + _objects = new uint[Size]; + _indices = ValuesGenerator.Array(Size); + for (int i = 0; i < Size; i++) + { + _indices[i] %= (uint)Size; + } + } + + [GlobalCleanup] + public virtual void Verify() + { + uint[] current = (uint[])_objects.Clone(); + Setup(); + Scalar(); + uint[] scalar = (uint[])_objects.Clone(); + // Check that the result is the same as the scalar result. + for (int i = 0; i < current.Length; i++) + { + Debug.Assert(current[i] == scalar[i]); + } + } + + // The following algorithms are adapted from the Arm simd-loops repository: + // https://gitlab.arm.com/architecture/simd-loops/-/blob/main/loops/loop_019.c + + [Benchmark] + public unsafe void Scalar() + { + fixed (uint* objects = _objects, indices = _indices) + { + for (int i = 0; i < Size; i++) + { + objects[indices[i]] = 1; + } + } + } + + [Benchmark] + public unsafe void SveScatterStore() + { + fixed (uint* objects = _objects, indices = _indices) + { + int i = 0; + int cntw = (int)Sve.Count32BitElements(); + + Vector ones = Vector.One; + Vector pTrue = Sve.CreateTrueMaskUInt32(); + Vector pLoop = Sve.CreateWhileLessThanMask32Bit(0, Size); + while (Sve.TestFirstTrue(pTrue, pLoop)) + { + Vector idxVec = Sve.LoadVector(pLoop, indices + i); + + // Set the indices within objects with ones. + Sve.Scatter(pLoop, objects, idxVec, ones); + + i += cntw; + pLoop = Sve.CreateWhileLessThanMask32Bit(i, Size); + } + } + } + + } +} From be8aca8619304cece3e5426d8239fb83e4fb6c5d Mon Sep 17 00:00:00 2001 From: Yat Long Poon Date: Fri, 13 Mar 2026 12:34:27 +0000 Subject: [PATCH 2/4] Add SVE Category --- src/benchmarks/micro/sve/GatherLoad.cs | 2 +- src/benchmarks/micro/sve/ScatterStore.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/benchmarks/micro/sve/GatherLoad.cs b/src/benchmarks/micro/sve/GatherLoad.cs index 858f42e81f0..3c0e6dd5a26 100644 --- a/src/benchmarks/micro/sve/GatherLoad.cs +++ b/src/benchmarks/micro/sve/GatherLoad.cs @@ -11,7 +11,7 @@ namespace SveBenchmarks { - [BenchmarkCategory(Categories.Runtime)] + [BenchmarkCategory(Categories.Sve)] [OperatingSystemsArchitectureFilter(allowed: true, System.Runtime.InteropServices.Architecture.Arm64)] [Config(typeof(Config))] public class GatherLoad diff --git a/src/benchmarks/micro/sve/ScatterStore.cs b/src/benchmarks/micro/sve/ScatterStore.cs index 563bfb165b8..e5ec7fa651f 100644 --- a/src/benchmarks/micro/sve/ScatterStore.cs +++ b/src/benchmarks/micro/sve/ScatterStore.cs @@ -11,7 +11,7 @@ namespace SveBenchmarks { - [BenchmarkCategory(Categories.Runtime)] + [BenchmarkCategory(Categories.Sve)] [OperatingSystemsArchitectureFilter(allowed: true, System.Runtime.InteropServices.Architecture.Arm64)] [Config(typeof(Config))] public class ScatterStore From 84bac1b98983ccd17d7127c989261997366d56b8 Mon Sep 17 00:00:00 2001 From: Yat Long Poon <56300571+ylpoonlg@users.noreply.github.com> Date: Mon, 23 Mar 2026 16:12:07 +0000 Subject: [PATCH 3/4] Remove unused variable Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/benchmarks/micro/sve/GatherLoad.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/benchmarks/micro/sve/GatherLoad.cs b/src/benchmarks/micro/sve/GatherLoad.cs index 3c0e6dd5a26..f02baa3a8a6 100644 --- a/src/benchmarks/micro/sve/GatherLoad.cs +++ b/src/benchmarks/micro/sve/GatherLoad.cs @@ -78,7 +78,6 @@ public unsafe void SveGatherLoad() int i = 0; int cntw = (int)Sve.Count32BitElements(); - Vector ones = Vector.One; Vector resVec = Vector.Zero; Vector pTrue = Sve.CreateTrueMaskUInt32(); Vector pLoop = Sve.CreateWhileLessThanMask32Bit(0, Size); From 2ad287da9547d7163395a6fa9bbfc0744f181a5a Mon Sep 17 00:00:00 2001 From: Yat Long Poon Date: Mon, 23 Mar 2026 16:32:20 +0000 Subject: [PATCH 4/4] Use uint for gather result --- src/benchmarks/micro/sve/GatherLoad.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/benchmarks/micro/sve/GatherLoad.cs b/src/benchmarks/micro/sve/GatherLoad.cs index f02baa3a8a6..badec15d255 100644 --- a/src/benchmarks/micro/sve/GatherLoad.cs +++ b/src/benchmarks/micro/sve/GatherLoad.cs @@ -29,7 +29,7 @@ public Config() private uint[] _objects; private uint[] _indices; - private ulong _result; + private uint _result; [GlobalSetup] public virtual void Setup() @@ -45,10 +45,10 @@ public virtual void Setup() [GlobalCleanup] public virtual void Verify() { - ulong current = _result; + uint current = _result; Setup(); Scalar(); - ulong scalar = _result; + uint scalar = _result; // Check that the result is the same as the scalar result. Debug.Assert(current == scalar); } @@ -61,7 +61,7 @@ public unsafe void Scalar() { fixed (uint* objects = _objects, indices = _indices) { - ulong res = 0; + uint res = 0; for (int i = 0; i < Size; i++) { res += objects[indices[i]]; @@ -94,7 +94,7 @@ public unsafe void SveGatherLoad() pLoop = Sve.CreateWhileLessThanMask32Bit(i, Size); } // Add up all elements in resVec. - ulong res = (ulong)Sve.AddAcross(resVec).ToScalar(); + uint res = (uint)Sve.AddAcross(resVec).ToScalar(); _result = res; } }