{"id":18290704,"url":"https://github.com/nikouu/httpclientbenchmarking","last_synced_at":"2026-05-01T19:34:39.402Z","repository":{"id":212040417,"uuid":"730050226","full_name":"nikouu/HttpClientBenchmarking","owner":"nikouu","description":"Benchmarking different ways C#'s HttpClient is used for GET calls so we can all better understand performance and use cases in .NET 8.","archived":false,"fork":false,"pushed_at":"2023-12-18T01:18:48.000Z","size":1354,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-27T09:37:47.178Z","etag":null,"topics":["benchmark","benchmarkdotnet","benchmarking","csharp","dotnet","dotnet6","dotnet7","dotnet8","httpclient"],"latest_commit_sha":null,"homepage":"","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nikouu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2023-12-11T05:06:12.000Z","updated_at":"2023-12-12T09:35:01.000Z","dependencies_parsed_at":"2023-12-16T00:13:15.353Z","dependency_job_id":"62cbcb04-c1f8-4711-ab31-11174abb376b","html_url":"https://github.com/nikouu/HttpClientBenchmarking","commit_stats":{"total_commits":16,"total_committers":1,"mean_commits":16.0,"dds":0.0,"last_synced_commit":"44836a8c54f2daeaa7a828b837d068b5b4e47be2"},"previous_names":["nikouu/httpclientbenchmarking"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/nikouu/HttpClientBenchmarking","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikouu%2FHttpClientBenchmarking","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikouu%2FHttpClientBenchmarking/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikouu%2FHttpClientBenchmarking/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikouu%2FHttpClientBenchmarking/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nikouu","download_url":"https://codeload.github.com/nikouu/HttpClientBenchmarking/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikouu%2FHttpClientBenchmarking/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32510808,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-30T13:12:12.517Z","status":"online","status_checked_at":"2026-05-01T02:00:05.856Z","response_time":64,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["benchmark","benchmarkdotnet","benchmarking","csharp","dotnet","dotnet6","dotnet7","dotnet8","httpclient"],"created_at":"2024-11-05T14:11:54.801Z","updated_at":"2026-05-01T19:34:39.369Z","avatar_url":"https://github.com/nikouu.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# HttpClientBenchmarking\nBenchmarking different ways C#'s `HttpClient` is used for GET calls so we can all better understand performance and use cases in .NET 8.\n\n## How to run\n1. Get the repo\n1. Run `RunBenchmark.ps1`\n\t- This runs both the API project and begins the benchmarking project\n\n## Test Scenario\n\n1. Firing off requests for JSON data from a given API \n1. The data is then deserialized\n\nThere are nine ways of using `HttpClient` to get JSON data and they are compared for n = 10, 100, 1000, 10000 JSON object sizes.\n\nThe JSON data is from the default .NET minimal API example, and looks like:\n\n```json\n[\n\t{\n\t\t\"date\": \"2023-12-13\",\n\t\t\"temperatureC\": 48,\n\t\t\"summary\": \"Hot\",\n\t\t\"temperatureF\": 118\n\t}\n]\n```\n\n### Other Points\n\n- Benchmarks generated via [BenchmarkDotNet](https://benchmarkdotnet.org/)\n- Benchmark class is [HttpClientBenchmarks.cs](src/HttpClientBenchmarking/HttpClientBenchmarks.cs)\n- The API project is [HttpClientBenchmarking.Api](src/HttpClientBenchmarking.Api)\n\n## Results\n\n*Lower is better*\n\n![](results/dotnet8/HttpClientBenchmarking.HttpClientBenchmarks-barplot-modified.png)\n*The generated graph order does not match the order of the results table* 😢\n\n| Method                                      | Size  | Mean       | Error     | StdDev    | Median     | Ratio | RatioSD | Gen0      | Gen1      | Gen2     | Allocated  | Alloc Ratio |\n|-------------------------------------------- |------ |-----------:|----------:|----------:|-----------:|------:|--------:|----------:|----------:|---------:|-----------:|------------:|\n| GetAsync_ReadAsStringAsync                  | 10    |   113.1 μs |   1.95 μs |   1.73 μs |   112.2 μs |  1.00 |    0.00 |    1.2207 |         - |        - |    5.67 KB |        1.00 |\n| GetAsync_ReadAsStreamAsync                  | 10    |   110.4 μs |   1.42 μs |   1.19 μs |   110.6 μs |  0.98 |    0.02 |    0.9766 |         - |        - |    3.97 KB |        0.70 |\n| GetAsync_ReadFromJsonAsync                  | 10    |   112.8 μs |   1.69 μs |   1.58 μs |   112.1 μs |  1.00 |    0.02 |    0.9766 |         - |        - |    4.27 KB |        0.75 |\n| GetAsync_ReadAsStringAsync_CompletionOption | 10    |   112.0 μs |   0.91 μs |   0.81 μs |   111.8 μs |  0.99 |    0.02 |    1.2207 |         - |        - |    5.67 KB |        1.00 |\n| GetAsync_ReadAsStreamAsync_CompletionOption | 10    |   112.2 μs |   0.99 μs |   0.82 μs |   112.4 μs |  0.99 |    0.02 |    0.7324 |         - |        - |    3.05 KB |        0.54 |\n| GetAsync_ReadFromJsonAsync_CompletionOption | 10    |   115.1 μs |   2.10 μs |   4.91 μs |   112.8 μs |  1.01 |    0.03 |    0.7324 |         - |        - |    3.35 KB |        0.59 |\n| GetStreamAsync                              | 10    |   113.0 μs |   0.72 μs |   0.57 μs |   112.9 μs |  1.00 |    0.02 |    0.7324 |         - |        - |    2.97 KB |        0.52 |\n| GetStringAsync                              | 10    |   111.9 μs |   1.88 μs |   1.57 μs |   111.4 μs |  0.99 |    0.02 |    0.9766 |         - |        - |     4.8 KB |        0.85 |\n| GetFromJsonAsync                            | 10    |   111.9 μs |   0.66 μs |   0.62 μs |   111.9 μs |  0.99 |    0.02 |    0.7324 |         - |        - |    3.72 KB |        0.66 |\n|                                             |       |            |           |           |            |       |         |           |           |          |            |             |\n| GetAsync_ReadAsStringAsync                  | 100   |   181.7 μs |   1.10 μs |   0.98 μs |   181.7 μs |  1.00 |    0.00 |    8.3008 |    0.4883 |        - |   35.39 KB |        1.00 |\n| GetAsync_ReadAsStreamAsync                  | 100   |   178.9 μs |   2.15 μs |   1.91 μs |   178.5 μs |  0.98 |    0.01 |    4.8828 |         - |        - |   20.13 KB |        0.57 |\n| GetAsync_ReadFromJsonAsync                  | 100   |   183.6 μs |   3.58 μs |   5.58 μs |   180.8 μs |  1.01 |    0.03 |    4.8828 |         - |        - |   20.42 KB |        0.58 |\n| GetAsync_ReadAsStringAsync_CompletionOption | 100   |   181.4 μs |   0.97 μs |   0.76 μs |   181.5 μs |  1.00 |    0.01 |    8.3008 |         - |        - |   35.39 KB |        1.00 |\n| GetAsync_ReadAsStreamAsync_CompletionOption | 100   |   175.9 μs |   0.98 μs |   0.87 μs |   175.9 μs |  0.97 |    0.01 |    1.9531 |         - |        - |    8.39 KB |        0.24 |\n| GetAsync_ReadFromJsonAsync_CompletionOption | 100   |   177.1 μs |   1.16 μs |   0.97 μs |   176.7 μs |  0.97 |    0.01 |    1.9531 |         - |        - |    8.69 KB |        0.25 |\n| GetStreamAsync                              | 100   |   176.7 μs |   2.67 μs |   2.37 μs |   176.0 μs |  0.97 |    0.01 |    1.9531 |         - |        - |     8.3 KB |        0.23 |\n| GetStringAsync                              | 100   |   180.2 μs |   1.45 μs |   1.36 μs |   179.9 μs |  0.99 |    0.01 |    5.8594 |         - |        - |    23.7 KB |        0.67 |\n| GetFromJsonAsync                            | 100   |   182.4 μs |   3.22 μs |   5.37 μs |   179.3 μs |  1.02 |    0.03 |    1.9531 |         - |        - |     9.2 KB |        0.26 |\n|                                             |       |            |           |           |            |       |         |           |           |          |            |             |\n| GetAsync_ReadAsStringAsync                  | 1000  |   841.4 μs |  12.98 μs |  10.13 μs |   842.6 μs |  1.00 |    0.00 |  126.9531 |   83.9844 |  82.0313 |  457.04 KB |        1.00 |\n| GetAsync_ReadAsStreamAsync                  | 1000  |   881.0 μs |   6.81 μs |   5.68 μs |   880.1 μs |  1.05 |    0.02 |   78.1250 |   39.0625 |  39.0625 |  305.84 KB |        0.67 |\n| GetAsync_ReadFromJsonAsync                  | 1000  |   877.0 μs |   6.78 μs |   6.34 μs |   876.2 μs |  1.04 |    0.01 |   78.1250 |   39.0625 |  39.0625 |  306.14 KB |        0.67 |\n| GetAsync_ReadAsStringAsync_CompletionOption | 1000  |   846.3 μs |   9.44 μs |   7.88 μs |   845.6 μs |  1.01 |    0.01 |  126.9531 |   82.0313 |  82.0313 |  457.06 KB |        1.00 |\n| GetAsync_ReadAsStreamAsync_CompletionOption | 1000  |   660.4 μs |   8.66 μs |   7.67 μs |   659.6 μs |  0.79 |    0.01 |   13.6719 |         - |        - |   58.01 KB |        0.13 |\n| GetAsync_ReadFromJsonAsync_CompletionOption | 1000  |   669.4 μs |  13.17 μs |  16.17 μs |   667.4 μs |  0.80 |    0.02 |   13.6719 |    0.9766 |        - |   58.23 KB |        0.13 |\n| GetStreamAsync                              | 1000  |   655.7 μs |   7.85 μs |   6.56 μs |   655.4 μs |  0.78 |    0.01 |   13.6719 |    0.9766 |        - |   57.91 KB |        0.13 |\n| GetStringAsync                              | 1000  |   876.9 μs |  22.57 μs |  65.13 μs |   845.9 μs |  1.11 |    0.12 |   56.6406 |   42.9688 |  42.9688 |  209.19 KB |        0.46 |\n| GetFromJsonAsync                            | 1000  |   672.1 μs |  12.43 μs |  10.38 μs |   672.3 μs |  0.80 |    0.02 |   13.6719 |    1.9531 |        - |   58.68 KB |        0.13 |\n|                                             |       |            |           |           |            |       |         |           |           |          |            |             |\n| GetAsync_ReadAsStringAsync                  | 10000 | 7,793.5 μs | 155.56 μs | 268.34 μs | 7,660.0 μs |  1.00 |    0.00 | 1093.7500 | 1093.7500 | 984.3750 | 4922.88 KB |        1.00 |\n| GetAsync_ReadAsStreamAsync                  | 10000 | 7,469.3 μs | 114.95 μs | 107.53 μs | 7,488.8 μs |  0.96 |    0.04 |  593.7500 |  593.7500 | 500.0000 | 2662.29 KB |        0.54 |\n| GetAsync_ReadFromJsonAsync                  | 10000 | 7,467.0 μs |  95.15 μs |  79.45 μs | 7,464.7 μs |  0.96 |    0.04 |  609.3750 |  609.3750 | 500.0000 | 2662.96 KB |        0.54 |\n| GetAsync_ReadAsStringAsync_CompletionOption | 10000 | 7,678.6 μs | 107.05 μs | 100.13 μs | 7,683.3 μs |  0.98 |    0.04 | 1109.3750 | 1046.8750 | 984.3750 | 4922.51 KB |        1.00 |\n| GetAsync_ReadAsStreamAsync_CompletionOption | 10000 | 5,530.7 μs |  95.23 μs |  93.52 μs | 5,509.9 μs |  0.71 |    0.03 |  101.5625 |   62.5000 |  39.0625 |   649.4 KB |        0.13 |\n| GetAsync_ReadFromJsonAsync_CompletionOption | 10000 | 5,575.3 μs | 104.66 μs |  97.90 μs | 5,568.0 μs |  0.71 |    0.03 |  101.5625 |   62.5000 |  39.0625 |   649.7 KB |        0.13 |\n| GetStreamAsync                              | 10000 | 5,476.0 μs |  50.20 μs |  39.20 μs | 5,473.6 μs |  0.70 |    0.03 |  101.5625 |   62.5000 |  39.0625 |  649.33 KB |        0.13 |\n| GetStringAsync                              | 10000 | 7,488.2 μs | 120.12 μs | 138.33 μs | 7,458.6 μs |  0.96 |    0.04 |  250.0000 |  242.1875 | 164.0625 | 2915.64 KB |        0.59 |\n| GetFromJsonAsync                            | 10000 | 5,462.2 μs |  56.19 μs |  46.92 μs | 5,465.0 μs |  0.70 |    0.03 |  101.5625 |   62.5000 |  39.0625 |  650.25 KB |        0.13 |\n\n```\nSize        : Value of the 'Size' parameter - the number of weather records returned per request\nMean        : Arithmetic mean of all measurements\nError       : Half of 99.9% confidence interval\nStdDev      : Standard deviation of all measurements\nMedian      : Value separating the higher half of all measurements (50th percentile)\nRatio       : Mean of the ratio distribution ([Current]/[Baseline])\nRatioSD     : Standard deviation of the ratio distribution ([Current]/[Baseline])\nGen0        : GC Generation 0 collects per 1000 operations\nGen1        : GC Generation 1 collects per 1000 operations\nGen2        : GC Generation 2 collects per 1000 operations\nAllocated   : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)\nAlloc Ratio : Allocated memory ratio distribution ([Current]/[Baseline])\n1 μs        : 1 Microsecond (0.000001 sec)\n```\n\nThis data is found in the [dotnet8](results/dotnet8) folder.\n\n### Other Results\n\nResults for a comparison between .NET 6, .NET 7, and .NET 8 can be found in the [dotnet6-7-8](results/dotnet6-7-8) folder. \n\nIn short, the focus on performance for .NET 8 really shows.\n\n## Short Analysis\n\n*The benchmark test cases can be found in [HttpClientBenchmarks.cs](src/HttpClientBenchmarking/HttpClientBenchmarks.cs)*\n\nThe test functions `GetStreamAsync()` and `GetFromJsonAsync()` are the simpliest and easiest to use with being among the top in performance for both execution time and memory allocation when compared to the baseline `GetAsync_ReadAsStringAsync()` test case. \n\nThe test cases:\n```csharp\n// top performer\npublic async Task GetStreamAsync()\n{\n    using var stream = await _httpClient.GetStreamAsync(_url);\n\n    var data = await JsonSerializer.DeserializeAsync\u003cList\u003cWeatherForecast\u003e\u003e(stream);\n}\n```\n\n```csharp\n// top performer\npublic async Task GetFromJsonAsync()\n{\n    var data = await _httpClient.GetFromJsonAsync\u003cList\u003cWeatherForecast\u003e\u003e(_url);\n}\n```\n\n```csharp\n// baseline\npublic async Task GetAsync_ReadAsStringAsync()\n{\n    using HttpResponseMessage response = await _httpClient.GetAsync(_url);\n\n    var jsonResponse = await response.Content.ReadAsStringAsync();\n\n    // Note this is NOT async when dealing with string input\n    var data = JsonSerializer.Deserialize\u003cList\u003cWeatherForecast\u003e\u003e(jsonResponse);\n}\n```\n\nWhy are these good? They pass the `Stream` straight back to the user without buffering it - I.E. the data does not get copied into another `MemoryStream` *then* returned to the user. We can see the stream is passed back in the source [HttpClient.cs#L346](https://github.com/dotnet/runtime/blob/f83838b2ba88f8db115588ec2eab82b2993ccab4/src/libraries/System.Net.Http/src/System/Net/Http/HttpClient.cs#L346) file. The extension method `GetFromJsonAsync()` works the same way as seen in [HttpContentJsonExtensions.cs#L136](https://github.com/dotnet/runtime/blob/f83838b2ba88f8db115588ec2eab82b2993ccab4/src/libraries/System.Net.Http.Json/src/System/Net/Http/Json/HttpContentJsonExtensions.cs#L136).\n\nThis explains why `GetAsync_ReadAsStreamAsync_CompletionOption()` and `GetAsync_ReadFromJsonAsync_CompletionOption()` test methods are equally as good. Due to the `HttpCompletionOption.ResponseHeadersRead` option, we can also avoid the extra copy. We can see that in action ourselves in the source [HttpClient.cs#L479](https://github.com/dotnet/runtime/blob/f83838b2ba88f8db115588ec2eab82b2993ccab4/src/libraries/System.Net.Http/src/System/Net/Http/HttpClient.cs#L479) file.\n\n### What about `PostAsync()`, `PutAsync()`, `PatchAsync()`, and `DeleteAsync()`?\n\nAll of these are convenience overloads for `SendAsync()`. See the source for [`PostAsync()`](https://github.com/dotnet/runtime/blob/f83838b2ba88f8db115588ec2eab82b2993ccab4/src/libraries/System.Net.Http/src/System/Net/Http/HttpClient.cs#L387) and notice how the calls build in each overload until the final call to `SendAsync()`.\n\n### What about `SendAsync()` and `HttpRequestMessage`?\n\nYou might've seen `HttpRequestMessage` when seeing `HttpClient` snippets. This object is used for more fine grained control over the request such as changing the verb. All the `SendAsync()` type calls end up using `HttpRequestMessage` in the end, even if all you pass is a URL to `SendAsync()`. Check it out yourself in the source [HttpClient.cs#L324](https://github.com/dotnet/runtime/blob/f83838b2ba88f8db115588ec2eab82b2993ccab4/src/libraries/System.Net.Http/src/System/Net/Http/HttpClient.cs#L324) file.\n\nAs a refresher, here's changing the verb to HEAD:\n\n```csharp\nusing HttpRequestMessage request = new(HttpMethod.Head,\"https://www.example.com\");\n\nusing HttpResponseMessage response = await httpClient.SendAsync(request);\n```\n\nWe're not concerned about it in respect to tangible performance.\n\n## Why do this repo?\nOver time .NET has evolved for the better, however these improvements may not make it to old StackOverflow answers or to Copilot easily. This repo is designed to compare all the relevant ways to use HttpClient that I see to help others and myself make informed decisions - and perhaps dispel some old myths. (For instance, it was confusing back when .NET Framework had [three native ways](https://stackoverflow.com/a/4015346) to make requests).\n\n## References\nThis repo references or is inspired by the following people and their work:\n\n| Person                                                                                                            | Reason                                                                                                                                              | Link                                                                                                                                                                                                         |\n| ----------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| [Steve Gordon](https://www.stevejgordon.co.uk/)                                                                   | Original inspiration for this repo - including the [benchmarking code](https://github.com/stevejgordon/HttpCompletionOptionBenchmarks).             | [Using HttpCompletionOption to Improve HttpClient Performance in .NET](https://www.stevejgordon.co.uk/using-httpcompletionoption-responseheadersread-to-improve-httpclient-performance-dotnet)               |\n| [Abdul Rahman](https://www.linkedin.com/in/fingers10), [Regina Sharon](https://www.linkedin.com/in/regina-sharon) | Good, simple examples of different ways to use HttpClient with easy to understand diagrams.                                                         | [Improving performance and memory use while accessing APIs using HTTPClient in dotnet](https://ilovedotnet.org/blogs/improving-performance-and-memory-use-while-accessing-apis-using-http-client-in-dotnet/) |\n| [John Thiriet](https://johnthiriet.com/)                                                                          | A post I read a long while ago about ways to speed up HttpClient - including some [benchmarks](https://github.com/johnthiriet/EfficientHttpClient). | [Efficient api calls with HttpClient and JSON.NET](https://johnthiriet.com/efficient-api-calls/)                                                                                                             |\n| [Stephen Toub](https://devblogs.microsoft.com/dotnet/author/toub/)                                                | Writer of the inspirational .NET performance improvement tomes.                                                                                      | [Performance Improvements in .NET 8](https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-8/)                                                                                               |\n\n## Notes\n- For the sake of simplicity there is no response checking or validating.\n- Similarly for brevity, no `CancellationToken` objects are used.\n- No [decompression flags](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclienthandler.automaticdecompression?view=net-8.0#system-net-http-httpclienthandler-automaticdecompression) are explicitly set for simplicity.\n- You may find different performance depending on your hardware, OS, data, network speed, data type, etc. \n- Deserializing JSON was chosen as it's widely understood and a common workflow with `HttpClient`. So much so, we get the `GetFromJsonAsync()` extension method.\n- This repo does not look at `IHttpClientFactory`.\n\n## Other Links\n- [HttpClient Class via Microsoft](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=net-8.0)\n- [Make HTTP requests with the HttpClient class via Microsoft](https://learn.microsoft.com/en-us/dotnet/fundamentals/networking/http/httpclient)\n- [Guidelines for using HttpClient via Microsoft](https://learn.microsoft.com/en-us/dotnet/fundamentals/networking/http/httpclient-guidelines)\n- [HttpClient JSON Extensions via Microsoft](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.json.httpclientjsonextensions?view=net-8.0)\n- [Starting PowerShell script in another window](https://stackoverflow.com/questions/23237473/powershell-launch-script-in-new-instance)\n- [HttpRequestMessage Class via Microsoft](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httprequestmessage?view=net-8.0)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnikouu%2Fhttpclientbenchmarking","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnikouu%2Fhttpclientbenchmarking","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnikouu%2Fhttpclientbenchmarking/lists"}