Skip to content

Conversation

@kzrnm
Copy link
Contributor

@kzrnm kzrnm commented Mar 9, 2025

Related #63722, #113005

benchmark


BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3194)
13th Gen Intel Core i5-13500, 1 CPU, 20 logical and 14 physical cores
.NET SDK 10.0.100-preview.3.25125.5
  [Host]     : .NET 10.0.0 (10.0.25.12411), X64 RyuJIT AVX2
  Job-UQJRRD : .NET 10.0.0 (42.42.42.42424), X64 RyuJIT AVX2
  Job-IYICYK : .NET 10.0.0 (42.42.42.42424), X64 RyuJIT AVX2


Method Toolchain N Mean Ratio Allocated Alloc Ratio
LeftShift \main\corerun.exe 256 45.553 ns 1.00 - NA
LeftShift \pr\corerun.exe 256 10.037 ns 0.22 - NA
RightShift \main\corerun.exe 256 56.281 ns 1.00 - NA
RightShift \pr\corerun.exe 256 9.565 ns 0.17 - NA
LeftShift \main\corerun.exe 65536 10,493.384 ns 1.00 - NA
LeftShift \pr\corerun.exe 65536 1,699.862 ns 0.16 - NA
RightShift \main\corerun.exe 65536 11,729.467 ns 1.00 - NA
RightShift \pr\corerun.exe 65536 1,860.553 ns 0.16 - NA
LeftShift \main\corerun.exe 16777216 3,054,162.161 ns 1.00 - NA
LeftShift \pr\corerun.exe 16777216 1,051,349.065 ns 0.34 - NA
RightShift \main\corerun.exe 16777216 3,341,553.776 ns 1.00 - NA
RightShift \pr\corerun.exe 16777216 639,720.483 ns 0.19 - NA
Benchmark code
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using System.Collections;

