-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Perf: ReasonPhrases as Array instead of Dictionary #56304
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
Conversation
@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. |
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. 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. So if the given context requires it, I'll change the PR to use a FrozenDictionary instead. Benchmark Codeusing 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
};
} |
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 |
Done. |
mmmh... I just found a different approach and created a Benchmark for that one.
results after adding a Shuffle like @gfoidl used:
Benchmarksusing 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 |
Funny enough I had the same idea. I ran both benchmarks twice and results in the order as before: First run:
second run:
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
|
So I got some more results. Home-PC:
Codespace-Instance:
Work-Machine:
Based on these and my previous results I would tend to use DivRem with the latest approach, if that's alright by you. Benchmarkusing 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:
I'll adjust my array based benchmarks and update the description accordingly. Benchmarksusing 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 |
Co-authored-by: Günther Foidl <[email protected]>
/azp run |
Azure Pipelines successfully started running 3 pipeline(s). |
Thanks for your patience, @WhatzGames. |
/azp run |
Azure Pipelines successfully started running 3 pipeline(s). |
Did we look at frozen dictionary at all? cc @stephentoub |
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. |
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 dotnet/runtime#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. |
ReasonPhrases as Array instead of Dictionary
Summary of the changes (Less than 80 chars)
Perf improvement ReasonPhrase
Description
Replaced the Dictionary based implementation for an Array.
Benchmarks show an overall speed-improvement:
UnchangedBenchmarks
PreparedBenchmarks
UnpreparedBenchmarks
StraightBenchmarks
Edit: updated benchmarks to show results according to #56304 (comment)
Edit2: updated benchmarks to show results according to #56304 (comment)
Edit3: updated benchmarks to show results according to 344293d