いくつかのASP.NET Web API 2.0エンドポイントのベンチマーク(Apacheベンチを使用)を試みています。 1つは同期的で、もう1つは非同期です。
[Route("user/{userId}/feeds")]
[HttpGet]
public IEnumerable<NewsFeedItem> GetNewsFeedItemsForUser(string userId)
{
return _newsFeedService.GetNewsFeedItemsForUser(userId);
}
[Route("user/{userId}/feeds/async")]
[HttpGet]
public async Task<IEnumerable<NewsFeedItem>> GetNewsFeedItemsForUserAsync(string userId)
{
return await Task.Run(() => _newsFeedService.GetNewsFeedItemsForUser(userId));
}
Steve Sandersonのプレゼンテーション を見た後、次のコマンドを発行しましたab -n 100 -c 10 http://localhost....
各エンドポイントに。
各エンドポイントのベンチマークがほぼ同じであるように思えたので驚きました。
スティーブが説明したことをやめると、非同期エンドポイントはスレッドプールスレッドをすぐにスレッドプールに解放し、他の要求で利用できるようになり、スループットが向上するため、より高性能になると期待していました。しかし、数字はまったく同じように見えます。
ここで誤解していることは何ですか?
await Task.Run
を使用して "async"を作成するのは悪い考えです-引き続きスレッドを使用し、 リクエストに使用されるのと同じスレッドプール 。
詳細 で説明されている不快な瞬間につながります :
- Task.Runスレッドプールスレッドへの余分な(不要な)スレッド切り替え。同様に、そのスレッドがリクエストを完了すると、リクエストコンテキストに入る必要があります(これは実際のスレッドスイッチではありませんが、オーバーヘッドがあります)。
- 余分な(不要な)ゴミが作成されます。非同期プログラミングはトレードオフです。メモリ使用量を増やすことで、応答性が向上します。この場合、完全に不要な非同期操作のガベージを作成することになります。
- ASP.NETスレッドプールヒューリスティックは、スレッドプールスレッドを "予期せず"借用するTask.Runによって破棄されます。ここではあまり経験がありませんが、私の直感は、予期しないタスクが本当に短い場合はヒューリスティックがうまく回復し、予期しないタスクが2秒以上続く場合はエレガントに処理できないことを教えてくれます。
- ASP.NETは、リクエストを早期に終了できません。つまり、クライアントが切断したり、リクエストがタイムアウトしたりする場合です。同期の場合、ASP.NETはリクエストスレッドを認識しており、それを中止できました。非同期の場合、ASP.NETは、セカンダリスレッドプールスレッドがその要求の「対象」であることを認識しません。キャンセルトークンを使用してこれを修正することは可能ですが、これはこのブログ投稿の範囲外です。
基本的に、ASP.NETへの非同期を許可しません-CPUにバインドされた同期コードを非同期ファサードの背後に隠すだけです。 Async
自体はI/Oバウンドコードに最適です。CPU(スレッド)を最高の効率(I/Oのブロッキングなし)で利用できるためですが、コンピュートバウンドコードがある場合は、 CPUを同じ程度まで使用する必要があります。
Task
とコンテキストの切り替えによる追加のオーバーヘッドを考慮すると、単純な同期コントローラーメソッドよりもさらに悪い結果が得られます。
真に非同期にする方法:
GetNewsFeedItemsForUser
メソッドはasync
に変換されます。
[Route("user/{userId}/feeds/async")]
[HttpGet]
public async Task<IEnumerable<NewsFeedItem>> GetNewsFeedItemsForUserAsync(string userId)
{
return await _newsFeedService.GetNewsFeedItemsForUser(userId);
}
それをするために:
async
バリアントを探します(ない場合-運が悪い場合は、競合するアナログを検索する必要があります)。