[MemoryDiagnoser(false)]
[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public class ShiftTest
{
    [Params([
        1 << 8,
        1 << 16,
        1 << 24,
    ])]
    public int N;
    const int shiftSize = 13;
    BitArray b;

    [GlobalSetup]
    public void Setup()
    {
        var bytes = new byte[N];
        new Random(227).NextBytes(bytes);
        b = new BitArray(bytes);
    }

    [Benchmark] public BitArray LeftShift() => b.LeftShift(shiftSize);
    [Benchmark] public BitArray RightShift() => b.RightShift(shiftSize);
}

@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Mar 9, 2025
@eiriktsarpalis
Copy link
Member

Hi @kzrnm, apologies for the delay in reviewing this. Is there a chance you could rebase your changes on top of the latest main? I don't have write permissions in your PR branch to do it myself.

@eiriktsarpalis eiriktsarpalis added the needs-author-action An issue or pull request that requires more info or actions from the author. label Jul 1, 2025
@kzrnm
Copy link
Contributor Author

kzrnm commented Jul 3, 2025

I updated to match the latest implementation.
In commit e96a2b8, the implementation for shifting as a 32-bit integer has been updated to apply a mask within the byte range, aligning it with the behavior of the Apply method.

@tannergooding tannergooding requested a review from Copilot October 24, 2025 16:07
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR optimizes the BitArray.LeftShift and BitArray.RightShift methods by leveraging hardware intrinsics (Vector512, Vector256, Vector128) to achieve significant performance improvements. The benchmark results show 3-6x speedup across different array sizes.

Key Changes:

  • Replaced int-based shifting with byte-based operations using SIMD vectors
  • Introduced vectorized processing for bulk shift operations with fallback to scalar code
  • Added comprehensive test coverage for various bit array sizes and shift amounts

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
BitArray.cs Refactored RightShift and LeftShift to use byte-level operations with SIMD intrinsics (Vector512/256/128), replacing the previous int-based implementation
BitArray_OperatorsTests.cs Enhanced test coverage with additional test cases covering various sizes (1023-1025) and shift amounts, plus validation of high-order bit clearing


while (fromIndex >= 5)
{
uint lo = (Unsafe.ReadUnaligned<uint>(ref Unsafe.AddByteOffset(ref p, (uint)(fromIndex -= 4))) << shiftCount) & shiftMask;
Copy link

Copilot AI Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The decrement operation fromIndex -= 4 is embedded within the offset calculation, which reduces readability. Consider separating this into two statements: first decrement fromIndex, then perform the read operation.

Suggested change
uint lo = (Unsafe.ReadUnaligned<uint>(ref Unsafe.AddByteOffset(ref p, (uint)(fromIndex -= 4))) << shiftCount) & shiftMask;
fromIndex -= 4;
uint lo = (Unsafe.ReadUnaligned<uint>(ref Unsafe.AddByteOffset(ref p, (uint)fromIndex)) << shiftCount) & shiftMask;

Copilot uses AI. Check for mistakes.
uint lo = (Unsafe.ReadUnaligned<uint>(ref Unsafe.AddByteOffset(ref p, (uint)(fromIndex -= 4))) << shiftCount) & shiftMask;
uint hi = (Unsafe.ReadUnaligned<uint>(ref Unsafe.AddByteOffset(ref p, (uint)(fromIndex - 1))) >>> carryCount) & carryMask;
uint result = hi | lo;
Unsafe.WriteUnaligned(ref Unsafe.AddByteOffset(ref p, toIndex -= 4), result);
Copy link

Copilot AI Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The decrement operation toIndex -= 4 is embedded within the offset calculation, which reduces readability. Consider separating this into two statements: first decrement toIndex, then perform the write operation.

Suggested change
Unsafe.WriteUnaligned(ref Unsafe.AddByteOffset(ref p, toIndex -= 4), result);
toIndex -= 4;
Unsafe.WriteUnaligned(ref Unsafe.AddByteOffset(ref p, toIndex), result);

Copilot uses AI. Check for mistakes.

while (fromIndex >= TVector.ElementCount + 1)
{
TVector hi = TVector.LoadUnsafe(ref p, (nuint)(fromIndex -= TVector.ElementCount)) << shiftCount;
Copy link

Copilot AI Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The decrement operation fromIndex -= TVector.ElementCount is embedded within the offset calculation, which reduces readability. Consider separating this into two statements: first decrement fromIndex, then perform the load operation.

Suggested change
TVector hi = TVector.LoadUnsafe(ref p, (nuint)(fromIndex -= TVector.ElementCount)) << shiftCount;
fromIndex -= TVector.ElementCount;
TVector hi = TVector.LoadUnsafe(ref p, (nuint)fromIndex) << shiftCount;

Copilot uses AI. Check for mistakes.
TVector hi = TVector.LoadUnsafe(ref p, (nuint)(fromIndex -= TVector.ElementCount)) << shiftCount;
TVector lo = TVector.LoadUnsafe(ref p, (nuint)(fromIndex - 1)) >>> carryCount;
TVector result = hi | lo;
result.StoreUnsafe(ref p, (nuint)(toIndex -= TVector.ElementCount));
Copy link

Copilot AI Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The decrement operation toIndex -= TVector.ElementCount is embedded within the offset calculation, which reduces readability. Consider separating this into two statements: first decrement toIndex, then perform the store operation.

Suggested change
result.StoreUnsafe(ref p, (nuint)(toIndex -= TVector.ElementCount));
toIndex -= TVector.ElementCount;
result.StoreUnsafe(ref p, (nuint)toIndex);

Copilot uses AI. Check for mistakes.
Comment on lines +315 to +320
(int byteIndex, int bitOffeset) = Math.DivRem(length, BitsPerByte);
if (bitOffeset != 0)
{
Span<byte> bs = CollectionsMarshal.AsBytes(ba);
Assert.Equal(byteIndex + 1, bs.Length);
Assert.Equal(0, bs[byteIndex] >> bitOffeset);
Copy link

Copilot AI Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected spelling of 'bitOffeset' to 'bitOffset'.

Suggested change
(int byteIndex, int bitOffeset) = Math.DivRem(length, BitsPerByte);
if (bitOffeset != 0)
{
Span<byte> bs = CollectionsMarshal.AsBytes(ba);
Assert.Equal(byteIndex + 1, bs.Length);
Assert.Equal(0, bs[byteIndex] >> bitOffeset);
(int byteIndex, int bitOffset) = Math.DivRem(length, BitsPerByte);
if (bitOffset != 0)
{
Span<byte> bs = CollectionsMarshal.AsBytes(ba);
Assert.Equal(byteIndex + 1, bs.Length);
Assert.Equal(0, bs[byteIndex] >> bitOffset);

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-System.Collections community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants