Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vectorize IndexOfAnyExcept for four values #73696

Merged
merged 4 commits into from
Aug 15, 2022

Conversation

adamsitnik
Copy link
Member

I took a look and the usage of IndexOfAnyExcept and the patterns are following:

  • IndexOfAnyExcept(byte) - most frequent
  • IndexOfAnyExcept(int), IndexOfAnyExcept(char) - sporadic
  • IndexOfAnyExcept(" \t\r\n") - few places

Since the first two got vectorized by @stephentoub in #73488 I've vectorized the 4 values case.

Benchmarks:

public class IndexOfAnyExcept
{
    private string _whiteSpaces, _noWhiteSpaces;

    [Params(1, 4, 16, 64, 256, 1024)]
    public int Length { get; set; }

    [GlobalSetup]
    public void Setup()
    {
        _whiteSpaces = new string(' ', Length);
        _noWhiteSpaces = new string('a', Length);
    }

    [Benchmark]
    public int ImmediateMismatch() => _noWhiteSpaces.AsSpan().IndexOfAnyExcept(" \t\r\n");

    [Benchmark]
    public int NoMismatch() => _whiteSpaces.AsSpan().IndexOfAnyExcept(" \t\r\n");
}
BenchmarkDotNet=v0.13.1.1845-nightly, OS=Windows 11 (10.0.22000.795/21H2)
AMD Ryzen Threadripper PRO 3945WX 12-Cores, 1 CPU, 24 logical and 12 physical cores
.NET SDK=7.0.100-preview.7.22377.5
  [Host]     : .NET 7.0.0 (7.0.22.37506), X64 RyuJIT AVX2
  Job-OQFSEO : .NET 7.0.0 (42.42.42.42424), X64 RyuJIT AVX2
    Job-main : .NET 7.0.0 (42.42.42.42424), X64 RyuJIT AVX2
Method Job Length Mean Ratio
ImmediateMismatch Job-PR 1 7.401 ns 1.30
ImmediateMismatch Job-main 1 5.679 ns 1.00
NoMismatch Job-PR 1 7.259 ns 1.32
NoMismatch Job-main 1 5.483 ns 1.00
ImmediateMismatch Job-PR 4 7.597 ns 1.34
ImmediateMismatch Job-main 4 5.689 ns 1.00
NoMismatch Job-PR 4 10.021 ns 0.76
NoMismatch Job-main 4 13.155 ns 1.00
ImmediateMismatch Job-PR 16 9.135 ns 1.47
ImmediateMismatch Job-main 16 6.226 ns 1.00
NoMismatch Job-PR 16 9.973 ns 0.24
NoMismatch Job-main 16 41.561 ns 1.00
ImmediateMismatch Job-PR 64 9.123 ns 1.47
ImmediateMismatch Job-main 64 6.216 ns 1.00
NoMismatch Job-PR 64 15.135 ns 0.09
NoMismatch Job-main 64 164.828 ns 1.00
ImmediateMismatch Job-PR 256 9.126 ns 1.47
ImmediateMismatch Job-main 256 6.196 ns 1.00
NoMismatch Job-PR 256 31.836 ns 0.05
NoMismatch Job-main 256 642.908 ns 1.00
ImmediateMismatch Job-PR 1024 9.128 ns 1.48
ImmediateMismatch Job-main 1024 6.153 ns 1.00
NoMismatch Job-PR 1024 104.512 ns 0.04
NoMismatch Job-main 1024 2,534.292 ns 1.00

It's clearly a tradeoff:

  • for all strings with immediate mismatch, the method has regressed by 2-3ns
  • same goes for very short strings with no mismatch
  • for other cases, when there is no mismatch the longer the string the bigger the gain

@adamsitnik adamsitnik added this to the 7.0.0 milestone Aug 10, 2022
@adamsitnik adamsitnik requested a review from stephentoub August 10, 2022 15:01
@ghost
Copy link

ghost commented Aug 10, 2022

Tagging subscribers to this area: @dotnet/area-system-memory
See info in area-owners.md if you want to be subscribed.

Issue Details

I took a look and the usage of IndexOfAnyExcept and the patterns are following:

  • IndexOfAnyExcept(byte) - most frequent
  • IndexOfAnyExcept(int), IndexOfAnyExcept(char) - sporadic
  • IndexOfAnyExcept(" \t\r\n") - few places

Since the first two got vectorized by @stephentoub in #73488 I've vectorized the 4 values case.

Benchmarks:

public class IndexOfAnyExcept
{
    private string _whiteSpaces, _noWhiteSpaces;

    [Params(1, 4, 16, 64, 256, 1024)]
    public int Length { get; set; }

    [GlobalSetup]
    public void Setup()
    {
        _whiteSpaces = new string(' ', Length);
        _noWhiteSpaces = new string('a', Length);
    }

    [Benchmark]
    public int ImmediateMismatch() => _noWhiteSpaces.AsSpan().IndexOfAnyExcept(" \t\r\n");

    [Benchmark]
    public int NoMismatch() => _whiteSpaces.AsSpan().IndexOfAnyExcept(" \t\r\n");
}
BenchmarkDotNet=v0.13.1.1845-nightly, OS=Windows 11 (10.0.22000.795/21H2)
AMD Ryzen Threadripper PRO 3945WX 12-Cores, 1 CPU, 24 logical and 12 physical cores
.NET SDK=7.0.100-preview.7.22377.5
  [Host]     : .NET 7.0.0 (7.0.22.37506), X64 RyuJIT AVX2
  Job-OQFSEO : .NET 7.0.0 (42.42.42.42424), X64 RyuJIT AVX2
    Job-main : .NET 7.0.0 (42.42.42.42424), X64 RyuJIT AVX2
Method Job Length Mean Ratio
ImmediateMismatch Job-PR 1 7.401 ns 1.30
ImmediateMismatch Job-main 1 5.679 ns 1.00
NoMismatch Job-PR 1 7.259 ns 1.32
NoMismatch Job-main 1 5.483 ns 1.00
ImmediateMismatch Job-PR 4 7.597 ns 1.34
ImmediateMismatch Job-main 4 5.689 ns 1.00
NoMismatch Job-PR 4 10.021 ns 0.76
NoMismatch Job-main 4 13.155 ns 1.00
ImmediateMismatch Job-PR 16 9.135 ns 1.47
ImmediateMismatch Job-main 16 6.226 ns 1.00
NoMismatch Job-PR 16 9.973 ns 0.24
NoMismatch Job-main 16 41.561 ns 1.00
ImmediateMismatch Job-PR 64 9.123 ns 1.47
ImmediateMismatch Job-main 64 6.216 ns 1.00
NoMismatch Job-PR 64 15.135 ns 0.09
NoMismatch Job-main 64 164.828 ns 1.00
ImmediateMismatch Job-PR 256 9.126 ns 1.47
ImmediateMismatch Job-main 256 6.196 ns 1.00
NoMismatch Job-PR 256 31.836 ns 0.05
NoMismatch Job-main 256 642.908 ns 1.00
ImmediateMismatch Job-PR 1024 9.128 ns 1.48
ImmediateMismatch Job-main 1024 6.153 ns 1.00
NoMismatch Job-PR 1024 104.512 ns 0.04
NoMismatch Job-main 1024 2,534.292 ns 1.00

It's clearly a tradeoff:

  • for all strings with immediate mismatch, the method has regressed by 2-3ns
  • same goes for very short strings with no mismatch
  • for other cases, when there is no mismatch the longer the string the bigger the gain
Author: adamsitnik
Assignees: -
Labels:

area-System.Memory, tenet-performance

Milestone: 7.0.0

@ghost ghost assigned adamsitnik Aug 10, 2022
@danmoseley
Copy link
Member

Some pretty nice improvements!

@adamsitnik
Copy link
Member Author

The CI did not start... I am going to close and re-open the PR

@adamsitnik adamsitnik closed this Aug 11, 2022
@adamsitnik adamsitnik reopened this Aug 11, 2022
@@ -619,6 +680,9 @@ public static ReadOnlyMemory<char> AsMemory(this string? text, Range range)
case 3:
return IndexOfAnyExcept(span, values[0], values[1], values[2]);

case 4: // common for searching whitespaces
Copy link
Member

Choose a reason for hiding this comment

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

Nit: (don't restart CI just for this)

Suggested change
case 4: // common for searching whitespaces
case 4: // common for searching ASCII whitespaces

}
}

return -1;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int ComputeIndex<T>(ref T searchSpace, ref T current, Vector128<T> notEquals) where T : struct, IEquatable<T>
Copy link
Member

Choose a reason for hiding this comment

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

There are many other methods in this type, whereas this "ComputeIndex" is only relevant to LastIndexOfExcept. Consider renaming it accordingly.

Copy link
Member

@stephentoub stephentoub left a comment

Choose a reason for hiding this comment

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

Thanks!

@adamsitnik
Copy link
Member Author

@stephentoub Since I currently can't merge #73768, I am going to merge this PR now and address your feedback later (renaming ComputeIndex to ComputeFirstIndex). Normally I would do it first, but we have limited time ;)

@adamsitnik adamsitnik merged commit 49cb4ff into dotnet:main Aug 15, 2022
@ghost ghost locked as resolved and limited conversation to collaborators Sep 15, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants