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

Optimize HttpUtility.UrlEncodeToBytes for (string, Encoding) overload. #102805

Merged

Conversation

TrayanZapryanov
Copy link
Contributor

Do not allocate byte[] if Encoding.GetMaxByteCount for the string parameter is less than 512 characters.
In this case just encode in stackalloc array.

@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label May 29, 2024
Copy link
Contributor

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

@TrayanZapryanov
Copy link
Contributor Author

Benchmark:

[MemoryDiagnoser]
public class HttpUtilityBenchmarks
{
	[Benchmark(Baseline = true)]
	public byte[] UrlEncodeToBytes() => HttpUtility.UrlEncodeToBytes("http://127.0.0.1:8080/app%%Dir/page.aspx?foo=b%%r");

	[Benchmark]
	public byte[] UrlEncodeToBytes_PR() => MyHttpUtility.UrlEncodeToBytes("http://127.0.0.1:8080/app%%Dir/page.aspx?foo=b%%r");
}

Result:

Method Mean Error StdDev Ratio Gen0 Allocated Alloc Ratio
UrlEncodeToBytes 212.1 ns 1.93 ns 1.61 ns 1.00 0.0219 184 B 1.00
UrlEncodeToBytes_PR 206.4 ns 1.68 ns 1.58 ns 0.97 0.0124 104 B 0.57

@EgorBo
Copy link
Member

EgorBo commented May 29, 2024

Please, ignore me, I am just testing my bot 🙂

@EgorBot -arm64 -amd -profiler

using System.Web;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkRunner.Run<HttpUtilityBenchmarks>(args: args);

public class HttpUtilityBenchmarks
{
    [Benchmark(Baseline = true)]
    public byte[] UrlEncodeToBytes() => 
        HttpUtility.UrlEncodeToBytes("http://127.0.0.1:8080/app%%Dir/page.aspx?foo=b%%r");
}

@EgorBot
Copy link

EgorBot commented May 29, 2024

BenchmarkDotNet v0.13.12, Ubuntu 22.04.4 LTS (Jammy Jellyfish)
AMD EPYC 7763, 1 CPU, 16 logical and 8 physical cores
  Job-DKBAVF : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
  Job-OUKBYD : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
Method Toolchain Mean Error Ratio
UrlEncodeToBytes Main 349.5 ns 4.33 ns 1.00
UrlEncodeToBytes PR 294.4 ns 4.77 ns 0.84

BDN_Artifacts.zip

Profiler🔥

Flame graphs: Main vs PR 🔥
Hot asm: Main vs PR
Hot functions: Main vs PR

For clean perf results, make sure you have just one [Benchmark] in your app.

@EgorBot
Copy link

EgorBot commented May 29, 2024

BenchmarkDotNet v0.13.12, Ubuntu 22.04.4 LTS (Jammy Jellyfish)
Unknown processor
  Job-QWEGHO : .NET 9.0.0 (42.42.42.42424), Arm64 RyuJIT AdvSIMD
  Job-AUNLAU : .NET 9.0.0 (42.42.42.42424), Arm64 RyuJIT AdvSIMD
Method Toolchain Mean Error Ratio
UrlEncodeToBytes Main 411.6 ns 2.59 ns 1.00
UrlEncodeToBytes PR 358.4 ns 0.62 ns 0.87

BDN_Artifacts.zip

Profiler🔥

Flame graphs: Main vs PR 🔥
Hot asm: Main vs PR
Hot functions: Main vs PR

For clean perf results, make sure you have just one [Benchmark] in your app.

@TrayanZapryanov TrayanZapryanov force-pushed the optimize_httputility_urlencodetobytes branch from d550e79 to da876e9 Compare May 31, 2024 08:16
@TrayanZapryanov
Copy link
Contributor Author

Final benchmark results:

Method Mean Error StdDev Ratio Gen0 Allocated Alloc Ratio
UrlEncodeToBytes 209.05 ns 1.128 ns 0.942 ns 1.00 0.0219 184 B 1.00
UrlEncodeToBytes_PR 86.31 ns 1.668 ns 1.638 ns 0.41 0.0124 104 B 0.57

@TrayanZapryanov
Copy link
Contributor Author

@EgorBot -arm64 -amd -profiler

@EgorBot
Copy link

EgorBot commented May 31, 2024

EgorBot manual
Usage: @EgorBot [-%target%] [-profiler] [raw args for BDN] `C# snippet surrounded with triple ticks`
-%target%:       Can be -arm64, -amd or -intel. Or multiple at once, e.g. '-amd -intel'
                 -intel is used when none of the targets are specified.
-profiler:       Use 'perf record' to collect a flamegraph/hot asm - shouldn't be used 
                 when the given benchmark snippet contains more than one [Benchmark]
                 Disabled by default.
-mono:           Use Mono runtime instead of CoreCLR for all targets. Should be possible to use
                 Mono interp too (LLVM is not supported yet).
                 Mono doesn't support -profiler (at least JIT)
                 To use mono-interp, use BDN args, e.g. --envvars MONO_ENV_OPTIONS:"--interpreter"
                 Disabled by default.
-[args for BDN]: Args directly passed to BDN e.g. '--disasm', see
                 https://github.com/dotnet/BenchmarkDotNet/blob/master/docs/articles/guides/console-args.md

All targets are Linux-only at the moment.
NOTE: BenchmarkRunner.Run or BenchmarkSwitcher.From* can be omitted (snippet without an entrypoint)
Although, if they're presented then Program's args must be be forwarded to Run(args: args)

NOTE: [DisassemblyDiagnoser] may cause unexpected crashes in BDN on Linux (at least on x64)

Usage example: link

@TrayanZapryanov
Copy link
Contributor Author

@EgorBot -arm64 -amd

@EgorBot
Copy link

EgorBot commented May 31, 2024

EgorBot manual
Usage: @EgorBot [-%target%] [-profiler] [raw args for BDN] `C# snippet surrounded with triple ticks`
-%target%:       Can be -arm64, -amd or -intel. Or multiple at once, e.g. '-amd -intel'
                 -intel is used when none of the targets are specified.
-profiler:       Use 'perf record' to collect a flamegraph/hot asm - shouldn't be used 
                 when the given benchmark snippet contains more than one [Benchmark]
                 Disabled by default.
-mono:           Use Mono runtime instead of CoreCLR for all targets. Should be possible to use
                 Mono interp too (LLVM is not supported yet).
                 Mono doesn't support -profiler (at least JIT)
                 To use mono-interp, use BDN args, e.g. --envvars MONO_ENV_OPTIONS:"--interpreter"
                 Disabled by default.
-[args for BDN]: Args directly passed to BDN e.g. '--disasm', see
                 https://github.com/dotnet/BenchmarkDotNet/blob/master/docs/articles/guides/console-args.md

All targets are Linux-only at the moment.
NOTE: BenchmarkRunner.Run or BenchmarkSwitcher.From* can be omitted (snippet without an entrypoint)
Although, if they're presented then Program's args must be be forwarded to Run(args: args)

NOTE: [DisassemblyDiagnoser] may cause unexpected crashes in BDN on Linux (at least on x64)

Usage example: link

@TrayanZapryanov
Copy link
Contributor Author

@EgorBot -arm64 -amd -profiler

using System.Web;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkRunner.Run(args: args);

public class HttpUtilityBenchmarks
{
[Benchmark(Baseline = true)]
public byte[] UrlEncodeToBytes() =>
HttpUtility.UrlEncodeToBytes("http://127.0.0.1:8080/app%%Dir/page.aspx?foo=b%%r");
}

@EgorBot
Copy link

EgorBot commented May 31, 2024

