LoginSignup
7
6

More than 5 years have passed since last update.

System.Threading.Channels性能検証

Last updated at Posted at 2018-07-05

はじめに

この記事では、System.Threading.Channelsについて、色々と性能検証をしてみたので、その結果を書く。
この検証結果はあくまでも自分のコードの中でという条件付きなので、実際に使う際は性能比較は当然必要だが、何かの指針になれば幸い。
System.Threading.Channelsって何?という人は、System.Threading.Channelsを使うにて解説ページを書いたので、まずそちらを参照のこと。

テストコードについての指摘は随時歓迎。

検証項目について

さて、今回検証したのは以下の項目。

  • BlockingCollectionとの比較
  • SingleReader/SingleWriter設定時の性能差
  • AllowSynchronousContinuationsオンオフの性能差

BlockingCollectionとの比較

役割として最も重複すると思われるBlockingCollectionとの比較をしてみた。

シングルスレッド

単純に全て同一スレッドで行った場合の所要時間の比較。

結果は以下。

Method Toolchain LoopNum Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
ConcurrentQueueBenchWrite .NET Core 2.0 100000 1.816 ms 0.3366 ms 0.0190 ms 285.1563 285.1563 285.1563 1 MB
BlockingCollectionWrite .NET Core 2.0 100000 6.002 ms 1.6192 ms 0.0915 ms 281.2500 281.2500 281.2500 1 MB
ThreadingChannelsWrite .NET Core 2.0 100000 4.256 ms 0.5604 ms 0.0317 ms 281.2500 281.2500 281.2500 1 MB
ConcurrentQueueBenchWriteRead .NET Core 2.0 100000 3.243 ms 0.3243 ms 0.0183 ms 285.1563 285.1563 285.1563 1 MB
BlockingCollectionWriteRead .NET Core 2.0 100000 12.885 ms 2.9885 ms 0.1689 ms 281.2500 281.2500 281.2500 1 MB
ThreadingChannelsWriteRead .NET Core 2.0 100000 6.144 ms 2.7683 ms 0.1564 ms 281.2500 281.2500 281.2500 1 MB
ConcurrentQueueBenchWrite .NET Core 2.1 100000 1.872 ms 0.2313 ms 0.0131 ms 285.1563 285.1563 285.1563 1 MB
BlockingCollectionWrite .NET Core 2.1 100000 6.289 ms 1.4640 ms 0.0827 ms 281.2500 281.2500 281.2500 1.01 MB
ThreadingChannelsWrite .NET Core 2.1 100000 4.618 ms 1.6158 ms 0.0913 ms 281.2500 281.2500 281.2500 1.01 MB
ConcurrentQueueBenchWriteRead .NET Core 2.1 100000 3.593 ms 0.8562 ms 0.0484 ms 285.1563 285.1563 285.1563 1 MB
BlockingCollectionWriteRead .NET Core 2.1 100000 13.606 ms 2.1582 ms 0.1219 ms 281.2500 281.2500 281.2500 1.01 MB
ThreadingChannelsWriteRead .NET Core 2.1 100000 6.412 ms 0.6740 ms 0.0381 ms 281.2500 281.2500 281.2500 1.01 MB

マルチスレッド

マルチスレッド環境下での読み書き所要時間を計測。

結果は以下。

Method Toolchain LoopNum WriteTaskNum ReadTaskNum Mean Error StdDev
BlockingCollectionReadWrite .NET Core 2.0 10000 1 1 13.320 ms 3.9529 ms 0.2233 ms
ThreadingChannelReadWrite .NET Core 2.0 10000 1 1 11.181 ms 6.4940 ms 0.3669 ms
BlockingCollectionReadWrite .NET Core 2.1 10000 1 1 12.506 ms 2.1169 ms 0.1196 ms
ThreadingChannelReadWrite .NET Core 2.1 10000 1 1 11.865 ms 6.1815 ms 0.3493 ms
BlockingCollectionReadWrite .NET Core 2.0 10000 1 100 257.627 ms 143.1464 ms 8.0880 ms
ThreadingChannelReadWrite .NET Core 2.0 10000 1 100 173.708 ms 36.2240 ms 2.0467 ms
BlockingCollectionReadWrite .NET Core 2.1 10000 1 100 28.791 ms 280.0257 ms 15.8220 ms
ThreadingChannelReadWrite .NET Core 2.1 10000 1 100 107.643 ms 27.7679 ms 1.5689 ms
BlockingCollectionReadWrite .NET Core 2.0 10000 100 1 13.668 ms 1.5261 ms 0.0862 ms
ThreadingChannelReadWrite .NET Core 2.0 10000 100 1 13.040 ms 0.5886 ms 0.0333 ms
BlockingCollectionReadWrite .NET Core 2.1 10000 100 1 11.976 ms 0.3496 ms 0.0198 ms
ThreadingChannelReadWrite .NET Core 2.1 10000 100 1 11.635 ms 1.3184 ms 0.0745 ms
BlockingCollectionReadWrite .NET Core 2.0 10000 100 100 86.314 ms 32.3458 ms 1.8276 ms
ThreadingChannelReadWrite .NET Core 2.0 10000 100 100 6.034 ms 1.1547 ms 0.0652 ms
BlockingCollectionReadWrite .NET Core 2.1 10000 100 100 16.961 ms 63.2741 ms 3.5751 ms
ThreadingChannelReadWrite .NET Core 2.1 10000 100 100 2.876 ms 2.6431 ms 0.1493 ms

考察

単独スレッドにおける処理速度は、ConcurrentQueue > Channels > BlockingCollectionというのは恐らく間違いないと見ていいと思う。
ConcurrentQueueが最速なのは、担当する仕事の少なさに由来する。実際待ち受け処理が欲しい場合はセマフォ等を駆使することになるので、処理性能的には似たような所に落ち着くかもしれない。

