1 文章目的
昨天寫了走進WebApiClientCore的設計,介紹了WebApiClient的變化與設計之後,收到大家支援的、讚許的,還有好的建議和顧慮,比如WebApiClient效能怎麼樣,有沒有一些對比參考值?我一直有個不好毛病,憑直接感觀程式碼的執行效率,直覺裡WebApiClient的程式碼都是優化的,就算效能不好,也沒有更好的效能彌補方案!但真要這樣和使用這個庫的開發者說,相信要一秒內被打屎的,所以今天使用了BenchmarkDotNet對WebApiClient.JIT最新穩定版、WebApiClientCore預覽版和原生的HttpClient做了請求和響應處理耗時對比。
2 排除網路IO等待干擾
我們必須要想辦法去掉真實網路請求的時間干擾,因為一個請求回合中,網路等待時間可能會佔用到整個流程的99.99%以上的時間。最好的辦法就是沒有請求,而是模擬一個響應內容。
System.Net.Http支援管道式包裝請求處理物件,叫DelegatingHandler,我們可以自定義一個DelegatingHandler,重寫其SendAsync()方法,然後直接返回模擬的響應訊息HttpResponseMessage,使用這個DelegatingHandler的HttpClient,在請求時不會真實將資料傳送到目標伺服器去。
/// <summary>
/// 無真實http請求的Handler
/// </summary>
class NoneHttpDelegatingHandler : DelegatingHandler
{
private readonly HttpResponseMessage benchmarkModelResponseMessage;
public NoneHttpDelegatingHandler()
{
var model = new Model { A = "A", B = 2, C = 3d };
var json = JsonSerializer.SerializeToUtf8Bytes(model);
this.benchmarkModelResponseMessage = new HttpResponseMessage(HttpStatusCode.OK) { Content = new JsonContent(json) };
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return Task.FromResult(this.benchmarkModelResponseMessage);
}
}
3 Benchmark測試
3.1 註冊到DI
為了公平,三種對比物件都註冊到到DI裡,使用時從DI獲取例項
var services = new ServiceCollection();
// 原生HttpClient
services
.AddHttpClient(typeof(HttpClient).FullName)
.AddHttpMessageHandler(() => new NoneHttpDelegatingHandler());
// WebApiClientCore
services
.AddHttpApi<IWebApiClientCoreApi>(o => o.HttpHost = new Uri("http://webapiclient.com/"))
.AddHttpMessageHandler(() => new NoneHttpDelegatingHandler());
// WebApiClient
WebApiClient.Extension
.AddHttpApi<IWebApiClientApi>(services, o => o.HttpHost = new Uri("http://webapiclient.com/"))
.AddHttpMessageHandler(() => new NoneHttpDelegatingHandler());
3.2 Benchmark方法
我們為三種客戶端每個比較專案各寫一個Benchmark方法:從DI獲取例項,然後發起Get請求,最後將響應的內容反序列化為強模型。
/// <summary>
/// 使用WebApiClient.JIT請求
/// </summary>
/// <returns></returns>
[Benchmark]
public async Task<Model> WebApiClient_GetAsync()
{
using var scope = this.serviceProvider.CreateScope();
var banchmarkApi = scope.ServiceProvider.GetRequiredService<IWebApiClientApi>();
return await banchmarkApi.GetAsyc(id: "id");
}
/// <summary>
/// 使用WebApiClientCore請求
/// </summary>
/// <returns></returns>
[Benchmark]
public async Task<Model> WebApiClientCore_GetAsync()
{
using var scope = this.serviceProvider.CreateScope();
var banchmarkApi = scope.ServiceProvider.GetRequiredService<IWebApiClientCoreApi>();
return await banchmarkApi.GetAsyc(id: "id");
}
/// <summary>
/// 使用原生HttpClient請求
/// </summary>
/// <returns></returns>
[Benchmark]
public async Task<Model> HttpClient_GetAsync()
{
using var scope = this.serviceProvider.CreateScope();
var httpClient = scope.ServiceProvider.GetRequiredService<IHttpClientFactory>().CreateClient(typeof(HttpClient).FullName);
var id = "id";
var request = new HttpRequestMessage(HttpMethod.Get, $"http://webapiclient.com/{id}");
var response = await httpClient.SendAsync(request);
var json = await response.Content.ReadAsByteArrayAsync();
return JsonSerializer.Deserialize<Model>(json);
}
3.3 Benchmark執行與結果
原於程式碼篇幅,實際中我們還加入了Post請求測試,所以最後的結果如下:
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.836 (1909/November2018Update/19H2)
Intel Core i7-8565U CPU 1.80GHz (Whiskey Lake), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.1.201
[Host] : .NET Core 3.1.3 (CoreCLR 4.700.20.11803, CoreFX 4.700.20.12001), X64 RyuJIT
DefaultJob : .NET Core 3.1.3 (CoreCLR 4.700.20.11803, CoreFX 4.700.20.12001), X64 RyuJIT
| Method | Mean | Error | StdDev |
|--------------------------- |----------:|----------:|----------:|
| WebApiClient_GetAsync | 25.716 us | 0.5106 us | 0.9838 us |
| WebApiClientCore_GetAsync | 16.047 us | 0.1231 us | 0.0961 us |
| HttpClient_GetAsync | 2.028 us | 0.0240 us | 0.0224 us |
| WebApiClient_PostAsync | 18.712 us | 0.3707 us | 0.3641 us |
| WebApiClientCore_PostAsync | 8.799 us | 0.1696 us | 0.1666 us |
| HttpClient_PostAsync | 3.673 us | 0.0710 us | 0.0972 us |
總結
不管是成熟穩定如狗的WebApiClient,還是新秀WebApiClientCore,其效能和原生的HttpClient在一個數量級,大家放心地使用就是了。