EgorBot manual
Usage: @EgorBot [-%target%] [-profiler] [raw args for BDN] `C# snippet surrounded with triple ticks`
-%target%:       Can be -arm64, -amd or -intel. Or multiple at once, e.g. '-amd -intel'
                 -intel is used when none of the targets are specified.
-profiler:       Use 'perf record' to collect a flamegraph/hot asm - shouldn't be used 
                 when the given benchmark snippet contains more than one [Benchmark]
                 Disabled by default.
-mono:           Use Mono runtime instead of CoreCLR for all targets. Should be possible to use
                 Mono interp too (LLVM is not supported yet).
                 Mono doesn't support -profiler (at least JIT)
                 To use mono-interp, use BDN args, e.g. --envvars MONO_ENV_OPTIONS:"--interpreter"
                 Disabled by default.
-[args for BDN]: Args directly passed to BDN e.g. '--disasm', see
                 https://github.com/dotnet/BenchmarkDotNet/blob/master/docs/articles/guides/console-args.md

All targets are Linux-only at the moment.
NOTE: BenchmarkRunner.Run or BenchmarkSwitcher.From* can be omitted (snippet without an entrypoint)
Although, if they're presented then Program's args must be be forwarded to Run(args: args)

NOTE: [DisassemblyDiagnoser] may cause unexpected crashes in BDN on Linux (at least on x64)

Usage example: link

@TrayanZapryanov
Copy link
Contributor Author

@EgorBot -arm64 -amd -profiler

using System.Web;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkRunner.Run<HttpUtilityBenchmarks>(args: args);

public class HttpUtilityBenchmarks
{
    [Benchmark(Baseline = true)]
    public byte[] UrlEncodeToBytes() => 
        HttpUtility.UrlEncodeToBytes("http://127.0.0.1:8080/app%%Dir/page.aspx?foo=b%%r");
}

@EgorBot
Copy link

EgorBot commented May 31, 2024

Benchmark results on Amd
BenchmarkDotNet v0.13.12, Ubuntu 22.04.4 LTS (Jammy Jellyfish)
AMD EPYC 7763, 1 CPU, 8 logical and 4 physical cores
  Job-CUYSUK : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
  Job-JSZBQA : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
Method Toolchain Mean Error Ratio
UrlEncodeToBytes Main 340.1 ns 1.08 ns 1.00
UrlEncodeToBytes PR 132.4 ns 0.41 ns 0.39

BDN_Artifacts.zip

Flame graphs: Main vs PR 🔥
Hot asm: Main vs PR
Hot functions: Main vs PR

For clean perf results, make sure you have just one [Benchmark] in your app.

@EgorBot
Copy link

EgorBot commented May 31, 2024

Benchmark results on Arm64
BenchmarkDotNet v0.13.12, Ubuntu 22.04.4 LTS (Jammy Jellyfish)
Unknown processor
  Job-VMBDUG : .NET 9.0.0 (42.42.42.42424), Arm64 RyuJIT AdvSIMD
  Job-YJLOWM : .NET 9.0.0 (42.42.42.42424), Arm64 RyuJIT AdvSIMD
Method Toolchain Mean Error Ratio
UrlEncodeToBytes Main 415.5 ns 0.60 ns 1.00
UrlEncodeToBytes PR 218.0 ns 0.41 ns 0.52

BDN_Artifacts.zip

Flame graphs: Main vs PR 🔥
Hot asm: Main vs PR
Hot functions: Main vs PR

For clean perf results, make sure you have just one [Benchmark] in your app.

Copy link
Member

@MihaZupan MihaZupan left a comment

Choose a reason for hiding this comment

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

Thanks

@MihaZupan MihaZupan added this to the 9.0.0 milestone May 31, 2024
@MihaZupan MihaZupan merged commit e730d83 into dotnet:main Jun 3, 2024
83 checks passed
@TrayanZapryanov TrayanZapryanov deleted the optimize_httputility_urlencodetobytes branch June 4, 2024 11:38
@github-actions github-actions bot locked and limited conversation to collaborators Jul 5, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Net 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