This is an automated email from the ASF dual-hosted git repository.

CurtHagenlocher pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-dotnet.git


The following commit(s) were added to refs/heads/main by this push:
     new b739fda  perf: Copy fixed-size binary values using CopyTo (#434)
b739fda is described below

commit b739fda203ce7ec380b44e9d2d84341aee1be83b
Author: George Vanburgh <[email protected]>
AuthorDate: Mon Sep 14 00:48:30 2026 +0100

    perf: Copy fixed-size binary values using CopyTo (#434)
    
    When looking at some work related to #96, I noticed that
    `FixedSizeBinaryArray.BuilderBase.Set` copied each value one byte at a
    time. `CopyTo` copies the requested fixed-width slice in one call to
    remove work that scaled with the byte width. It's a small absolute
    improvement, but it does show up on the hot path for some fixed width
    type conversions.
    
    Benchmarked the impact on `GuidArray`, and setting raw bytes. Results
    are mean time per operation; lower is better.
    
    | Operation | .NET 8 before | .NET 8 after | Change | .NET 10 before |
    .NET 10 after | Change |
    |---|---:|---:|---:|---:|---:|---:|
    | Set GUIDs | 328.6 µs | 170.5 µs | 48% faster | 367.2 µs | 152.4 µs |
    58% faster |
    | Set precomputed 16-byte values | 259.3 µs | 79.3 µs | 69% faster |
    318.2 µs | 62.8 µs | 80% faster |
    
    Both runtime comparisons used BenchmarkDotNet 0.15.8, 15 measured
    iterations across two launches, on an AMD Ryzen 7 5800X. Runtime
    versions were .NET 8.0.31 and .NET 10.0.12. The harness verifies GUID
    round trips in setup.
    
    <details>
    
    <summary>Benchmark harness used for these GUID results:</summary>
    
    ```csharp
    using System;
    using BenchmarkDotNet.Attributes;
    
    namespace Apache.Arrow.Benchmarks
    {
        [MemoryDiagnoser]
        public class GuidBenchmark
        {
            [Params(10_000)]
            public int Count { get; set; }
            private Guid[] _values;
            private byte[][] _bytes;
            private GuidArray.Builder _setBuilder;
    
            [GlobalSetup]
            public void Setup()
            {
                var random = new Random(42);
                _values = new Guid[Count];
                _bytes = new byte[Count][];
                _setBuilder = new GuidArray.Builder().Resize(Count);
                _appendBuilder = new GuidArray.Builder().Reserve(Count);
                byte[] bytes = new byte[16];
                for (int i = 0; i < Count; i++)
                {
                    random.NextBytes(bytes);
                    _values[i] = new Guid(bytes);
                    _bytes[i] = GuidArray.GuidToBytes(_values[i]);
                }
                SetGuid();
                _array = _setBuilder.Build();
                for (int i = 0; i < Count; i++)
                    if (_array.GetGuid(i) != _values[i]) throw new 
Exception("GUID round trip failed");
            }
    
            [Benchmark]
            public GuidArray.Builder SetGuid()
            {
                var builder = _setBuilder;
                for (int i = 0; i < Count; i++) builder.Set(i, _values[i]);
                return builder;
            }
    
            [Benchmark]
            public GuidArray.Builder SetBytes()
            {
                var builder = _setBuilder;
                for (int i = 0; i < Count; i++) builder.Set(i, _bytes[i]);
                return builder;
            }
    
            [GlobalCleanup]
            public void Cleanup() => _array.Dispose();
        }
    }
    ```
    </details>
    
    Also ran some benchmarks for different values of ByteWidth:
    
    ### Before
    
    | Method | Job | Runtime | Count | Mean | Error | StdDev | Ratio |
    RatioSD | Allocated | Alloc Ratio |
    |------------ |---------- |---------- |------
    
|----------:|----------:|----------:|------:|--------:|----------:|------------:|
    | SetBytes_4 | .NET 10.0 | .NET 10.0 | 10000 | 103.39 μs | 0.680 μs |
    1.018 μs | 1.12 | 0.01 | - | NA |
    | SetBytes_4 | .NET 8.0 | .NET 8.0 | 10000 | 92.33 μs | 0.581 μs | 0.814
    μs | 1.00 | 0.01 | - | NA |
    | | | | | | | | | | | |
    | SetBytes_8 | .NET 10.0 | .NET 10.0 | 10000 | 205.57 μs | 24.169 μs |
    34.662 μs | 1.38 | 0.23 | - | NA |
    | SetBytes_8 | .NET 8.0 | .NET 8.0 | 10000 | 148.93 μs | 2.049 μs |
    3.004 μs | 1.00 | 0.03 | - | NA |
    | | | | | | | | | | | |
    | SetBytes_16 | .NET 10.0 | .NET 10.0 | 10000 | 307.22 μs | 1.168 μs |
    1.638 μs | 1.16 | 0.02 | - | NA |
    | SetBytes_16 | .NET 8.0 | .NET 8.0 | 10000 | 265.20 μs | 3.166 μs |
    4.738 μs | 1.00 | 0.02 | - | NA |
    | | | | | | | | | | | |
    | SetBytes_32 | .NET 10.0 | .NET 10.0 | 10000 | 579.55 μs | 2.377 μs |
    3.254 μs | 1.16 | 0.02 | - | NA |
    | SetBytes_32 | .NET 8.0 | .NET 8.0 | 10000 | 497.96 μs | 5.542 μs |
    7.948 μs | 1.00 | 0.02 | - | NA |
    
    ### After
    
    | Method | Job | Runtime | Count | Mean | Error | StdDev | Median |
    Ratio | RatioSD | Allocated | Alloc Ratio |
    |------------ |---------- |---------- |------
    
|---------:|---------:|---------:|---------:|------:|--------:|----------:|------------:|
    | SetBytes_4 | .NET 10.0 | .NET 10.0 | 10000 | 62.43 μs | 0.610 μs |
    0.914 μs | 62.26 μs | 0.74 | 0.01 | - | NA |
    | SetBytes_4 | .NET 8.0 | .NET 8.0 | 10000 | 84.29 μs | 0.356 μs | 0.522
    μs | 84.20 μs | 1.00 | 0.01 | - | NA |
    | | | | | | | | | | | | |
    | SetBytes_8 | .NET 10.0 | .NET 10.0 | 10000 | 69.49 μs | 4.899 μs |
    7.181 μs | 75.72 μs | 0.82 | 0.08 | - | NA |
    | SetBytes_8 | .NET 8.0 | .NET 8.0 | 10000 | 85.25 μs | 1.264 μs | 1.892
    μs | 84.68 μs | 1.00 | 0.03 | - | NA |
    | | | | | | | | | | | | |
    | SetBytes_16 | .NET 10.0 | .NET 10.0 | 10000 | 61.86 μs | 0.297 μs |
    0.407 μs | 61.83 μs | 0.73 | 0.01 | - | NA |
    | SetBytes_16 | .NET 8.0 | .NET 8.0 | 10000 | 84.78 μs | 0.803 μs |
    1.201 μs | 84.42 μs | 1.00 | 0.02 | - | NA |
    | | | | | | | | | | | | |
    | SetBytes_32 | .NET 10.0 | .NET 10.0 | 10000 | 62.44 μs | 0.506 μs |
    0.709 μs | 62.09 μs | 0.73 | 0.02 | - | NA |
    | SetBytes_32 | .NET 8.0 | .NET 8.0 | 10000 | 86.12 μs | 1.471 μs |
    2.061 μs | 84.99 μs | 1.00 | 0.03 | - | NA |
---
 src/Apache.Arrow/Arrays/FixedSizeBinaryArray.cs | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/src/Apache.Arrow/Arrays/FixedSizeBinaryArray.cs 
b/src/Apache.Arrow/Arrays/FixedSizeBinaryArray.cs
index 2bc1965..c67a277 100644
--- a/src/Apache.Arrow/Arrays/FixedSizeBinaryArray.cs
+++ b/src/Apache.Arrow/Arrays/FixedSizeBinaryArray.cs
@@ -222,10 +222,7 @@ namespace Apache.Arrow.Arrays
             public TBuilder Set(int index, ReadOnlySpan<byte> value)
             {
                 int startIndex = index * ByteWidth;
-                for (int i = 0; i < ByteWidth; i++)
-                {
-                    ValueBuffer.Span[startIndex + i] = value[i];
-                }
+                value.Slice(0, 
ByteWidth).CopyTo(ValueBuffer.Span.Slice(startIndex, ByteWidth));
 
                 ValidityBuffer.Set(index, true);
                 return Instance;

Reply via email to