Skip to content

Commit caa35ff

Browse files
committed
update
1 parent 5586fb0 commit caa35ff

File tree

3 files changed

+62
-21
lines changed

3 files changed

+62
-21
lines changed

README.md

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,27 @@ fully reproducible.
3737
| Intel Ice Lake (2.0 GHz) | 6.5 | 3.4 | 1.9 x |
3838
| AMD EPYC 7R32 (Zen 2, 2.8 GHz) | 6.8 | 2.9 | 2.3 x |
3939

40+
## Results (SimdBase64 vs. string .NET functions)
4041

41-
As an aside, there is no accelerated base64 functions for UTF-16 inputs (e.g., `string` types).
42-
We can multiply the decoding speed compared to the .NET standard library (`Convert.FromBase64String(mystring)`),
43-
but we omit the numbers for simplicity.
42+
The .NET runtime did not accelerate the `Convert.FromBase64String(mystring)` functions.
43+
We can multiply the decoding speed compared to the .NET standard library.
44+
45+
Replace the following code based on the standard library...
46+
47+
```C#
48+
byte[] newBytes = Convert.FromBase64String(s);
49+
```
50+
51+
with our version...
52+
53+
```C#
54+
byte[] newBytes = SimdBase64.Base64.FromBase64String(s);
55+
```
56+
57+
| processor and base freq. | SimdBase64 (GB/s) | .NET speed (GB/s) | speed up |
58+
|:----------------|:------------------------|:-------------------|:-------------------|
59+
| Apple M2 processor (ARM, 3.5 Ghz) | 4.0 | 1.1 | 3.6 x |
60+
| Intel Ice Lake (2.0 GHz) | 6.5 | 3.4 | 1.9 x |
4461

4562
## AVX-512
4663

@@ -110,6 +127,14 @@ cd benchmark
110127
sudo dotnet run -c Release
111128
```
112129

130+
For UTF-16 benchmarks, you need to pass a flag as they are not enabled by default:
131+
132+
```
133+
cd benchmark
134+
dotnet run -c Release --anyCategories UTF16
135+
```
136+
137+
113138
## Building the library
114139

115140
```