読み出し側が単一の場合は、多くの場合においてChannelsの方がかなりいい成績を出したが、
読み出しをマルチで行った場合は、Channelsの方は著しく性能が落ちてしまった。
この辺り、読み:書き=M:Nで行おうとしている人は要注意。
なお、今回の場合、両方ともオプション無指定なので、チューニングすればもっと早くなる可能性はある。
特に後述するSingleReaderオプションを付ければ、差は更に広がる。

SingleReader/SingleWriterオンオフの性能差

次にSingleReaderとSingleWriterの設定値が、実際にどの程度性能に影響を及ぼすのか、検証してみた。

結果

Method Toolchain LoopNum SingleReader SingleWriter Mean Error StdDev Gen 0 Allocated
SettingBench .NET Core 2.0 10000 False False 1,091.2 us 352.63 us 19.924 us 15.6250 1.88 KB
SettingBench .NET Core 2.1 10000 False False 1,145.9 us 917.52 us 51.841 us 1.9531 2.05 KB
SettingBench .NET Core 2.0 10000 False True 1,093.2 us 159.06 us 8.987 us 19.5313 1.88 KB
SettingBench .NET Core 2.1 10000 False True 1,054.6 us 303.04 us 17.122 us 1.9531 2.05 KB
SettingBench .NET Core 2.0 10000 True False 882.1 us 84.44 us 4.771 us 16.6016 1.87 KB
SettingBench .NET Core 2.1 10000 True False 644.6 us 426.33 us 24.088 us 1.9531 1.85 KB
SettingBench .NET Core 2.0 10000 True True 875.6 us 54.41 us 3.075 us 15.6250 1.87 KB
SettingBench .NET Core 2.1 10000 True True 629.9 us 228.31 us 12.900 us 1.9531 1.85 KB

考察

特にSingleReaderについて、思ったよりも多大な効果が得られた。Channelsは読み出し側を一本に集約するという使い方がかなり多いと思うので、要件を確認して、可能ならばこのフラグを立てた方がいいと思う。

AllowSynchronousContinuationsオンオフの性能差

最後に、AllowSynchronousContinuationsのオンオフによる性能差を比較してみた。

結果

Method Toolchain LoopNum WriteTaskNum ReadTaskNum AllowAsync Mean Error StdDev Gen 0 Gen 1 Allocated
AllowAsyncBench .NET Core 2.0 10000 1 1 False 11.653 ms 0.7141 ms 0.0403 ms 250.0000 - 2.73 KB
AllowAsyncBench .NET Core 2.1 10000 1 1 False 11.013 ms 1.7051 ms 0.0963 ms 250.0000 - 2.69 KB
AllowAsyncBench .NET Core 2.0 10000 1 1 True 12.351 ms 3.2105 ms 0.1814 ms 250.0000 - 2.73 KB
AllowAsyncBench .NET Core 2.1 10000 1 1 True 10.855 ms 2.2629 ms 0.1279 ms 250.0000 - 2.69 KB
AllowAsyncBench .NET Core 2.0 10000 1 100 False 169.190 ms 65.8978 ms 3.7233 ms 17562.5000 - 39.01 KB
AllowAsyncBench .NET Core 2.1 10000 1 100 False 105.575 ms 64.6819 ms 3.6546 ms 15937.5000 - 28.03 KB
AllowAsyncBench .NET Core 2.0 10000 1 100 True 293.862 ms 13.3134 ms 0.7522 ms 35875.0000 - 39.09 KB
AllowAsyncBench .NET Core 2.1 10000 1 100 True 301.211 ms 146.6420 ms 8.2855 ms 33125.0000 - 28.24 KB
AllowAsyncBench .NET Core 2.0 10000 100 1 False 12.921 ms 1.6427 ms 0.0928 ms 265.6250 46.8750 31.09 KB
AllowAsyncBench .NET Core 2.1 10000 100 1 False 11.487 ms 0.8529 ms 0.0482 ms 281.2500 46.8750 20.99 KB
AllowAsyncBench .NET Core 2.0 10000 100 1 True 12.872 ms 0.2811 ms 0.0159 ms 265.6250 46.8750 31.09 KB
AllowAsyncBench .NET Core 2.1 10000 100 1 True 11.378 ms 1.1391 ms 0.0644 ms 296.8750 15.6250 20.99 KB
AllowAsyncBench .NET Core 2.0 10000 100 100 False 5.755 ms 1.0442 ms 0.0590 ms 492.1875 - 60.98 KB
AllowAsyncBench .NET Core 2.1 10000 100 100 False 2.810 ms 0.4191 ms 0.0237 ms 285.1563 - 40.82 KB
AllowAsyncBench .NET Core 2.0 10000 100 100 True 7.418 ms 3.4187 ms 0.1932 ms 562.5000 - 60.98 KB
AllowAsyncBench .NET Core 2.1 10000 100 100 True 4.918 ms 9.8277 ms 0.5553 ms 281.2500 - 40.83 KB

考察

コメントにはtrueの方がスループット出るみたいなこと書かれていたけど、結果を見るとfalseが全般的に成績が良いように見える。
もしかしたら何か見落としがあるかもしれないが、とりあえずfalseの方が良さそう。

終りに

今まで非同期FIFOとしてBlockingCollectionを使用していた部分については、Channelsに移行した方が良さそう。
特にW:R=N:1の場合は、かなり性能向上が望めるかもしれない。
後、SingleReaderフラグは結構影響が大なので、要件が許すならば必ず設定した方が良さそう。

7
6
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6