-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Try SimdUnicode for Utf8 validation #103860
Conversation
@EgorBot -arm64 -amd -intel -profiler using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Text.Unicode;
BenchmarkRunner.Run<Utf8Bench>(args: args);
public class Utf8Bench
{
public static IEnumerable<byte[]> TestData()
{
// 1069 bytes, ru-ru Lorem impsum
yield return "Лорем ипсум долор сит амет, хас тале феугаит ех, мел дицит сонет сцрипта ид? Еррорибус темпорибус адверсариум про те, видит ностер хас не, яуод феугаит цу ест. Но дицунт рецусабо диссентиас цум, оптион евертитур ан вих. Но мел антиопам молестиае, продессет абхорреант витуператорибус ат сит, дицант глориатур персецути при еу. При еяуидем пхаедрум рецусабо ех, не вим ерант вертерем Ехерци семпер те нец. Ид нолуиссе детерруиссет нам, яуо ан адхуц дицит пертинациа, мел тота цлита цомпрехенсам ид? Ид аугуе граецис еффициенди вис, ат анимал фиерент инструцтиор пер, не виде еффициенди при!"u8.ToArray();
// 527 bytes, ru-ru Lorem impsum
yield return "Лорем ипсум долор сит амет, хас тале феугаит ех, мел дицит сонет сцрипта ид? Еррорибус темпорибус адверсариум про те, видит ностер хас не, яуод феугаит цу ест. Но дицунт рецусабо диссентиас цум, оптион евертитур ан вих. Но мел антиопам молестиае, продессет абхорреант витуператорибус ат сит"u8.ToArray();
// 286 bytes, ru-ru Lorem impsum
yield return "Лорем ипсум долор сит амет, хас тале феугаит ех, мел дицит сонет сцрипта ид? Еррорибус темпорибус адверсариум про те, видит ностер хас не, яуод феугаит цу ест."u8.ToArray();
// 136 bytes, ru-ru Lorem impsum
yield return "Лорем ипсум долор сит амет, хас тале феугаит ех, мел дицит сонет сцрипта ид?"u8.ToArray();
}
[Benchmark]
[ArgumentsSource(nameof(TestData))]
public bool Count(byte[] str) => Utf8.IsValid(str);
} |
Benchmark results on Intel
Flame graphs: Main vs PR 🔥 For clean |
Benchmark results on Amd
Flame graphs: Main vs PR 🔥 For clean |
Benchmark results on Arm64
Flame graphs: Main vs PR 🔥 For clean |
So, my understanding that See the benchmarks results above ^ I used 3 different CPUs:
Also, looks like SimdUnicode is a bit slower when input is semi-ASCII (e.g. rare non-ASCII symbols in random places). cc @lemire @Nick-Nuon @GrabYourPitchforks I don't think I am in charge to decide whether we take it or not considering amount of changes, importance of these APIs and the fact that it only kicks in for large inputs, I'll leave that decision to System.Text.* code owners, my only comment that is nice to see optimizations in the non-ASCII space, since .NET is fairly ASCII-focused. E.g. this API in Main:
Although, I am surprised we don't see improvements with this PR on arm64.. |
Closing as the intent of this PR was to validate changes on CI + run initial benchmarks, see #103860 (comment) |
@EgorBo We could look at the Ampere performance. However, we have had good luck with Graviton and Apple Silicon. We could also examine the cases where the new code is less favourable generally. |
In the initial release, we did not request inlining for some functions and there are small overhead issues that were not tuned. I did some initial tuning (very little) and here are the results on my Apple M2. We are now going to include your benchmark data. Hopefully, you've not included swear words in there. :-) As you can see, at least on my Apple M2, the gains are all over.
|
I have updated results at simdutf/SimdUnicode#46 with positive benchmarks on AWS Graviton 3 (Neoverse V1). We just needed to do some minor tuning. |
On a Neoverse V1 (Graviton 3), our validation function is 1.3 to over four times
Please see simdutf/SimdUnicode#46 for full details, including @EgorBo's inputs which are now part of our benchmarks. |
05f51aa
to
ba04808
Compare
@EgorBot -arm64 -amd -profiler using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Text.Unicode;
BenchmarkRunner.Run<Utf8Bench>(args: args);
public class Utf8Bench
{
public static IEnumerable<byte[]> TestData()
{
// 1069 bytes, ru-ru Lorem impsum
yield return "Лорем ипсум долор сит амет, хас тале феугаит ех, мел дицит сонет сцрипта ид? Еррорибус темпорибус адверсариум про те, видит ностер хас не, яуод феугаит цу ест. Но дицунт рецусабо диссентиас цум, оптион евертитур ан вих. Но мел антиопам молестиае, продессет абхорреант витуператорибус ат сит, дицант глориатур персецути при еу. При еяуидем пхаедрум рецусабо ех, не вим ерант вертерем Ехерци семпер те нец. Ид нолуиссе детерруиссет нам, яуо ан адхуц дицит пертинациа, мел тота цлита цомпрехенсам ид? Ид аугуе граецис еффициенди вис, ат анимал фиерент инструцтиор пер, не виде еффициенди при!"u8.ToArray();
// 527 bytes, ru-ru Lorem impsum
yield return "Лорем ипсум долор сит амет, хас тале феугаит ех, мел дицит сонет сцрипта ид? Еррорибус темпорибус адверсариум про те, видит ностер хас не, яуод феугаит цу ест. Но дицунт рецусабо диссентиас цум, оптион евертитур ан вих. Но мел антиопам молестиае, продессет абхорреант витуператорибус ат сит"u8.ToArray();
// 286 bytes, ru-ru Lorem impsum
yield return "Лорем ипсум долор сит амет, хас тале феугаит ех, мел дицит сонет сцрипта ид? Еррорибус темпорибус адверсариум про те, видит ностер хас не, яуод феугаит цу ест."u8.ToArray();
// 136 bytes, ru-ru Lorem impsum
yield return "Лорем ипсум долор сит амет, хас тале феугаит ех, мел дицит сонет сцрипта ид?"u8.ToArray();
}
[Benchmark]
[ArgumentsSource(nameof(TestData))]
public bool Count(byte[] str) => Utf8.IsValid(str);
} |
Benchmark results on Amd
Flame graphs: Main vs PR 🔥 For clean |
I think that they are explained by the type of ARM processor being tested. I suspect it might be a processor with weak SIMD performance like Neoverse N1. A possibility to consider is to selectively enable SimdUnicode. I expect good performance on recent ARM-based Qualcomm processors. |
Here are the results with a Windows ARM dev kit (2023) with a Snapdragon 8cx Gen 3. They are pretty good!!! Of course, that's not going to be an important platform but I do not yet have access to hardware with the latest Qualcomm processors.
|
@EgorBo I have done a bit more tuning, see simdutf/SimdUnicode#48 I have not tested on a Neoverse N1, but I am not overly optimistic given that these tiny chips have poor SIMD performance. |
Numbers on Apple M2 Max (high performance profile, on charge):
So the numbers definitely look a lot better for Apple M CPU |
Benchmark results on Arm64
Flame graphs: Main vs PR 🔥 For clean |
@EgorBot -arm64 --envvars DOTNET_EnableAVX:0 DOTNET_ReadyToRun:0 DOTNET_TieredCompilation:0 using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.InteropServices;
using System.Text.Unicode;
BenchmarkRunner.Run<Utf8Bench>(args: args);
public unsafe class Utf8Bench
{
void* _aligned64 = null;
[GlobalSetup]
public void GlobalSetup()
{
_aligned64 = NativeMemory.AlignedAlloc(2048, 64);
ReadOnlySpan<byte> testData = "Лорем ипсум долор сит амет, хас тале феугаит ех, мел дицит сонет сцрипта ид? Еррорибус темпорибус адверсариум про те, видит ностер хас не, яуод феугаит цу ест. Но дицунт рецусабо диссентиас цум, оптион евертитур ан вих. Но мел антиопам молестиае, продессет абхорреант витуператорибус ат сит, дицант глориатур персецути при еу. При еяуидем пхаедрум рецусабо ех, не вим ерант вертерем Ехерци семпер те нец. Ид нолуиссе детерруиссет нам, яуо ан адхуц дицит пертинациа, мел тота цлита цомпрехенсам ид? Ид аугуе граецис еффициенди вис, ат анимал фиерент инструцтиор пер, не виде еффициенди при!"u8;
testData.CopyTo(new Span<byte>(_aligned64, 2048));
}
[GlobalCleanup]
public void GlobalCleanup() => NativeMemory.Free(_aligned64);
[Benchmark]
public bool IsValid_aligned() => Utf8.IsValid(new Span<byte>(_aligned64, 1069));
} |
Benchmark results on Arm64
|
The problem on Neoverse N1 is that you have just two SIMD execution units, and the 8-bit add-across has terrible performance. Yet we need, somehow, to count the number of 4-byte characters. That becomes a bottleneck. We could amortize the cost over wider iteration but I'd rather avoid it that if I can. On a Neoverse N1 (Graviton 2), our validation function is up to three times
On your Russian (?) tests, the results are not rather neutral:
|
@EgorBot -arm64 using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.InteropServices;
using System.Text.Unicode;
BenchmarkRunner.Run<Utf8Bench>(args: args);
public unsafe class Utf8Bench
{
void* _aligned64 = null;
[GlobalSetup]
public void GlobalSetup()
{
_aligned64 = NativeMemory.AlignedAlloc(2048, 64);
ReadOnlySpan<byte> testData = "Лорем ипсум долор сит амет, хас тале феугаит ех, мел дицит сонет сцрипта ид? Еррорибус темпорибус адверсариум про те, видит ностер хас не, яуод феугаит цу ест. Но дицунт рецусабо диссентиас цум, оптион евертитур ан вих. Но мел антиопам молестиае, продессет абхорреант витуператорибус ат сит, дицант глориатур персецути при еу. При еяуидем пхаедрум рецусабо ех, не вим ерант вертерем Ехерци семпер те нец. Ид нолуиссе детерруиссет нам, яуо ан адхуц дицит пертинациа, мел тота цлита цомпрехенсам ид? Ид аугуе граецис еффициенди вис, ат анимал фиерент инструцтиор пер, не виде еффициенди при!"u8;
testData.CopyTo(new Span<byte>(_aligned64, 2048));
}
[GlobalCleanup]
public void GlobalCleanup() => NativeMemory.Free(_aligned64);
[Benchmark]
public bool IsValid_aligned() => Utf8.IsValid(new Span<byte>(_aligned64, 1069));
} |
@EgorBot -arm64 -profiler using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Text.Unicode;
BenchmarkRunner.Run<Utf8Bench>(args: args);
public class Utf8Bench
{
public static IEnumerable<byte[]> TestData()
{
// 1069 bytes, ru-ru Lorem impsum
yield return "Лорем ипсум долор сит амет, хас тале феугаит ех, мел дицит сонет сцрипта ид? Еррорибус темпорибус адверсариум про те, видит ностер хас не, яуод феугаит цу ест. Но дицунт рецусабо диссентиас цум, оптион евертитур ан вих. Но мел антиопам молестиае, продессет абхорреант витуператорибус ат сит, дицант глориатур персецути при еу. При еяуидем пхаедрум рецусабо ех, не вим ерант вертерем Ехерци семпер те нец. Ид нолуиссе детерруиссет нам, яуо ан адхуц дицит пертинациа, мел тота цлита цомпрехенсам ид? Ид аугуе граецис еффициенди вис, ат анимал фиерент инструцтиор пер, не виде еффициенди при!"u8.ToArray();
// 527 bytes, ru-ru Lorem impsum
yield return "Лорем ипсум долор сит амет, хас тале феугаит ех, мел дицит сонет сцрипта ид? Еррорибус темпорибус адверсариум про те, видит ностер хас не, яуод феугаит цу ест. Но дицунт рецусабо диссентиас цум, оптион евертитур ан вих. Но мел антиопам молестиае, продессет абхорреант витуператорибус ат сит"u8.ToArray();
// 286 bytes, ru-ru Lorem impsum
yield return "Лорем ипсум долор сит амет, хас тале феугаит ех, мел дицит сонет сцрипта ид? Еррорибус темпорибус адверсариум про те, видит ностер хас не, яуод феугаит цу ест."u8.ToArray();
// 136 bytes, ru-ru Lorem impsum
yield return "Лорем ипсум долор сит амет, хас тале феугаит ех, мел дицит сонет сцрипта ид?"u8.ToArray();
}
[Benchmark]
[ArgumentsSource(nameof(TestData))]
public bool Count(byte[] str) => Utf8.IsValid(str);
} |
Benchmark results on Arm64
|
Benchmark results on Arm64
Flame graphs: Main vs PR 🔥 For clean |
Thanks for running this... You see the very clear bottleneck? That's where we compute the continuation bytes... It is not uqsub that is blocking... it is almost surely the next addv. The profiler must get it wrong. |
I have yet another design that you sidestep the performance issues on Neoverse N1: The trick is to do the sum across only once per string (or, once per blocks of ~4kB for long strings). On a Graviton 3 box, I get pretty decent results. I am hoping that whatever Azure is using is about as good as a Graviton 2 (Neoverse N1).
So for the 1kB string, we have a 30% boost and about a 7% gain on the 286 byte string, with the 527 being in-between (20% gain). These are not super impressive but the Neoverse N1 has only two 16-byte SIMD execution units... there is only so much you can do. If you ingest a sizeable JSON file (a few 100s of kilobytes), the results are more impressive:
So a 35% boost. These results are not as impressive as on x64 with powerful SIMD capabilities (i.e., AVX-512) or on Apple Silicon, but they are not bad if they pan out in your tests. It is possible that there are methodological issues and testing is definitively required, but the hotspot should be different with this new code. Try and try again until you succeed. :-) |
Graphical representation of my results on a Graviton 2 with PR simdutf/SimdUnicode#50
|
@EgorBot -arm64 using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Text.Unicode;
BenchmarkRunner.Run<Utf8Bench>(args: args);
public class Utf8Bench
{
public static IEnumerable<byte[]> TestData()
{
// 1069 bytes, ru-ru Lorem impsum
yield return "Лорем ипсум долор сит амет, хас тале феугаит ех, мел дицит сонет сцрипта ид? Еррорибус темпорибус адверсариум про те, видит ностер хас не, яуод феугаит цу ест. Но дицунт рецусабо диссентиас цум, оптион евертитур ан вих. Но мел антиопам молестиае, продессет абхорреант витуператорибус ат сит, дицант глориатур персецути при еу. При еяуидем пхаедрум рецусабо ех, не вим ерант вертерем Ехерци семпер те нец. Ид нолуиссе детерруиссет нам, яуо ан адхуц дицит пертинациа, мел тота цлита цомпрехенсам ид? Ид аугуе граецис еффициенди вис, ат анимал фиерент инструцтиор пер, не виде еффициенди при!"u8.ToArray();
// 527 bytes, ru-ru Lorem impsum
yield return "Лорем ипсум долор сит амет, хас тале феугаит ех, мел дицит сонет сцрипта ид? Еррорибус темпорибус адверсариум про те, видит ностер хас не, яуод феугаит цу ест. Но дицунт рецусабо диссентиас цум, оптион евертитур ан вих. Но мел антиопам молестиае, продессет абхорреант витуператорибус ат сит"u8.ToArray();
// 286 bytes, ru-ru Lorem impsum
yield return "Лорем ипсум долор сит амет, хас тале феугаит ех, мел дицит сонет сцрипта ид? Еррорибус темпорибус адверсариум про те, видит ностер хас не, яуод феугаит цу ест."u8.ToArray();
// 136 bytes, ru-ru Lorem impsum
yield return "Лорем ипсум долор сит амет, хас тале феугаит ех, мел дицит сонет сцрипта ид?"u8.ToArray();
}
[Benchmark]
[ArgumentsSource(nameof(TestData))]
public bool Count(byte[] str) => Utf8.IsValid(str);
} |
Benchmark results on Arm64
|
@EgorBo Your numbers broadly agrees with my tests this time. ❤️ I submit to you that tuning, such as insuring that GetPointerToFirstInvalidByteArm64 gets inlined, could improve further the results. :-) |
Just a CI test for #103781 without changes