benchmark/Benchmark.cs

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ public Config()
135135
{
136136
AddColumn(new DataVolume());
137137
AddColumn(new Speed());
138+
/*
138139
if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
139140
{
140141
if (!warned)
@@ -203,6 +204,7 @@ public Config()
203204
{
204205
AddFilter(new AnyCategoriesFilter(["scalar", "runtime"]));
205206
}
207+
*/
206208

207209
}
208210
}
@@ -365,7 +367,7 @@ public unsafe void RunSSEDecodingBenchmarkWithAllocUTF8(string[] data, int[] len
365367
for (int i = 0; i < FileContent.Length; i++)
366368
{
367369
Span<byte> base64 = input[i].AsSpan();
368-
byte[] dataoutput = new byte[SimdBase64.Scalar.Base64.MaximalBinaryLengthFromBase64Scalar<byte>(base64)];
370+
byte[] dataoutput = new byte[SimdBase64.Base64.MaximalBinaryLengthFromBase64<byte>(base64)];
369371
int bytesConsumed = 0;
370372
int bytesWritten = 0;
371373
SimdBase64.SSE.Base64.DecodeFromBase64SSE(base64, dataoutput, out bytesConsumed, out bytesWritten, false);
@@ -384,7 +386,7 @@ public unsafe void RunSSEDecodingBenchmarkWithAllocUTF16(string[] data, int[] le
384386
{
385387
string s = FileContent[i];
386388
Span<char> base64 = input16[i].AsSpan();
387-
byte[] dataoutput = new byte[SimdBase64.Scalar.Base64.MaximalBinaryLengthFromBase64Scalar<char>(base64)];
389+
byte[] dataoutput = new byte[SimdBase64.Base64.MaximalBinaryLengthFromBase64<char>(base64)];
388390
int bytesConsumed = 0;
389391
int bytesWritten = 0;
390392
SimdBase64.SSE.Base64.DecodeFromBase64SSE(base64, dataoutput, out bytesConsumed, out bytesWritten, false);
@@ -476,7 +478,7 @@ public unsafe void RunAVX2DecodingBenchmarkWithAllocUTF8(string[] data, int[] le
476478
for (int i = 0; i < FileContent.Length; i++)
477479
{
478480
Span<byte> base64 = input[i].AsSpan();
479-
byte[] dataoutput = new byte[SimdBase64.Scalar.Base64.MaximalBinaryLengthFromBase64Scalar<byte>(base64)];
481+
byte[] dataoutput = new byte[SimdBase64.Base64.MaximalBinaryLengthFromBase64<byte>(base64)];
480482
int bytesConsumed = 0;
481483
int bytesWritten = 0;
482484
SimdBase64.AVX2.Base64.DecodeFromBase64AVX2(base64, dataoutput, out bytesConsumed, out bytesWritten, false);
@@ -495,7 +497,7 @@ public unsafe void RunAVX2DecodingBenchmarkWithAllocUTF16(string[] data, int[] l
495497
{
496498
string s = FileContent[i];
497499
Span<char> base64 = input16[i].AsSpan();
498-
byte[] dataoutput = new byte[SimdBase64.Scalar.Base64.MaximalBinaryLengthFromBase64Scalar<char>(base64)];
500+
byte[] dataoutput = new byte[SimdBase64.Base64.MaximalBinaryLengthFromBase64<char>(base64)];
499501
int bytesConsumed = 0;
500502
int bytesWritten = 0;
501503
SimdBase64.AVX2.Base64.DecodeFromBase64AVX2(base64, dataoutput, out bytesConsumed, out bytesWritten, false);
@@ -514,7 +516,7 @@ public unsafe void RunOurDecodingBenchmarkWithAllocUTF8(string[] data, int[] len
514516
for (int i = 0; i < FileContent.Length; i++)
515517
{
516518
Span<byte> base64 = input[i].AsSpan();
517-
byte[] dataoutput = new byte[SimdBase64.Scalar.Base64.MaximalBinaryLengthFromBase64Scalar<byte>(base64)];
519+
byte[] dataoutput = new byte[SimdBase64.Base64.MaximalBinaryLengthFromBase64<byte>(base64)];
518520
int bytesConsumed = 0;
519521
int bytesWritten = 0;
520522
SimdBase64.Base64.DecodeFromBase64(base64, dataoutput, out bytesConsumed, out bytesWritten, false);
@@ -532,14 +534,11 @@ public unsafe void RunOurDecodingBenchmarkWithAllocUTF16(string[] data, int[] le
532534
for (int i = 0; i < FileContent.Length; i++)
533535
{
534536
string s = FileContent[i];
535-
Span<char> base64 = input16[i].AsSpan();
536-
byte[] dataoutput = new byte[SimdBase64.Scalar.Base64.MaximalBinaryLengthFromBase64Scalar<char>(base64)];
537-
int bytesConsumed = 0;
538-
int bytesWritten = 0;
539-
SimdBase64.Base64.DecodeFromBase64(base64, dataoutput, out bytesConsumed, out bytesWritten, false);
540-
if (bytesWritten != lengths[i])
537+
byte[] dataoutput = SimdBase64.Base64.FromBase64String(s);
538+
539+
if (dataoutput.Length != lengths[i])
541540
{
542-
Console.WriteLine($"Error: {bytesWritten} != {lengths[i]}");
541+
Console.WriteLine($"Error: {dataoutput.Length } != {lengths[i]}");
543542
#pragma warning disable CA2201
544543
throw new Exception("Error");
545544
}
@@ -589,7 +588,7 @@ public unsafe void RunARMDecodingBenchmarkWithAllocUTF8(string[] data, int[] len
589588
for (int i = 0; i < FileContent.Length; i++)
590589
{
591590
Span<byte> base64 = input[i].AsSpan();
592-
byte[] dataoutput = new byte[SimdBase64.Scalar.Base64.MaximalBinaryLengthFromBase64Scalar<byte>(base64)];
591+
byte[] dataoutput = new byte[SimdBase64.Base64.MaximalBinaryLengthFromBase64<byte>(base64)];
593592
int bytesConsumed = 0;
594593
int bytesWritten = 0;
595594
SimdBase64.Arm.Base64.DecodeFromBase64ARM(base64, dataoutput, out bytesConsumed, out bytesWritten, false);
@@ -608,7 +607,7 @@ public unsafe void RunARMDecodingBenchmarkWithAllocUTF16(string[] data, int[] le
608607
{
609608
string s = FileContent[i];
610609
Span<char> base64 = input16[i].AsSpan();
611-
byte[] dataoutput = new byte[SimdBase64.Scalar.Base64.MaximalBinaryLengthFromBase64Scalar<char>(base64)];
610+
byte[] dataoutput = new byte[SimdBase64.Base64.MaximalBinaryLengthFromBase64<char>(base64)];
612611
int bytesConsumed = 0;
613612
int bytesWritten = 0;
614613
SimdBase64.Arm.Base64.DecodeFromBase64ARM(base64, dataoutput, out bytesConsumed, out bytesWritten, false);
@@ -678,6 +677,8 @@ public unsafe void DotnetRuntimeSIMDBase64RealDataWithAllocUTF8()
678677
RunRuntimeSIMDDecodingBenchmarkWithAllocUTF8(FileContent, DecodedLengths);
679678
}
680679

680+
[Benchmark]
681+
[BenchmarkCategory("UTF16")]
681682
public unsafe void DotnetRuntimeBase64RealDataUTF16()
682683
{
683684
RunRuntimeDecodingBenchmarkUTF16(FileContent, DecodedLengths);
@@ -699,12 +700,19 @@ public unsafe void AVX2DecodingRealDataUTF8()
699700
}
700701

701702
[Benchmark]
702-
[BenchmarkCategory("default", "runtime")]
703+
[BenchmarkCategory("default")]
703704
public unsafe void SimdBase64DecodingRealDataUTF8()
704705
{
705706
RunOurDecodingBenchmarkUTF8(FileContent, DecodedLengths);
706707
}
707708

709+
[Benchmark]
710+
[BenchmarkCategory("UTF16")]
711+
public unsafe void SimdBase64DecodingRealDataWithAllocUTF16()
712+
{
713+
RunOurDecodingBenchmarkWithAllocUTF16(FileContent, DecodedLengths);
714+
}
715+
708716
public unsafe void AVX2DecodingRealDataWithAllocUTF8()
709717
{
710718
RunAVX2DecodingBenchmarkWithAllocUTF8(FileContent, DecodedLengths);
@@ -720,13 +728,11 @@ public unsafe void ARMDecodingRealDataWithAllocUTF8()
720728
RunARMDecodingBenchmarkWithAllocUTF8(FileContent, DecodedLengths);
721729
}
722730

723-
724731
public unsafe void ARMDecodingRealDataUTF16()
725732
{
726733
RunARMDecodingBenchmarkUTF16(FileContent, DecodedLengths);
727734
}
728735

729-
730736
public unsafe void SSEDecodingRealDataUTF16()
731737
{
732738
RunSSEDecodingBenchmarkUTF16(FileContent, DecodedLengths);
@@ -755,7 +761,7 @@ static void Main(string[] args)
755761
{
756762
if (args.Length == 0)
757763
{
758-
args = new string[] { "--filter", "*" };
764+
args = new string[] { "--filter", "*", "--anyCategories", "default" };
759765
}
760766
var job = Job.Default
761767
.WithWarmupCount(1)

src/Base64.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@ public static int MaximalBinaryLengthFromBase64<T>(ReadOnlySpan<T> input)
1414
{
1515
return Scalar.Base64.MaximalBinaryLengthFromBase64Scalar(input);
1616
}
17+
public static byte[] FromBase64String(string s) {
18+
ReadOnlySpan<char> base64 = s.AsSpan();
19+
byte[] newBytes = new byte[SimdBase64.Base64.MaximalBinaryLengthFromBase64<char>(base64)];
20+
int bytesConsumed = 0;
21+
int bytesWritten = 0;
22+
SimdBase64.Base64.DecodeFromBase64(base64, newBytes, out bytesConsumed, out bytesWritten, false);
23+
Array.Resize(ref newBytes, bytesWritten);
24+
return newBytes;
25+
}
26+
1727
public unsafe static OperationStatus DecodeFromBase64(ReadOnlySpan<byte> source, Span<byte> dest, out int bytesConsumed, out int bytesWritten, bool isUrl = false)
1828
{
1929

0 commit comments

Comments
 (0)