Perf: ReasonPhrases as FrozenDictionary instead of Dictionary
ReasonPhrases as FrozenDictionary instead of Dictionary
- [x] You've read the Contributor Guide and Code of Conduct.
- [x] You've included unit or integration tests for your change, where applicable.
- [x] You've included inline docs for your change, where applicable.
- [ ] There's an open issue for the PR that you are making. If you'd like to propose a new feature or change, please open an issue to discuss the change or find an existing issue.
Summary of the changes (Less than 80 chars) Perf improvement ReasonPhrase
Description
Replaced the Dictionary based implementation for a FrozenDictionary.
Benchmarks show an overall speed-improvement:
BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.3737/23H2/2023Update/SunValley3)
AMD Ryzen 5 2600X, 1 CPU, 12 logical and 6 physical cores
.NET SDK 9.0.100-preview.5.24307.3
[Host] : .NET 9.0.0 (9.0.24.30607), X64 RyuJIT AVX2
DefaultJob : .NET 9.0.0 (9.0.24.30607), X64 RyuJIT AVX2
UnchangedBenchmarks
| Method | Mean | Error | StdDev | Ratio |
|---|---|---|---|---|
| Reasoning_Dict | 43.80 ns | 0.328 ns | 0.307 ns | 1.00 |
| Reasoning_FrDict | 25.27 ns | 0.290 ns | 0.271 ns | 0.58 |
| Reasoning_Switch | 35.28 ns | 0.425 ns | 0.377 ns | 0.81 |
UnpreparedBenchmarks
| Method | Mean | Error | StdDev | Ratio | RatioSD |
|---|---|---|---|---|---|
| Default | 152.8 ns | 1.32 ns | 1.17 ns | 1.00 | 0.00 |
| FrozenDictionary | 135.6 ns | 1.29 ns | 1.01 ns | 0.89 | 0.01 |
| Switch | 213.1 ns | 2.11 ns | 1.76 ns | 1.39 | 0.02 |
PreparedBenchmarks
| Method | Mean | Error | StdDev | Ratio |
|---|---|---|---|---|
| Reasoning_Dict | 41.54 ns | 0.416 ns | 0.369 ns | 1.00 |
| Reasoning_Dict | 41.54 ns | 0.416 ns | 0.369 ns | 1.00 |
| Reasoning_FrDict | 24.87 ns | 0.476 ns | 0.445 ns | 0.60 |
| Reasoning_Switch | 35.04 ns | 0.232 ns | 0.217 ns | 0.84 |
StraightBenchmarks
| Method | StatusCode | Mean | Error | StdDev | Ratio | RatioSD |
|---|---|---|---|---|---|---|
| Reasoning_Dict | 0 | 3.4472 ns | 0.0425 ns | 0.0398 ns | 1.00 | 0.00 |
| Reasongin_FrDict | 0 | 1.6600 ns | 0.0498 ns | 0.0441 ns | 0.48 | 0.01 |
| Reasoning_Switch | 0 | 2.3406 ns | 0.0185 ns | 0.0145 ns | 0.68 | 0.01 |
| Reasoning_Dict | 100 | 3.7830 ns | 0.1137 ns | 0.1008 ns | 1.00 | 0.00 |
| Reasongin_FrDict | 100 | 1.8294 ns | 0.0827 ns | 0.0773 ns | 0.49 | 0.03 |
| Reasoning_Switch | 100 | 0.7691 ns | 0.0078 ns | 0.0061 ns | 0.20 | 0.01 |
| Reasoning_Dict | 102 | 3.9100 ns | 0.1110 ns | 0.1038 ns | 1.00 | 0.00 |
| Reasongin_FrDict | 102 | 1.7416 ns | 0.0108 ns | 0.0090 ns | 0.44 | 0.01 |
| Reasoning_Switch | 102 | 0.8137 ns | 0.0354 ns | 0.0331 ns | 0.21 | 0.01 |
| Reasoning_Dict | 200 | 3.7386 ns | 0.0331 ns | 0.0294 ns | 1.00 | 0.00 |
| Reasongin_FrDict | 200 | 1.8359 ns | 0.0803 ns | 0.0789 ns | 0.49 | 0.02 |
| Reasoning_Switch | 200 | 1.5291 ns | 0.0169 ns | 0.0132 ns | 0.41 | 0.00 |
| Reasoning_Dict | 226 | 4.7190 ns | 0.0411 ns | 0.0343 ns | 1.00 | 0.00 |
| Reasongin_FrDict | 226 | 1.7793 ns | 0.0637 ns | 0.0565 ns | 0.38 | 0.01 |
| Reasoning_Switch | 226 | 1.8923 ns | 0.0405 ns | 0.0379 ns | 0.40 | 0.01 |
| Reasoning_Dict | 300 | 3.7972 ns | 0.0397 ns | 0.0332 ns | 1.00 | 0.00 |
| Reasongin_FrDict | 300 | 2.2401 ns | 0.0626 ns | 0.0523 ns | 0.59 | 0.01 |
| Reasoning_Switch | 300 | 0.7895 ns | 0.0053 ns | 0.0047 ns | 0.21 | 0.00 |
| Reasoning_Dict | 308 | 3.8153 ns | 0.0695 ns | 0.0650 ns | 1.00 | 0.00 |
| Reasongin_FrDict | 308 | 1.8161 ns | 0.0852 ns | 0.0797 ns | 0.48 | 0.02 |
| Reasoning_Switch | 308 | 0.7688 ns | 0.0281 ns | 0.0249 ns | 0.20 | 0.01 |
| Reasoning_Dict | 499 | 3.7876 ns | 0.0734 ns | 0.0651 ns | 1.00 | 0.00 |
| Reasongin_FrDict | 499 | 1.7590 ns | 0.0150 ns | 0.0133 ns | 0.46 | 0.01 |
| Reasoning_Switch | 499 | 1.7082 ns | 0.0349 ns | 0.0326 ns | 0.45 | 0.01 |
| Reasoning_Dict | 500 | 3.7359 ns | 0.0368 ns | 0.0287 ns | 1.00 | 0.00 |
| Reasongin_FrDict | 500 | 1.7945 ns | 0.0708 ns | 0.0663 ns | 0.48 | 0.02 |
| Reasoning_Switch | 500 | 1.6812 ns | 0.0292 ns | 0.0244 ns | 0.45 | 0.01 |
| Reasoning_Dict | 511 | 3.8497 ns | 0.1179 ns | 0.1103 ns | 1.00 | 0.00 |
| Reasongin_FrDict | 511 | 1.8036 ns | 0.0737 ns | 0.0689 ns | 0.47 | 0.02 |
| Reasoning_Switch | 511 | 2.0824 ns | 0.0361 ns | 0.0320 ns | 0.54 | 0.02 |
Edit: updated benchmarks to show results according to https://github.com/dotnet/aspnetcore/pull/56304#issuecomment-2183338237
@dotnet-policy-service agree
@WhatzGames can you please show the benchmark code?
If the benchmarks have predictable input, then the CPU's branch predictor will do a good job (i.e. it predicts which branch to take) so that can bias the results.
It would be interesting to see these improvements w/ random status codes too. Maybe instead of the Dictionary a FrozenDictionary should be tried too.
Try a benchmark like the following one, w/ more random inputs. On my machine (x64) the FrozenDictionary is best.
Benchmark code
//#define SIMPLE_BENCH
using System.Collections.Frozen;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
BenchmarkRunner.Run<Bench>();
public class Bench
{
#if SIMPLE_BENCH
public int StatusCode { get; set; } = 200;
[Benchmark(Baseline = true)]
public string Default() => ReasonPhrases_Default.GetReasonPhrase(this.StatusCode);
[Benchmark]
public string FrozenDictionary() => ReasonPhrases_Frozen.GetReasonPhrase(this.StatusCode);
[Benchmark]
public string Switch() => ReasonPhrases_Switch.GetReasonPhrase(this.StatusCode);
#else
private readonly int[] _statusCodes = [0, 100, 102, 200, 226, 300, 308, 499, 500, 511];
[Benchmark(Baseline = true)]
public string Default()
{
Random.Shared.Shuffle(_statusCodes);
string phrase = "";
foreach (int statusCode in _statusCodes)
{
phrase = ReasonPhrases_Default.GetReasonPhrase(statusCode);
}
return phrase;
}
[Benchmark]
public string FrozenDictionary()
{
Random.Shared.Shuffle(_statusCodes);
string phrase = "";
foreach (int statusCode in _statusCodes)
{
phrase = ReasonPhrases_Frozen.GetReasonPhrase(statusCode);
}
return phrase;
}
[Benchmark]
public string Switch()
{
Random.Shared.Shuffle(_statusCodes);
string phrase = "";
foreach (int statusCode in _statusCodes)
{
phrase = ReasonPhrases_Switch.GetReasonPhrase(statusCode);
}
return phrase;
}
#endif
}
public static class ReasonPhrases_Default
{
private static readonly Dictionary<int, string> s_phrases = new()
{
{ 100, "Continue" },
{ 101, "Switching Protocols" },
{ 102, "Processing" },
{ 200, "OK" },
{ 201, "Created" },
{ 202, "Accepted" },
{ 203, "Non-Authoritative Information" },
{ 204, "No Content" },
{ 205, "Reset Content" },
{ 206, "Partial Content" },
{ 207, "Multi-Status" },
{ 208, "Already Reported" },
{ 226, "IM Used" },
{ 300, "Multiple Choices" },
{ 301, "Moved Permanently" },
{ 302, "Found" },
{ 303, "See Other" },
{ 304, "Not Modified" },
{ 305, "Use Proxy" },
{ 306, "Switch Proxy" },
{ 307, "Temporary Redirect" },
{ 308, "Permanent Redirect" },
{ 400, "Bad Request" },
{ 401, "Unauthorized" },
{ 402, "Payment Required" },
{ 403, "Forbidden" },
{ 404, "Not Found" },
{ 405, "Method Not Allowed" },
{ 406, "Not Acceptable" },
{ 407, "Proxy Authentication Required" },
{ 408, "Request Timeout" },
{ 409, "Conflict" },
{ 410, "Gone" },
{ 411, "Length Required" },
{ 412, "Precondition Failed" },
{ 413, "Payload Too Large" },
{ 414, "URI Too Long" },
{ 415, "Unsupported Media Type" },
{ 416, "Range Not Satisfiable" },
{ 417, "Expectation Failed" },
{ 418, "I'm a teapot" },
{ 419, "Authentication Timeout" },
{ 421, "Misdirected Request" },
{ 422, "Unprocessable Entity" },
{ 423, "Locked" },
{ 424, "Failed Dependency" },
{ 426, "Upgrade Required" },
{ 428, "Precondition Required" },
{ 429, "Too Many Requests" },
{ 431, "Request Header Fields Too Large" },
{ 451, "Unavailable For Legal Reasons" },
{ 499, "Client Closed Request" },
{ 500, "Internal Server Error" },
{ 501, "Not Implemented" },
{ 502, "Bad Gateway" },
{ 503, "Service Unavailable" },
{ 504, "Gateway Timeout" },
{ 505, "HTTP Version Not Supported" },
{ 506, "Variant Also Negotiates" },
{ 507, "Insufficient Storage" },
{ 508, "Loop Detected" },
{ 510, "Not Extended" },
{ 511, "Network Authentication Required" },
};
/// <summary>
/// Gets the reason phrase for the specified status code.
/// </summary>
/// <param name="statusCode">The status code.</param>
/// <returns>The reason phrase, or <see cref="string.Empty"/> if the status code is unknown.</returns>
public static string GetReasonPhrase(int statusCode)
{
return s_phrases.TryGetValue(statusCode, out string? phrase) ? phrase : string.Empty;
}
}
public static class ReasonPhrases_Frozen
{
private static readonly FrozenDictionary<int, string> s_phrases = new Dictionary<int, string>()
{
{ 100, "Continue" },
{ 101, "Switching Protocols" },
{ 102, "Processing" },
{ 200, "OK" },
{ 201, "Created" },
{ 202, "Accepted" },
{ 203, "Non-Authoritative Information" },
{ 204, "No Content" },
{ 205, "Reset Content" },
{ 206, "Partial Content" },
{ 207, "Multi-Status" },
{ 208, "Already Reported" },
{ 226, "IM Used" },
{ 300, "Multiple Choices" },
{ 301, "Moved Permanently" },
{ 302, "Found" },
{ 303, "See Other" },
{ 304, "Not Modified" },
{ 305, "Use Proxy" },
{ 306, "Switch Proxy" },
{ 307, "Temporary Redirect" },
{ 308, "Permanent Redirect" },
{ 400, "Bad Request" },
{ 401, "Unauthorized" },
{ 402, "Payment Required" },
{ 403, "Forbidden" },
{ 404, "Not Found" },
{ 405, "Method Not Allowed" },
{ 406, "Not Acceptable" },
{ 407, "Proxy Authentication Required" },
{ 408, "Request Timeout" },
{ 409, "Conflict" },
{ 410, "Gone" },
{ 411, "Length Required" },
{ 412, "Precondition Failed" },
{ 413, "Payload Too Large" },
{ 414, "URI Too Long" },
{ 415, "Unsupported Media Type" },
{ 416, "Range Not Satisfiable" },
{ 417, "Expectation Failed" },
{ 418, "I'm a teapot" },
{ 419, "Authentication Timeout" },
{ 421, "Misdirected Request" },
{ 422, "Unprocessable Entity" },
{ 423, "Locked" },
{ 424, "Failed Dependency" },
{ 426, "Upgrade Required" },
{ 428, "Precondition Required" },
{ 429, "Too Many Requests" },
{ 431, "Request Header Fields Too Large" },
{ 451, "Unavailable For Legal Reasons" },
{ 499, "Client Closed Request" },
{ 500, "Internal Server Error" },
{ 501, "Not Implemented" },
{ 502, "Bad Gateway" },
{ 503, "Service Unavailable" },
{ 504, "Gateway Timeout" },
{ 505, "HTTP Version Not Supported" },
{ 506, "Variant Also Negotiates" },
{ 507, "Insufficient Storage" },
{ 508, "Loop Detected" },
{ 510, "Not Extended" },
{ 511, "Network Authentication Required" },
}
.ToFrozenDictionary();
/// <summary>
/// Gets the reason phrase for the specified status code.
/// </summary>
/// <param name="statusCode">The status code.</param>
/// <returns>The reason phrase, or <see cref="string.Empty"/> if the status code is unknown.</returns>
public static string GetReasonPhrase(int statusCode)
{
return s_phrases.TryGetValue(statusCode, out string? phrase) ? phrase : string.Empty;
}
}
public static class ReasonPhrases_Switch
{
public static string GetReasonPhrase(int statusCode) => statusCode switch
{
// Status Codes listed at http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
100 => "Continue",
101 => "Switching Protocols",
102 => "Processing",
200 => "OK",
201 => "Created",
202 => "Accepted",
203 => "Non-Authoritative Information",
204 => "No Content",
205 => "Reset Content",
206 => "Partial Content",
207 => "Multi-Status",
208 => "Already Reported",
226 => "IM Used",
300 => "Multiple Choices",
301 => "Moved Permanently",
302 => "Found",
303 => "See Other",
304 => "Not Modified",
305 => "Use Proxy",
306 => "Switch Proxy",
307 => "Temporary Redirect",
308 => "Permanent Redirect",
400 => "Bad Request",
401 => "Unauthorized",
402 => "Payment Required",
403 => "Forbidden",
404 => "Not Found",
405 => "Method Not Allowed",
406 => "Not Acceptable",
407 => "Proxy Authentication Required",
408 => "Request Timeout",
409 => "Conflict",
410 => "Gone",
411 => "Length Required",
412 => "Precondition Failed",
413 => "Payload Too Large",
414 => "URI Too Long",
415 => "Unsupported Media Type",
416 => "Range Not Satisfiable",
417 => "Expectation Failed",
418 => "I'm a teapot",
419 => "Authentication Timeout",
421 => "Misdirected Request",
422 => "Unprocessable Entity",
423 => "Locked",
424 => "Failed Dependency",
426 => "Upgrade Required",
428 => "Precondition Required",
429 => "Too Many Requests",
431 => "Request Header Fields Too Large",
451 => "Unavailable For Legal Reasons",
499 => "Client Closed Request",
500 => "Internal Server Error",
501 => "Not Implemented",
502 => "Bad Gateway",
503 => "Service Unavailable",
504 => "Gateway Timeout",
505 => "HTTP Version Not Supported",
506 => "Variant Also Negotiates",
507 => "Insufficient Storage",
508 => "Loop Detected",
510 => "Not Extended",
511 => "Network Authentication Required",
_ => string.Empty
};
}
I concur. After running your Benchmarks, I end up with the same result.
Though I have to say, that I'm not too sure whether running Shuffle inside of the individual Benchmarks might have had a measurable impact.
And after adding my extra Benchmarks I do ask myself in hindsight, whether PreparedBenchmarks and UnchangedBenchmarks were probably just doing the same in the end.
Nevertheless, my results came to the same conclusion.
So if the given context requires it, I'll change the PR to use a FrozenDictionary instead.
Benchmark Code
using System.Collections.Frozen;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run();
//my initial Benchmark
public class StraightBenchmarks{
[Params(0, 100, 102, 200, 226, 300, 308, 499, 500, 511)]
public int StatusCode;
[Benchmark(Baseline = true)]
public string Reasoning_Dict() => ReasonPhrases_Default.GetReasonPhrase(StatusCode);
[Benchmark]
public string Reasongin_FrDict() => ReasonPhrases_Frozen.GetReasonPhrase(StatusCode);
[Benchmark]
public string Reasoning_Switch() => ReasonPhrases_Switch.GetReasonPhrase(StatusCode);
}
public class UnchangedBenchmarks
{
private readonly int[] _statusCodes = [0, 100, 102, 200, 226, 300, 308, 499, 500, 511];
[Benchmark(Baseline = true)]
public string Reasoning_Dict() {
string values = string.Empty;
foreach (var statusCode in _statusCodes)
{
ReasonPhrases_Default.GetReasonPhrase(statusCode);
}
return values;
}
[Benchmark]
public string Reasoning_FrDict() {
string values = string.Empty;
foreach (var statusCode in _statusCodes)
{
ReasonPhrases_Frozen.GetReasonPhrase(statusCode);
}
return values;
}
[Benchmark]
public string Reasoning_Switch() {
string values = string.Empty;
foreach (var statusCode in _statusCodes)
{
ReasonPhrases_Switch.GetReasonPhrase(statusCode);
}
return values;
}
}
public class PreparedBenchmarks{
private readonly int[] _statusCodes = [0, 100, 102, 200, 226, 300, 308, 499, 500, 511];
[GlobalSetup]
public void Setup() => Random.Shared.Shuffle(_statusCodes);
[Benchmark(Baseline = true)]
public string Reasoning_Dict() {
string values = string.Empty;
foreach (var statusCode in _statusCodes)
{
ReasonPhrases_Default.GetReasonPhrase(statusCode);
}
return values;
}
[Benchmark]
public string Reasoning_FrDict() {
string values = string.Empty;
foreach (var statusCode in _statusCodes)
{
ReasonPhrases_Frozen.GetReasonPhrase(statusCode);
}
return values;
}
[Benchmark]
public string Reasoning_Switch() {
string values = string.Empty;
foreach (var statusCode in _statusCodes)
{
ReasonPhrases_Switch.GetReasonPhrase(statusCode);
}
return values;
}
}
public class UnpreparedBenchmarks{
private readonly int[] _statusCodes = [0, 100, 102, 200, 226, 300, 308, 499, 500, 511];
[Benchmark(Baseline = true)]
public string Default()
{
Random.Shared.Shuffle(_statusCodes);
string phrase = "";
foreach (int statusCode in _statusCodes)
{
phrase = ReasonPhrases_Default.GetReasonPhrase(statusCode);
}
return phrase;
}
[Benchmark]
public string FrozenDictionary()
{
Random.Shared.Shuffle(_statusCodes);
string phrase = "";
foreach (int statusCode in _statusCodes)
{
phrase = ReasonPhrases_Frozen.GetReasonPhrase(statusCode);
}
return phrase;
}
[Benchmark]
public string Switch()
{
Random.Shared.Shuffle(_statusCodes);
string phrase = "";
foreach (int statusCode in _statusCodes)
{
phrase = ReasonPhrases_Switch.GetReasonPhrase(statusCode);
}
return phrase;
}
}
public static class ReasonPhrases_Default
{
private static readonly Dictionary<int, string> s_phrases = new()
{
{ 100, "Continue" },
{ 101, "Switching Protocols" },
{ 102, "Processing" },
{ 200, "OK" },
{ 201, "Created" },
{ 202, "Accepted" },
{ 203, "Non-Authoritative Information" },
{ 204, "No Content" },
{ 205, "Reset Content" },
{ 206, "Partial Content" },
{ 207, "Multi-Status" },
{ 208, "Already Reported" },
{ 226, "IM Used" },
{ 300, "Multiple Choices" },
{ 301, "Moved Permanently" },
{ 302, "Found" },
{ 303, "See Other" },
{ 304, "Not Modified" },
{ 305, "Use Proxy" },
{ 306, "Switch Proxy" },
{ 307, "Temporary Redirect" },
{ 308, "Permanent Redirect" },
{ 400, "Bad Request" },
{ 401, "Unauthorized" },
{ 402, "Payment Required" },
{ 403, "Forbidden" },
{ 404, "Not Found" },
{ 405, "Method Not Allowed" },
{ 406, "Not Acceptable" },
{ 407, "Proxy Authentication Required" },
{ 408, "Request Timeout" },
{ 409, "Conflict" },
{ 410, "Gone" },
{ 411, "Length Required" },
{ 412, "Precondition Failed" },
{ 413, "Payload Too Large" },
{ 414, "URI Too Long" },
{ 415, "Unsupported Media Type" },
{ 416, "Range Not Satisfiable" },
{ 417, "Expectation Failed" },
{ 418, "I'm a teapot" },
{ 419, "Authentication Timeout" },
{ 421, "Misdirected Request" },
{ 422, "Unprocessable Entity" },
{ 423, "Locked" },
{ 424, "Failed Dependency" },
{ 426, "Upgrade Required" },
{ 428, "Precondition Required" },
{ 429, "Too Many Requests" },
{ 431, "Request Header Fields Too Large" },
{ 451, "Unavailable For Legal Reasons" },
{ 499, "Client Closed Request" },
{ 500, "Internal Server Error" },
{ 501, "Not Implemented" },
{ 502, "Bad Gateway" },
{ 503, "Service Unavailable" },
{ 504, "Gateway Timeout" },
{ 505, "HTTP Version Not Supported" },
{ 506, "Variant Also Negotiates" },
{ 507, "Insufficient Storage" },
{ 508, "Loop Detected" },
{ 510, "Not Extended" },
{ 511, "Network Authentication Required" },
};
/// <summary>
/// Gets the reason phrase for the specified status code.
/// </summary>
/// <param name="statusCode">The status code.</param>
/// <returns>The reason phrase, or <see cref="string.Empty"/> if the status code is unknown.</returns>
public static string GetReasonPhrase(int statusCode)
{
return s_phrases.TryGetValue(statusCode, out string? phrase) ? phrase : string.Empty;
}
}
public static class ReasonPhrases_Frozen
{
private static readonly FrozenDictionary<int, string> s_phrases = new Dictionary<int, string>()
{
{ 100, "Continue" },
{ 101, "Switching Protocols" },
{ 102, "Processing" },
{ 200, "OK" },
{ 201, "Created" },
{ 202, "Accepted" },
{ 203, "Non-Authoritative Information" },
{ 204, "No Content" },
{ 205, "Reset Content" },
{ 206, "Partial Content" },
{ 207, "Multi-Status" },
{ 208, "Already Reported" },
{ 226, "IM Used" },
{ 300, "Multiple Choices" },
{ 301, "Moved Permanently" },
{ 302, "Found" },
{ 303, "See Other" },
{ 304, "Not Modified" },
{ 305, "Use Proxy" },
{ 306, "Switch Proxy" },
{ 307, "Temporary Redirect" },
{ 308, "Permanent Redirect" },
{ 400, "Bad Request" },
{ 401, "Unauthorized" },
{ 402, "Payment Required" },
{ 403, "Forbidden" },
{ 404, "Not Found" },
{ 405, "Method Not Allowed" },
{ 406, "Not Acceptable" },
{ 407, "Proxy Authentication Required" },
{ 408, "Request Timeout" },
{ 409, "Conflict" },
{ 410, "Gone" },
{ 411, "Length Required" },
{ 412, "Precondition Failed" },
{ 413, "Payload Too Large" },
{ 414, "URI Too Long" },
{ 415, "Unsupported Media Type" },
{ 416, "Range Not Satisfiable" },
{ 417, "Expectation Failed" },
{ 418, "I'm a teapot" },
{ 419, "Authentication Timeout" },
{ 421, "Misdirected Request" },
{ 422, "Unprocessable Entity" },
{ 423, "Locked" },
{ 424, "Failed Dependency" },
{ 426, "Upgrade Required" },
{ 428, "Precondition Required" },
{ 429, "Too Many Requests" },
{ 431, "Request Header Fields Too Large" },
{ 451, "Unavailable For Legal Reasons" },
{ 499, "Client Closed Request" },
{ 500, "Internal Server Error" },
{ 501, "Not Implemented" },
{ 502, "Bad Gateway" },
{ 503, "Service Unavailable" },
{ 504, "Gateway Timeout" },
{ 505, "HTTP Version Not Supported" },
{ 506, "Variant Also Negotiates" },
{ 507, "Insufficient Storage" },
{ 508, "Loop Detected" },
{ 510, "Not Extended" },
{ 511, "Network Authentication Required" },
}
.ToFrozenDictionary();
/// <summary>
/// Gets the reason phrase for the specified status code.
/// </summary>
/// <param name="statusCode">The status code.</param>
/// <returns>The reason phrase, or <see cref="string.Empty"/> if the status code is unknown.</returns>
public static string GetReasonPhrase(int statusCode)
{
return s_phrases.TryGetValue(statusCode, out string? phrase) ? phrase : string.Empty;
}
}
public static class ReasonPhrases_Switch
{
public static string GetReasonPhrase(int statusCode) => statusCode switch
{
// Status Codes listed at http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
100 => "Continue",
101 => "Switching Protocols",
102 => "Processing",
200 => "OK",
201 => "Created",
202 => "Accepted",
203 => "Non-Authoritative Information",
204 => "No Content",
205 => "Reset Content",
206 => "Partial Content",
207 => "Multi-Status",
208 => "Already Reported",
226 => "IM Used",
300 => "Multiple Choices",
301 => "Moved Permanently",
302 => "Found",
303 => "See Other",
304 => "Not Modified",
305 => "Use Proxy",
306 => "Switch Proxy",
307 => "Temporary Redirect",
308 => "Permanent Redirect",
400 => "Bad Request",
401 => "Unauthorized",
402 => "Payment Required",
403 => "Forbidden",
404 => "Not Found",
405 => "Method Not Allowed",
406 => "Not Acceptable",
407 => "Proxy Authentication Required",
408 => "Request Timeout",
409 => "Conflict",
410 => "Gone",
411 => "Length Required",
412 => "Precondition Failed",
413 => "Payload Too Large",
414 => "URI Too Long",
415 => "Unsupported Media Type",
416 => "Range Not Satisfiable",
417 => "Expectation Failed",
418 => "I'm a teapot",
419 => "Authentication Timeout",
421 => "Misdirected Request",
422 => "Unprocessable Entity",
423 => "Locked",
424 => "Failed Dependency",
426 => "Upgrade Required",
428 => "Precondition Required",
429 => "Too Many Requests",
431 => "Request Header Fields Too Large",
451 => "Unavailable For Legal Reasons",
499 => "Client Closed Request",
500 => "Internal Server Error",
501 => "Not Implemented",
502 => "Bad Gateway",
503 => "Service Unavailable",
504 => "Gateway Timeout",
505 => "HTTP Version Not Supported",
506 => "Variant Also Negotiates",
507 => "Insufficient Storage",
508 => "Loop Detected",
510 => "Not Extended",
511 => "Network Authentication Required",
_ => string.Empty
};
}
change the PR to use a FrozenDictionary instead.
Please do so, and thanks for checking the bench-results too.
@gfoidl Thanks for providing guidance here!
@WhatzGames Can you update the benchmarks in the PR description to reflect the latest numbers you are seeing with FrozenDictionary?
Done. Also updated some of the description to reflect this PRs changed approach.
mmmh... I just found a different approach and created a Benchmark for that one. In comparison to the FrozenDictionary it reduces the time even further.
| Method | Mean | Error | StdDev | Ratio | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|
| ReasonPhrases_Array | 12.62 ns | 0.181 ns | 0.161 ns | 0.49 | - | NA |
| ReasonPhrases_FrozenDict | 26.00 ns | 0.336 ns | 0.315 ns | 1.00 | - | NA |
results after adding a Shuffle like @gfoidl used:
| Method | Mean | Error | StdDev | Ratio |
|---|---|---|---|---|
| ReasonPhrases_Array | 133.0 ns | 1.80 ns | 1.60 ns | 0.91 |
| ReasonPhrases_FrozenDict | 146.4 ns | 0.48 ns | 0.45 ns | 1.00 |
Benchmarks
using System.Collections.Frozen;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run();
[MemoryDiagnoser]
public class Benchmark
{
public int[] statusCode = [0, 100, 102, 200, 226, 300, 308, 499, 500, 511];
[Benchmark]
public string? ReasonPhrases_Array()
{
string? value = string.Empty;
foreach (var item in statusCode)
{
value = Reasons_Array.Get(item);
}
return value;
}
[Benchmark(Baseline = true)]
public string? ReasonPhrases_FrozenDict()
{
string? value = string.Empty;
foreach (var item in statusCode)
{
value = Reasons_Frozen.Get(item);
}
return value;
}
}
public class ShuffledBenchmark
{
public int[] statusCode = [0, 100, 102, 200, 226, 300, 308, 499, 500, 511];
[Benchmark]
public string? ReasonPhrases_Array()
{
Random.Shared.Shuffle(statusCode);
string? value = string.Empty;
foreach (var item in statusCode)
{
value = Reasons_Array.Get(item);
}
return value;
}
[Benchmark(Baseline = true)]
public string? ReasonPhrases_FrozenDict()
{
Random.Shared.Shuffle(statusCode);
string? value = string.Empty;
foreach (var item in statusCode)
{
value = Reasons_Frozen.Get(item);
}
return value;
}
}
static class Reasons_Frozen
{
private static readonly FrozenDictionary<int, string?> Reasons = new Dictionary<int, string?>{
{100, "Continue"},
{101, "Switching Protocols"},
{200,"OK"},
{201,"Created"},
{202,"Accepted"},
{203,"Non-Authoritative Information"},
{204,"No Content"},
{205,"Reset Content"},
{206,"Partial Content"},
{207,"Multi-Status"},
{300,"Multiple Choices"},
{301,"Moved Permanently"},
{302,"Found"},
{303,"See Other"},
{304,"Not Modified"},
{305,"Use Proxy"},
{306, null},
{307,"Temporary Redirect"},
{400, "Bad Request"},
{401, "Unauthorized"},
{402, "Payment Required"},
{403, "Forbidden"},
{404, "Not Found"},
{405, "Method Not Allowed"},
{406, "Not Acceptable"},
{407, "Proxy Authentication Required"},
{408, "Request Timeout"},
{409, "Conflict"},
{410, "Gone"},
{411, "Length Required"},
{412, "Precondition Failed"},
{413, "Request Entity Too Large"},
{414, "Request-Uri Too Long"},
{415, "Unsupported Media Type"},
{416, "Requested Range Not Satisfiable"},
{417, "Expectation Failed"},
{418, null},
{419, null},
{420, null},
{421, null},
{422, "Unprocessable Entity"},
{423, "Locked"},
{424, "Failed Dependency"},
{425, null},
{426, "Upgrade Required"},
{500, "Internal Server Error"},
{501, "Not Implemented"},
{502, "Bad Gateway"},
{503, "Service Unavailable"},
{504, "Gateway Timeout"},
{505, "Http Version Not Supported"},
{506, null},
{507, "Insufficient Storage"},
}
.ToFrozenDictionary();
internal static string? Get(int statusCode){
return Reasons.TryGetValue(statusCode, out string? reason) ? reason : null;
}
}
static class Reasons_Array
{
private static readonly string?[]?[] HttpReasonPhrases = new string?[]?[]
{
null,
[
/* 100 */ "Continue",
/* 101 */ "Switching Protocols",
/* 102 */ "Processing"
],
[
/* 200 */ "OK",
/* 201 */ "Created",
/* 202 */ "Accepted",
/* 203 */ "Non-Authoritative Information",
/* 204 */ "No Content",
/* 205 */ "Reset Content",
/* 206 */ "Partial Content",
/* 207 */ "Multi-Status"
],
[
/* 300 */ "Multiple Choices",
/* 301 */ "Moved Permanently",
/* 302 */ "Found",
/* 303 */ "See Other",
/* 304 */ "Not Modified",
/* 305 */ "Use Proxy",
/* 306 */ null,
/* 307 */ "Temporary Redirect"
],
[
/* 400 */ "Bad Request",
/* 401 */ "Unauthorized",
/* 402 */ "Payment Required",
/* 403 */ "Forbidden",
/* 404 */ "Not Found",
/* 405 */ "Method Not Allowed",
/* 406 */ "Not Acceptable",
/* 407 */ "Proxy Authentication Required",
/* 408 */ "Request Timeout",
/* 409 */ "Conflict",
/* 410 */ "Gone",
/* 411 */ "Length Required",
/* 412 */ "Precondition Failed",
/* 413 */ "Request Entity Too Large",
/* 414 */ "Request-Uri Too Long",
/* 415 */ "Unsupported Media Type",
/* 416 */ "Requested Range Not Satisfiable",
/* 417 */ "Expectation Failed",
/* 418 */ null,
/* 419 */ null,
/* 420 */ null,
/* 421 */ null,
/* 422 */ "Unprocessable Entity",
/* 423 */ "Locked",
/* 424 */ "Failed Dependency",
/* 425 */ null,
/* 426 */ "Upgrade Required", // RFC 2817
],
[
/* 500 */ "Internal Server Error",
/* 501 */ "Not Implemented",
/* 502 */ "Bad Gateway",
/* 503 */ "Service Unavailable",
/* 504 */ "Gateway Timeout",
/* 505 */ "Http Version Not Supported",
/* 506 */ null,
/* 507 */ "Insufficient Storage",
]
};
internal static string? Get(int code)
{
if (code >= 100 && code < 600)
{
int i = code / 100;
int j = code % 100;
if (j < HttpReasonPhrases[i]!.Length)
{
return HttpReasonPhrases[i]![j];
}
}
return null;
}
}
As you're chasing performance, I'm curious, does using Math.DivRem() make the array version faster still, rather than doing / and % separately?
Funny enough I had the same idea.
I ran both benchmarks twice and results in the order as before:
First run:
| Method | Mean | Error | StdDev | Ratio | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|
| ReasonPhrases_Array | 12.65 ns | 0.180 ns | 0.160 ns | 0.49 | - | NA |
| ReasonPhrases_FrozenDict | 25.88 ns | 0.325 ns | 0.304 ns | 1.00 | - | NA |
| ReasonPhrases_DivRem | 12.56 ns | 0.116 ns | 0.103 ns | 0.49 | - | NA |
| Method | Mean | Error | StdDev | Ratio |
|---|---|---|---|---|
| ReasonPhrases_Array | 130.0 ns | 0.54 ns | 0.42 ns | 0.90 |
| ReasonPhrases_FrozenDict | 145.1 ns | 0.95 ns | 0.89 ns | 1.00 |
| ReasonPhrases_DivRem | 130.0 ns | 0.67 ns | 0.60 ns | 0.90 |
second run:
| Method | Mean | Error | StdDev | Ratio | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|
| ReasonPhrases_Array | 12.55 ns | 0.208 ns | 0.195 ns | 0.49 | - | NA |
| ReasonPhrases_FrozenDict | 25.57 ns | 0.394 ns | 0.368 ns | 1.00 | - | NA |
| ReasonPhrases_DivRem | 12.38 ns | 0.113 ns | 0.106 ns | 0.48 | - | NA |
| Method | Mean | Error | StdDev | Ratio |
|---|---|---|---|---|
| ReasonPhrases_Array | 130.5 ns | 0.89 ns | 0.79 ns | 0.89 |
| ReasonPhrases_FrozenDict | 146.2 ns | 1.52 ns | 1.27 ns | 1.00 |
| ReasonPhrases_DivRem | 132.9 ns | 1.30 ns | 1.22 ns | 0.91 |
The results are a bit inconclusive to me, so it would be probably be best if the benchmarks were run on a different machine to double check.
But as Shuffle seems to have quite the influence on performance here, i tend to believe that performance does improve when using DivRem.
Benchmarks
using System.Collections.Frozen;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run();
[MemoryDiagnoser]
public class Benchmark
{
public int[] statusCode = [0, 100, 102, 200, 226, 300, 308, 499, 500, 511];
[Benchmark]
public string? ReasonPhrases_Array()
{
string? value = string.Empty;
foreach (var item in statusCode)
{
value = Reasons_Array.Get(item);
}
return value;
}
[Benchmark(Baseline = true)]
public string? ReasonPhrases_FrozenDict()
{
string? value = string.Empty;
foreach (var item in statusCode)
{
value = Reasons_Frozen.Get(item);
}
return value;
}
[Benchmark]
public string? ReasonPhrases_DivRem()
{
string? value = string.Empty;
foreach (var item in statusCode)
{
value = Reasons_DivRem.Get(item);
}
return value;
}
}
public class ShuffledBenchmark
{
public int[] statusCode = [0, 100, 102, 200, 226, 300, 308, 499, 500, 511];
[Benchmark]
public string? ReasonPhrases_Array()
{
Random.Shared.Shuffle(statusCode);
string? value = string.Empty;
foreach (var item in statusCode)
{
value = Reasons_Array.Get(item);
}
return value;
}
[Benchmark(Baseline = true)]
public string? ReasonPhrases_FrozenDict()
{
Random.Shared.Shuffle(statusCode);
string? value = string.Empty;
foreach (var item in statusCode)
{
value = Reasons_Frozen.Get(item);
}
return value;
}
[Benchmark]
public string? ReasonPhrases_DivRem()
{
Random.Shared.Shuffle(statusCode);
string? value = string.Empty;
foreach (var item in statusCode)
{
value = Reasons_DivRem.Get(item);
}
return value;
}
}
static class Reasons_Frozen
{
private static readonly FrozenDictionary<int, string?> Reasons = new Dictionary<int, string?>{
{100, "Continue"},
{101, "Switching Protocols"},
{200,"OK"},
{201,"Created"},
{202,"Accepted"},
{203,"Non-Authoritative Information"},
{204,"No Content"},
{205,"Reset Content"},
{206,"Partial Content"},
{207,"Multi-Status"},
{300,"Multiple Choices"},
{301,"Moved Permanently"},
{302,"Found"},
{303,"See Other"},
{304,"Not Modified"},
{305,"Use Proxy"},
{306, null},
{307,"Temporary Redirect"},
{400, "Bad Request"},
{401, "Unauthorized"},
{402, "Payment Required"},
{403, "Forbidden"},
{404, "Not Found"},
{405, "Method Not Allowed"},
{406, "Not Acceptable"},
{407, "Proxy Authentication Required"},
{408, "Request Timeout"},
{409, "Conflict"},
{410, "Gone"},
{411, "Length Required"},
{412, "Precondition Failed"},
{413, "Request Entity Too Large"},
{414, "Request-Uri Too Long"},
{415, "Unsupported Media Type"},
{416, "Requested Range Not Satisfiable"},
{417, "Expectation Failed"},
{418, null},
{419, null},
{420, null},
{421, null},
{422, "Unprocessable Entity"},
{423, "Locked"},
{424, "Failed Dependency"},
{425, null},
{426, "Upgrade Required"},
{500, "Internal Server Error"},
{501, "Not Implemented"},
{502, "Bad Gateway"},
{503, "Service Unavailable"},
{504, "Gateway Timeout"},
{505, "Http Version Not Supported"},
{506, null},
{507, "Insufficient Storage"},
}
.ToFrozenDictionary();
internal static string? Get(int statusCode){
return Reasons.TryGetValue(statusCode, out string? reason) ? reason : null;
}
}
static class Reasons_Array
{
private static readonly string?[]?[] HttpReasonPhrases = new string?[]?[]
{
null,
[
/* 100 */ "Continue",
/* 101 */ "Switching Protocols",
/* 102 */ "Processing"
],
[
/* 200 */ "OK",
/* 201 */ "Created",
/* 202 */ "Accepted",
/* 203 */ "Non-Authoritative Information",
/* 204 */ "No Content",
/* 205 */ "Reset Content",
/* 206 */ "Partial Content",
/* 207 */ "Multi-Status"
],
[
/* 300 */ "Multiple Choices",
/* 301 */ "Moved Permanently",
/* 302 */ "Found",
/* 303 */ "See Other",
/* 304 */ "Not Modified",
/* 305 */ "Use Proxy",
/* 306 */ null,
/* 307 */ "Temporary Redirect"
],
[
/* 400 */ "Bad Request",
/* 401 */ "Unauthorized",
/* 402 */ "Payment Required",
/* 403 */ "Forbidden",
/* 404 */ "Not Found",
/* 405 */ "Method Not Allowed",
/* 406 */ "Not Acceptable",
/* 407 */ "Proxy Authentication Required",
/* 408 */ "Request Timeout",
/* 409 */ "Conflict",
/* 410 */ "Gone",
/* 411 */ "Length Required",
/* 412 */ "Precondition Failed",
/* 413 */ "Request Entity Too Large",
/* 414 */ "Request-Uri Too Long",
/* 415 */ "Unsupported Media Type",
/* 416 */ "Requested Range Not Satisfiable",
/* 417 */ "Expectation Failed",
/* 418 */ null,
/* 419 */ null,
/* 420 */ null,
/* 421 */ null,
/* 422 */ "Unprocessable Entity",
/* 423 */ "Locked",
/* 424 */ "Failed Dependency",
/* 425 */ null,
/* 426 */ "Upgrade Required", // RFC 2817
],
[
/* 500 */ "Internal Server Error",
/* 501 */ "Not Implemented",
/* 502 */ "Bad Gateway",
/* 503 */ "Service Unavailable",
/* 504 */ "Gateway Timeout",
/* 505 */ "Http Version Not Supported",
/* 506 */ null,
/* 507 */ "Insufficient Storage",
]
};
internal static string? Get(int code)
{
if (code >= 100 && code < 600)
{
int i = code / 100;
int j = code % 100;
if (j < HttpReasonPhrases[i]!.Length)
{
return HttpReasonPhrases[i]![j];
}
}
return null;
}
}
static class Reasons_DivRem
{
private static readonly string?[]?[] HttpReasonPhrases = new string?[]?[]
{
null,
[
/* 100 */ "Continue",
/* 101 */ "Switching Protocols",
/* 102 */ "Processing"
],
[
/* 200 */ "OK",
/* 201 */ "Created",
/* 202 */ "Accepted",
/* 203 */ "Non-Authoritative Information",
/* 204 */ "No Content",
/* 205 */ "Reset Content",
/* 206 */ "Partial Content",
/* 207 */ "Multi-Status"
],
[
/* 300 */ "Multiple Choices",
/* 301 */ "Moved Permanently",
/* 302 */ "Found",
/* 303 */ "See Other",
/* 304 */ "Not Modified",
/* 305 */ "Use Proxy",
/* 306 */ null,
/* 307 */ "Temporary Redirect"
],
[
/* 400 */ "Bad Request",
/* 401 */ "Unauthorized",
/* 402 */ "Payment Required",
/* 403 */ "Forbidden",
/* 404 */ "Not Found",
/* 405 */ "Method Not Allowed",
/* 406 */ "Not Acceptable",
/* 407 */ "Proxy Authentication Required",
/* 408 */ "Request Timeout",
/* 409 */ "Conflict",
/* 410 */ "Gone",
/* 411 */ "Length Required",
/* 412 */ "Precondition Failed",
/* 413 */ "Request Entity Too Large",
/* 414 */ "Request-Uri Too Long",
/* 415 */ "Unsupported Media Type",
/* 416 */ "Requested Range Not Satisfiable",
/* 417 */ "Expectation Failed",
/* 418 */ null,
/* 419 */ null,
/* 420 */ null,
/* 421 */ null,
/* 422 */ "Unprocessable Entity",
/* 423 */ "Locked",
/* 424 */ "Failed Dependency",
/* 425 */ null,
/* 426 */ "Upgrade Required", // RFC 2817
],
[
/* 500 */ "Internal Server Error",
/* 501 */ "Not Implemented",
/* 502 */ "Bad Gateway",
/* 503 */ "Service Unavailable",
/* 504 */ "Gateway Timeout",
/* 505 */ "Http Version Not Supported",
/* 506 */ null,
/* 507 */ "Insufficient Storage",
]
};
internal static string? Get(int code)
{
if (code >= 100 && code < 600)
{
int i = Math.DivRem(code, 100, out int j);
if (j < HttpReasonPhrases[i]!.Length)
{
return HttpReasonPhrases[i]![j];
}
}
return null;
}
}
So I got some more results.
Home-PC:
BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.3737/23H2/2023Update/SunValley3)
AMD Ryzen 5 2600X, 1 CPU, 12 logical and 6 physical cores
.NET SDK 9.0.100-preview.5.24307.3
[Host] : .NET 9.0.0 (9.0.24.30607), X64 RyuJIT AVX2
DefaultJob : .NET 9.0.0 (9.0.24.30607), X64 RyuJIT AVX2
| Method | Mean | Error | StdDev | Ratio | RatioSD |
|---|---|---|---|---|---|
| Reasoning_2DArray | 12.52 ns | 0.180 ns | 0.168 ns | 1.00 | 0.00 |
| Reasoning_2DArrayDivRem | 11.35 ns | 0.255 ns | 0.239 ns | 0.91 | 0.02 |
Codespace-Instance:
BenchmarkDotNet v0.13.12, Ubuntu 20.04.6 LTS (Focal Fossa) (container)
AMD EPYC 7763, 1 CPU, 2 logical cores and 1 physical core
.NET SDK 9.0.100-preview.5.24307.3
[Host] : .NET 9.0.0 (9.0.24.30607), X64 RyuJIT AVX2
DefaultJob : .NET 9.0.0 (9.0.24.30607), X64 RyuJIT AVX2
| Method | Mean | Error | StdDev | Median | Ratio | RatioSD |
|---|---|---|---|---|---|---|
| Reasoning_2DArray | 13.78 ns | 0.379 ns | 1.106 ns | 13.30 ns | 1.00 | 0.00 |
| Reasoning_2DArrayDivRem | 13.08 ns | 0.336 ns | 0.898 ns | 12.80 ns | 0.96 | 0.10 |
Work-Machine:
BenchmarkDotNet v0.13.12, Windows 10 (10.0.19045.4529/22H2/2022Update)
11th Gen Intel Core i7-1165G7 2.80GHz, 1 CPU, 8 logical and 4 physical cores
.NET SDK 8.0.302
[Host] : .NET 8.0.6 (8.0.624.26715), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
DefaultJob : .NET 8.0.6 (8.0.624.26715), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
| Method | Mean | Error | StdDev | Median | Ratio | RatioSD |
|---|---|---|---|---|---|---|
| Reasoning_2DArray | 12.50 ns | 0.313 ns | 0.779 ns | 12.22 ns | 1.00 | 0.00 |
| Reasoning_2DArrayDivRem | 13.85 ns | 0.319 ns | 0.426 ns | 13.86 ns | 1.09 | 0.08 |
Based on these and my previous results I would tend to use DivRem with the latest approach, if that's alright by you.
Benchmark
using System.Collections.Frozen;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run();
public class PreparedBenchmarks
{
private readonly int[] _statusCodes = [0, 100, 102, 200, 226, 300, 308, 499, 500, 511];
[GlobalSetup]
public void Setup() => Random.Shared.Shuffle(_statusCodes);
[Benchmark(Baseline = true)]
public string Reasoning_2DArray()
{
string values = string.Empty;
foreach (var statusCode in _statusCodes)
{
ReasonPhrases_Array.GetReasonPhrase(statusCode);
}
return values;
}
[Benchmark]
public string Reasoning_2DArrayDivRem()
{
string values = string.Empty;
foreach (var statusCode in _statusCodes)
{
ReasonPhrases_Array_DivRem.GetReasonPhrase(statusCode);
}
return values;
}
}
public static class ReasonPhrases_Array
{
private static readonly string[][] HttpReasonPhrases = [
[],
[
/* 100 */ "Continue",
/* 101 */ "Switching Protocols",
/* 102 */ "Processing"
],
[
/* 200 */ "OK",
/* 201 */ "Created",
/* 202 */ "Accepted",
/* 203 */ "Non-Authoritative Information",
/* 204 */ "No Content",
/* 205 */ "Reset Content",
/* 206 */ "Partial Content",
/* 207 */ "Multi-Status",
/* 208 */ "Already Reported",
/* 209 */ string.Empty,
/* 210 */ string.Empty,
/* 211 */ string.Empty,
/* 212 */ string.Empty,
/* 213 */ string.Empty,
/* 214 */ string.Empty,
/* 215 */ string.Empty,
/* 216 */ string.Empty,
/* 217 */ string.Empty,
/* 218 */ string.Empty,
/* 219 */ string.Empty,
/* 220 */ string.Empty,
/* 221 */ string.Empty,
/* 222 */ string.Empty,
/* 223 */ string.Empty,
/* 224 */ string.Empty,
/* 225 */ string.Empty,
/* 226 */ "IM Used"
],
[
/* 300 */ "Multiple Choices",
/* 301 */ "Moved Permanently",
/* 302 */ "Found",
/* 303 */ "See Other",
/* 304 */ "Not Modified",
/* 305 */ "Use Proxy",
/* 306 */ "Switch Proxy",
/* 307 */ "Temporary Redirect",
/* 308 */ "Permanent Redirect"
],
[
/* 400 */ "Bad Request",
/* 401 */ "Unauthorized",
/* 402 */ "Payment Required",
/* 403 */ "Forbidden",
/* 404 */ "Not Found",
/* 405 */ "Method Not Allowed",
/* 406 */ "Not Acceptable",
/* 407 */ "Proxy Authentication Required",
/* 408 */ "Request Timeout",
/* 409 */ "Conflict",
/* 410 */ "Gone",
/* 411 */ "Length Required",
/* 412 */ "Precondition Failed",
/* 413 */ "Payload Too Large",
/* 414 */ "URI Too Long",
/* 415 */ "Unsupported Media Type",
/* 416 */ "Range Not Satisfiable",
/* 417 */ "Expectation Failed",
/* 418 */ "I'm a teapot",
/* 419 */ "Authentication Timeout",
/* 420 */ string.Empty,
/* 421 */ "Misdirected Request",
/* 422 */ "Unprocessable Entity",
/* 423 */ "Locked",
/* 424 */ "Failed Dependency",
/* 425 */ string.Empty,
/* 426 */ "Upgrade Required",
/* 427 */ string.Empty,
/* 428 */ "Precondition Required",
/* 429 */ "Too Many Requests",
/* 430 */ string.Empty,
/* 431 */ "Request Header Fields Too Large",
/* 432 */ string.Empty,
/* 433 */ string.Empty,
/* 434 */ string.Empty,
/* 435 */ string.Empty,
/* 436 */ string.Empty,
/* 437 */ string.Empty,
/* 438 */ string.Empty,
/* 439 */ string.Empty,
/* 440 */ string.Empty,
/* 441 */ string.Empty,
/* 442 */ string.Empty,
/* 443 */ string.Empty,
/* 444 */ string.Empty,
/* 445 */ string.Empty,
/* 446 */ string.Empty,
/* 447 */ string.Empty,
/* 448 */ string.Empty,
/* 449 */ string.Empty,
/* 450 */ string.Empty,
/* 451 */ "Unavailable For Legal Reasons",
/* 452 */ string.Empty,
/* 453 */ string.Empty,
/* 454 */ string.Empty,
/* 455 */ string.Empty,
/* 456 */ string.Empty,
/* 457 */ string.Empty,
/* 458 */ string.Empty,
/* 459 */ string.Empty,
/* 460 */ string.Empty,
/* 461 */ string.Empty,
/* 462 */ string.Empty,
/* 463 */ string.Empty,
/* 464 */ string.Empty,
/* 465 */ string.Empty,
/* 466 */ string.Empty,
/* 467 */ string.Empty,
/* 468 */ string.Empty,
/* 469 */ string.Empty,
/* 470 */ string.Empty,
/* 471 */ string.Empty,
/* 472 */ string.Empty,
/* 473 */ string.Empty,
/* 474 */ string.Empty,
/* 475 */ string.Empty,
/* 476 */ string.Empty,
/* 477 */ string.Empty,
/* 478 */ string.Empty,
/* 479 */ string.Empty,
/* 480 */ string.Empty,
/* 481 */ string.Empty,
/* 482 */ string.Empty,
/* 483 */ string.Empty,
/* 484 */ string.Empty,
/* 485 */ string.Empty,
/* 486 */ string.Empty,
/* 487 */ string.Empty,
/* 488 */ string.Empty,
/* 489 */ string.Empty,
/* 490 */ string.Empty,
/* 491 */ string.Empty,
/* 492 */ string.Empty,
/* 493 */ string.Empty,
/* 494 */ string.Empty,
/* 495 */ string.Empty,
/* 496 */ string.Empty,
/* 497 */ string.Empty,
/* 498 */ string.Empty,
/* 499 */ "Client Closed Request"
],
[
/* 500 */ "Internal Server Error",
/* 501 */ "Not Implemented",
/* 502 */ "Bad Gateway",
/* 503 */ "Service Unavailable",
/* 504 */ "Gateway Timeout",
/* 505 */ "HTTP Version Not Supported",
/* 506 */ "Variant Also Negotiates",
/* 507 */ "Insufficient Storage",
/* 508 */ "Loop Detected",
/* 509 */ string.Empty,
/* 510 */ "Not Extended",
/* 511 */ "Network Authentication Required"
]
];
public static string GetReasonPhrase(int statusCode)
{
if (statusCode >= 100 && statusCode < 600)
{
int i = statusCode / 100;
int j = statusCode % 100;
if (j < HttpReasonPhrases[i].Length)
{
return HttpReasonPhrases[i][j];
}
}
return string.Empty;
}
}
public static class ReasonPhrases_Array_DivRem
{
private static readonly string[][] HttpReasonPhrases = [
[],
[
/* 100 */ "Continue",
/* 101 */ "Switching Protocols",
/* 102 */ "Processing"
],
[
/* 200 */ "OK",
/* 201 */ "Created",
/* 202 */ "Accepted",
/* 203 */ "Non-Authoritative Information",
/* 204 */ "No Content",
/* 205 */ "Reset Content",
/* 206 */ "Partial Content",
/* 207 */ "Multi-Status",
/* 208 */ "Already Reported",
/* 209 */ string.Empty,
/* 210 */ string.Empty,
/* 211 */ string.Empty,
/* 212 */ string.Empty,
/* 213 */ string.Empty,
/* 214 */ string.Empty,
/* 215 */ string.Empty,
/* 216 */ string.Empty,
/* 217 */ string.Empty,
/* 218 */ string.Empty,
/* 219 */ string.Empty,
/* 220 */ string.Empty,
/* 221 */ string.Empty,
/* 222 */ string.Empty,
/* 223 */ string.Empty,
/* 224 */ string.Empty,
/* 225 */ string.Empty,
/* 226 */ "IM Used"
],
[
/* 300 */ "Multiple Choices",
/* 301 */ "Moved Permanently",
/* 302 */ "Found",
/* 303 */ "See Other",
/* 304 */ "Not Modified",
/* 305 */ "Use Proxy",
/* 306 */ "Switch Proxy",
/* 307 */ "Temporary Redirect",
/* 308 */ "Permanent Redirect"
],
[
/* 400 */ "Bad Request",
/* 401 */ "Unauthorized",
/* 402 */ "Payment Required",
/* 403 */ "Forbidden",
/* 404 */ "Not Found",
/* 405 */ "Method Not Allowed",
/* 406 */ "Not Acceptable",
/* 407 */ "Proxy Authentication Required",
/* 408 */ "Request Timeout",
/* 409 */ "Conflict",
/* 410 */ "Gone",
/* 411 */ "Length Required",
/* 412 */ "Precondition Failed",
/* 413 */ "Payload Too Large",
/* 414 */ "URI Too Long",
/* 415 */ "Unsupported Media Type",
/* 416 */ "Range Not Satisfiable",
/* 417 */ "Expectation Failed",
/* 418 */ "I'm a teapot",
/* 419 */ "Authentication Timeout",
/* 420 */ string.Empty,
/* 421 */ "Misdirected Request",
/* 422 */ "Unprocessable Entity",
/* 423 */ "Locked",
/* 424 */ "Failed Dependency",
/* 425 */ string.Empty,
/* 426 */ "Upgrade Required",
/* 427 */ string.Empty,
/* 428 */ "Precondition Required",
/* 429 */ "Too Many Requests",
/* 430 */ string.Empty,
/* 431 */ "Request Header Fields Too Large",
/* 432 */ string.Empty,
/* 433 */ string.Empty,
/* 434 */ string.Empty,
/* 435 */ string.Empty,
/* 436 */ string.Empty,
/* 437 */ string.Empty,
/* 438 */ string.Empty,
/* 439 */ string.Empty,
/* 440 */ string.Empty,
/* 441 */ string.Empty,
/* 442 */ string.Empty,
/* 443 */ string.Empty,
/* 444 */ string.Empty,
/* 445 */ string.Empty,
/* 446 */ string.Empty,
/* 447 */ string.Empty,
/* 448 */ string.Empty,
/* 449 */ string.Empty,
/* 450 */ string.Empty,
/* 451 */ "Unavailable For Legal Reasons",
/* 452 */ string.Empty,
/* 453 */ string.Empty,
/* 454 */ string.Empty,
/* 455 */ string.Empty,
/* 456 */ string.Empty,
/* 457 */ string.Empty,
/* 458 */ string.Empty,
/* 459 */ string.Empty,
/* 460 */ string.Empty,
/* 461 */ string.Empty,
/* 462 */ string.Empty,
/* 463 */ string.Empty,
/* 464 */ string.Empty,
/* 465 */ string.Empty,
/* 466 */ string.Empty,
/* 467 */ string.Empty,
/* 468 */ string.Empty,
/* 469 */ string.Empty,
/* 470 */ string.Empty,
/* 471 */ string.Empty,
/* 472 */ string.Empty,
/* 473 */ string.Empty,
/* 474 */ string.Empty,
/* 475 */ string.Empty,
/* 476 */ string.Empty,
/* 477 */ string.Empty,
/* 478 */ string.Empty,
/* 479 */ string.Empty,
/* 480 */ string.Empty,
/* 481 */ string.Empty,
/* 482 */ string.Empty,
/* 483 */ string.Empty,
/* 484 */ string.Empty,
/* 485 */ string.Empty,
/* 486 */ string.Empty,
/* 487 */ string.Empty,
/* 488 */ string.Empty,
/* 489 */ string.Empty,
/* 490 */ string.Empty,
/* 491 */ string.Empty,
/* 492 */ string.Empty,
/* 493 */ string.Empty,
/* 494 */ string.Empty,
/* 495 */ string.Empty,
/* 496 */ string.Empty,
/* 497 */ string.Empty,
/* 498 */ string.Empty,
/* 499 */ "Client Closed Request"
],
[
/* 500 */ "Internal Server Error",
/* 501 */ "Not Implemented",
/* 502 */ "Bad Gateway",
/* 503 */ "Service Unavailable",
/* 504 */ "Gateway Timeout",
/* 505 */ "HTTP Version Not Supported",
/* 506 */ "Variant Also Negotiates",
/* 507 */ "Insufficient Storage",
/* 508 */ "Loop Detected",
/* 509 */ string.Empty,
/* 510 */ "Not Extended",
/* 511 */ "Network Authentication Required"
]
];
public static string GetReasonPhrase(int statusCode)
{
if (statusCode >= 100 && statusCode < 600)
{
int i = Math.DivRem(statusCode, 100, out int j);
if (j < HttpReasonPhrases[i].Length)
{
return HttpReasonPhrases[i][j];
}
}
return string.Empty;
}
}
This suggests that frozen dictionary could usefully have a specialization for (near?) sequential integer keys, if that case is common enough...?
A quick initial update on the benchmark after the latest adjustments:
| Method | Mean | Error | StdDev | Ratio |
|---|---|---|---|---|
| Reasoning_Dict | 42.606 ns | 0.2499 ns | 0.2338 ns | 1.00 |
| Reasoning_Array | 11.076 ns | 0.1218 ns | 0.1140 ns | 0.26 |
| Reasoning_Array_Review | 7.442 ns | 0.0560 ns | 0.0497 ns | 0.17 |
I'll adjust my array based benchmarks and update the description accordingly.
Benchmarks
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run();
public class Benchmarks{
private readonly int[] _statusCodes = [0, 100, 102, 200, 226, 300, 308, 499, 500, 511];
[GlobalSetup]
public void Setup() => Random.Shared.Shuffle(_statusCodes);
[Benchmark(Baseline = true)]
public string Reasoning_Dict() {
string values = string.Empty;
foreach (var statusCode in _statusCodes)
{
ReasonPhrases_Default.GetReasonPhrase(statusCode);
}
return values;
}
[Benchmark]
public string Reasoning_Array(){
string values = string.Empty;
foreach (var statusCode in _statusCodes)
{
ReasonPhrases_Array.GetReasonPhrase(statusCode);
}
return values;
}
[Benchmark]
public string Reasoning_Array_Review(){
string values = string.Empty;
foreach (var statusCode in _statusCodes)
{
ReasonPhrases_Array_Review.GetReasonPhrase(statusCode);
}
return values;
}
}
public static class ReasonPhrases_Default
{
private static readonly Dictionary<int, string> s_phrases = new()
{
{ 100, "Continue" },
{ 101, "Switching Protocols" },
{ 102, "Processing" },
{ 200, "OK" },
{ 201, "Created" },
{ 202, "Accepted" },
{ 203, "Non-Authoritative Information" },
{ 204, "No Content" },
{ 205, "Reset Content" },
{ 206, "Partial Content" },
{ 207, "Multi-Status" },
{ 208, "Already Reported" },
{ 226, "IM Used" },
{ 300, "Multiple Choices" },
{ 301, "Moved Permanently" },
{ 302, "Found" },
{ 303, "See Other" },
{ 304, "Not Modified" },
{ 305, "Use Proxy" },
{ 306, "Switch Proxy" },
{ 307, "Temporary Redirect" },
{ 308, "Permanent Redirect" },
{ 400, "Bad Request" },
{ 401, "Unauthorized" },
{ 402, "Payment Required" },
{ 403, "Forbidden" },
{ 404, "Not Found" },
{ 405, "Method Not Allowed" },
{ 406, "Not Acceptable" },
{ 407, "Proxy Authentication Required" },
{ 408, "Request Timeout" },
{ 409, "Conflict" },
{ 410, "Gone" },
{ 411, "Length Required" },
{ 412, "Precondition Failed" },
{ 413, "Payload Too Large" },
{ 414, "URI Too Long" },
{ 415, "Unsupported Media Type" },
{ 416, "Range Not Satisfiable" },
{ 417, "Expectation Failed" },
{ 418, "I'm a teapot" },
{ 419, "Authentication Timeout" },
{ 421, "Misdirected Request" },
{ 422, "Unprocessable Entity" },
{ 423, "Locked" },
{ 424, "Failed Dependency" },
{ 426, "Upgrade Required" },
{ 428, "Precondition Required" },
{ 429, "Too Many Requests" },
{ 431, "Request Header Fields Too Large" },
{ 451, "Unavailable For Legal Reasons" },
{ 499, "Client Closed Request" },
{ 500, "Internal Server Error" },
{ 501, "Not Implemented" },
{ 502, "Bad Gateway" },
{ 503, "Service Unavailable" },
{ 504, "Gateway Timeout" },
{ 505, "HTTP Version Not Supported" },
{ 506, "Variant Also Negotiates" },
{ 507, "Insufficient Storage" },
{ 508, "Loop Detected" },
{ 510, "Not Extended" },
{ 511, "Network Authentication Required" },
};
/// <summary>
/// Gets the reason phrase for the specified status code.
/// </summary>
/// <param name="statusCode">The status code.</param>
/// <returns>The reason phrase, or <see cref="string.Empty"/> if the status code is unknown.</returns>
public static string GetReasonPhrase(int statusCode)
{
return s_phrases.TryGetValue(statusCode, out string? phrase) ? phrase : string.Empty;
}
}
public static class ReasonPhrases_Array
{
private static readonly string[][] HttpReasonPhrases = [
[],
[
/* 100 */ "Continue",
/* 101 */ "Switching Protocols",
/* 102 */ "Processing"
],
[
/* 200 */ "OK",
/* 201 */ "Created",
/* 202 */ "Accepted",
/* 203 */ "Non-Authoritative Information",
/* 204 */ "No Content",
/* 205 */ "Reset Content",
/* 206 */ "Partial Content",
/* 207 */ "Multi-Status",
/* 208 */ "Already Reported",
/* 209 */ string.Empty,
/* 210 */ string.Empty,
/* 211 */ string.Empty,
/* 212 */ string.Empty,
/* 213 */ string.Empty,
/* 214 */ string.Empty,
/* 215 */ string.Empty,
/* 216 */ string.Empty,
/* 217 */ string.Empty,
/* 218 */ string.Empty,
/* 219 */ string.Empty,
/* 220 */ string.Empty,
/* 221 */ string.Empty,
/* 222 */ string.Empty,
/* 223 */ string.Empty,
/* 224 */ string.Empty,
/* 225 */ string.Empty,
/* 226 */ "IM Used"
],
[
/* 300 */ "Multiple Choices",
/* 301 */ "Moved Permanently",
/* 302 */ "Found",
/* 303 */ "See Other",
/* 304 */ "Not Modified",
/* 305 */ "Use Proxy",
/* 306 */ "Switch Proxy",
/* 307 */ "Temporary Redirect",
/* 308 */ "Permanent Redirect"
],
[
/* 400 */ "Bad Request",
/* 401 */ "Unauthorized",
/* 402 */ "Payment Required",
/* 403 */ "Forbidden",
/* 404 */ "Not Found",
/* 405 */ "Method Not Allowed",
/* 406 */ "Not Acceptable",
/* 407 */ "Proxy Authentication Required",
/* 408 */ "Request Timeout",
/* 409 */ "Conflict",
/* 410 */ "Gone",
/* 411 */ "Length Required",
/* 412 */ "Precondition Failed",
/* 413 */ "Payload Too Large",
/* 414 */ "URI Too Long",
/* 415 */ "Unsupported Media Type",
/* 416 */ "Range Not Satisfiable",
/* 417 */ "Expectation Failed",
/* 418 */ "I'm a teapot",
/* 419 */ "Authentication Timeout",
/* 420 */ string.Empty,
/* 421 */ "Misdirected Request",
/* 422 */ "Unprocessable Entity",
/* 423 */ "Locked",
/* 424 */ "Failed Dependency",
/* 425 */ string.Empty,
/* 426 */ "Upgrade Required",
/* 427 */ string.Empty,
/* 428 */ "Precondition Required",
/* 429 */ "Too Many Requests",
/* 430 */ string.Empty,
/* 431 */ "Request Header Fields Too Large",
/* 432 */ string.Empty,
/* 433 */ string.Empty,
/* 434 */ string.Empty,
/* 435 */ string.Empty,
/* 436 */ string.Empty,
/* 437 */ string.Empty,
/* 438 */ string.Empty,
/* 439 */ string.Empty,
/* 440 */ string.Empty,
/* 441 */ string.Empty,
/* 442 */ string.Empty,
/* 443 */ string.Empty,
/* 444 */ string.Empty,
/* 445 */ string.Empty,
/* 446 */ string.Empty,
/* 447 */ string.Empty,
/* 448 */ string.Empty,
/* 449 */ string.Empty,
/* 450 */ string.Empty,
/* 451 */ "Unavailable For Legal Reasons",
/* 452 */ string.Empty,
/* 453 */ string.Empty,
/* 454 */ string.Empty,
/* 455 */ string.Empty,
/* 456 */ string.Empty,
/* 457 */ string.Empty,
/* 458 */ string.Empty,
/* 459 */ string.Empty,
/* 460 */ string.Empty,
/* 461 */ string.Empty,
/* 462 */ string.Empty,
/* 463 */ string.Empty,
/* 464 */ string.Empty,
/* 465 */ string.Empty,
/* 466 */ string.Empty,
/* 467 */ string.Empty,
/* 468 */ string.Empty,
/* 469 */ string.Empty,
/* 470 */ string.Empty,
/* 471 */ string.Empty,
/* 472 */ string.Empty,
/* 473 */ string.Empty,
/* 474 */ string.Empty,
/* 475 */ string.Empty,
/* 476 */ string.Empty,
/* 477 */ string.Empty,
/* 478 */ string.Empty,
/* 479 */ string.Empty,
/* 480 */ string.Empty,
/* 481 */ string.Empty,
/* 482 */ string.Empty,
/* 483 */ string.Empty,
/* 484 */ string.Empty,
/* 485 */ string.Empty,
/* 486 */ string.Empty,
/* 487 */ string.Empty,
/* 488 */ string.Empty,
/* 489 */ string.Empty,
/* 490 */ string.Empty,
/* 491 */ string.Empty,
/* 492 */ string.Empty,
/* 493 */ string.Empty,
/* 494 */ string.Empty,
/* 495 */ string.Empty,
/* 496 */ string.Empty,
/* 497 */ string.Empty,
/* 498 */ string.Empty,
/* 499 */ "Client Closed Request"
],
[
/* 500 */ "Internal Server Error",
/* 501 */ "Not Implemented",
/* 502 */ "Bad Gateway",
/* 503 */ "Service Unavailable",
/* 504 */ "Gateway Timeout",
/* 505 */ "HTTP Version Not Supported",
/* 506 */ "Variant Also Negotiates",
/* 507 */ "Insufficient Storage",
/* 508 */ "Loop Detected",
/* 509 */ string.Empty,
/* 510 */ "Not Extended",
/* 511 */ "Network Authentication Required"
]
];
public static string GetReasonPhrase(int statusCode)
{
if (statusCode >= 100 && statusCode < 600)
{
int i = Math.DivRem(statusCode, 100, out int j);
if (j < HttpReasonPhrases[i].Length)
{
return HttpReasonPhrases[i][j];
}
}
return string.Empty;
}
}
public static class ReasonPhrases_Array_Review
{
private static readonly string[][] HttpReasonPhrases = [
[],
[
/* 100 */ "Continue",
/* 101 */ "Switching Protocols",
/* 102 */ "Processing"
],
[
/* 200 */ "OK",
/* 201 */ "Created",
/* 202 */ "Accepted",
/* 203 */ "Non-Authoritative Information",
/* 204 */ "No Content",
/* 205 */ "Reset Content",
/* 206 */ "Partial Content",
/* 207 */ "Multi-Status",
/* 208 */ "Already Reported",
/* 209 */ string.Empty,
/* 210 */ string.Empty,
/* 211 */ string.Empty,
/* 212 */ string.Empty,
/* 213 */ string.Empty,
/* 214 */ string.Empty,
/* 215 */ string.Empty,
/* 216 */ string.Empty,
/* 217 */ string.Empty,
/* 218 */ string.Empty,
/* 219 */ string.Empty,
/* 220 */ string.Empty,
/* 221 */ string.Empty,
/* 222 */ string.Empty,
/* 223 */ string.Empty,
/* 224 */ string.Empty,
/* 225 */ string.Empty,
/* 226 */ "IM Used"
],
[
/* 300 */ "Multiple Choices",
/* 301 */ "Moved Permanently",
/* 302 */ "Found",
/* 303 */ "See Other",
/* 304 */ "Not Modified",
/* 305 */ "Use Proxy",
/* 306 */ "Switch Proxy",
/* 307 */ "Temporary Redirect",
/* 308 */ "Permanent Redirect"
],
[
/* 400 */ "Bad Request",
/* 401 */ "Unauthorized",
/* 402 */ "Payment Required",
/* 403 */ "Forbidden",
/* 404 */ "Not Found",
/* 405 */ "Method Not Allowed",
/* 406 */ "Not Acceptable",
/* 407 */ "Proxy Authentication Required",
/* 408 */ "Request Timeout",
/* 409 */ "Conflict",
/* 410 */ "Gone",
/* 411 */ "Length Required",
/* 412 */ "Precondition Failed",
/* 413 */ "Payload Too Large",
/* 414 */ "URI Too Long",
/* 415 */ "Unsupported Media Type",
/* 416 */ "Range Not Satisfiable",
/* 417 */ "Expectation Failed",
/* 418 */ "I'm a teapot",
/* 419 */ "Authentication Timeout",
/* 420 */ string.Empty,
/* 421 */ "Misdirected Request",
/* 422 */ "Unprocessable Entity",
/* 423 */ "Locked",
/* 424 */ "Failed Dependency",
/* 425 */ string.Empty,
/* 426 */ "Upgrade Required",
/* 427 */ string.Empty,
/* 428 */ "Precondition Required",
/* 429 */ "Too Many Requests",
/* 430 */ string.Empty,
/* 431 */ "Request Header Fields Too Large",
/* 432 */ string.Empty,
/* 433 */ string.Empty,
/* 434 */ string.Empty,
/* 435 */ string.Empty,
/* 436 */ string.Empty,
/* 437 */ string.Empty,
/* 438 */ string.Empty,
/* 439 */ string.Empty,
/* 440 */ string.Empty,
/* 441 */ string.Empty,
/* 442 */ string.Empty,
/* 443 */ string.Empty,
/* 444 */ string.Empty,
/* 445 */ string.Empty,
/* 446 */ string.Empty,
/* 447 */ string.Empty,
/* 448 */ string.Empty,
/* 449 */ string.Empty,
/* 450 */ string.Empty,
/* 451 */ "Unavailable For Legal Reasons",
/* 452 */ string.Empty,
/* 453 */ string.Empty,
/* 454 */ string.Empty,
/* 455 */ string.Empty,
/* 456 */ string.Empty,
/* 457 */ string.Empty,
/* 458 */ string.Empty,
/* 459 */ string.Empty,
/* 460 */ string.Empty,
/* 461 */ string.Empty,
/* 462 */ string.Empty,
/* 463 */ string.Empty,
/* 464 */ string.Empty,
/* 465 */ string.Empty,
/* 466 */ string.Empty,
/* 467 */ string.Empty,
/* 468 */ string.Empty,
/* 469 */ string.Empty,
/* 470 */ string.Empty,
/* 471 */ string.Empty,
/* 472 */ string.Empty,
/* 473 */ string.Empty,
/* 474 */ string.Empty,
/* 475 */ string.Empty,
/* 476 */ string.Empty,
/* 477 */ string.Empty,
/* 478 */ string.Empty,
/* 479 */ string.Empty,
/* 480 */ string.Empty,
/* 481 */ string.Empty,
/* 482 */ string.Empty,
/* 483 */ string.Empty,
/* 484 */ string.Empty,
/* 485 */ string.Empty,
/* 486 */ string.Empty,
/* 487 */ string.Empty,
/* 488 */ string.Empty,
/* 489 */ string.Empty,
/* 490 */ string.Empty,
/* 491 */ string.Empty,
/* 492 */ string.Empty,
/* 493 */ string.Empty,
/* 494 */ string.Empty,
/* 495 */ string.Empty,
/* 496 */ string.Empty,
/* 497 */ string.Empty,
/* 498 */ string.Empty,
/* 499 */ "Client Closed Request"
],
[
/* 500 */ "Internal Server Error",
/* 501 */ "Not Implemented",
/* 502 */ "Bad Gateway",
/* 503 */ "Service Unavailable",
/* 504 */ "Gateway Timeout",
/* 505 */ "HTTP Version Not Supported",
/* 506 */ "Variant Also Negotiates",
/* 507 */ "Insufficient Storage",
/* 508 */ "Loop Detected",
/* 509 */ string.Empty,
/* 510 */ "Not Extended",
/* 511 */ "Network Authentication Required"
]
];
public static string GetReasonPhrase(int statusCode)
{
if ((uint)(statusCode - 100) < 500)
{
var (i,j) = Math.DivRem((uint)statusCode, 100);
string[] phrases = HttpReasonPhrases[i];
if (j < (uint)phrases.Length)
{
return phrases[j];
}
}
return string.Empty;
}
}
@WhatzGames Thanks for the thoroughness here, and welcome to the repo!
And thanks for the great review feedback @gfoidl
/azp run
Azure Pipelines successfully started running 3 pipeline(s).
Thanks for your patience, @WhatzGames. @gfoidl thanks for the review. Given that you haven't yet signed off, I wonder if you still have any feedback to share? @BrennanConroy you've also reviewed this, thank you. If there is nothing blocking from your point of view, could you please sign off?
/azp run
Azure Pipelines successfully started running 3 pipeline(s).
Did we look at frozen dictionary at all?
cc @stephentoub
https://github.com/dotnet/aspnetcore/pull/56304#issuecomment-2190015487
Feels like this could be an optimization of frozen dict if the keysaare int and the range is known and under some limit. (https://github.com/dotnet/aspnetcore/pull/56304#issuecomment-2209369961)
Feels like this could be an optimization of frozen dict if the keysaare int and the range is known and under some limit. (#56304 (comment))
@stephentoub anything worth recording here as a suggestion? frozendictionary has various different heuristics already and it's not clear to me whether there's something here. linking https://github.com/dotnet/runtime/issues/77891 but it seems to be basically left for source generation.
There are many possible specializations in FrozenSet/Dictionary. Each one adds more code, heuristics, maintenance, etc. Do we think this scenario of densely-packed Int32s is common enough to special-case it? It obviously could implement such a scheme; the question is whether it's worthwhile. This particular scheme is also very-finely tuned specifically for HTTP status codes, with groups that start evenly at 100/200/300/400/500; handling other groupings would either require more space or more expensive partitioning. Again, it comes down to what scenarios we're trying to optimize and how much we want to invest to do so.
I put up https://github.com/dotnet/runtime/pull/111886. Not sure you'd actually want to switch to it, but you could experiment; it'll likely consume more memory but might be a tad faster.