月別アーカイブ: 2013年11月

GCDのブロックの実行時間の許容範囲は5ミリ秒くらい?

 Grand Central Dispatch+Blocks、便利である。おいらの作ってるアプリも非同期で動作する部分のほとんどがGCDに書き改められた。

 さて、いい気になってあれもこれもGCDで処理させていたら、おかしな挙動に出くわすようになった。くそやったらと遅くなるのだ。何故? Why? と思って調べてみると、dispatch_queueのスレッドが大量に作られていた。
スクリーンショット 2013-11-20 2.53.30
 え? GCDって最適化された数のスレッドにブロックをいいカンジに割り振ってくれるんじゃなかったの? なんでこんな大量にスレッド作られちゃってるの??

テキトーに確認してみた。

 こんなカンジのコードをiPad2とiPad mini Retinaの2台で走らせて最大並列実行数をチェックしてみる。ブロックの実行にかかる時間=スリープ時間。

 実行時間が0.1ミリ秒の場合どちらの端末でも最大並列実行数は2だった。CPUコア数と同じでいいカンジである。
 実行時間を徐々に長くしてみると1ミリ秒でも最大数並列実行数は2のまま。5ミリ秒でも同じく。

 ところが実行時間がiPad2では6ミリ秒、iPad mini Retinaでは7ミリ秒になったところででスレッド数がガンガン膨れ上がった。ファック!!!!

 どうやら用意されたスレッドの全てが時間のかかるブロックに塞がれてしまってる場合、いつになったらそのブロックの実行が終わるのか判らんので残りのブロックが待たされっぱなしになるのを防ぐために実行スレッドをひとつ増やすっぽい。その閾値が5ミリ秒強のようだ。性能差が何倍もあるiPad2とiPad mini Retinaでおおよそ同じ値なのでこの辺CPU時間でなく実時間ベースで閾値があるのであろう。

 ここで、10ミリ秒とかかかるブロックを大量にqueueに投入した場合、あっという間にデフォルトで用意されたスレッドが埋まり、しびれを切らして実行スレッドが増やされるんだけどそのスレッドも時間のかかるブロックで埋まり。。。。 が繰り返されてスレッドがガンガン増殖していく模様。

 なお上に書いたのは簡単な計測までは実際にやったけどその後の考察は単なる憶測、おいらの妄想です。GCDはソース公開されてるんだからそれくらいコード読んで調べろよってハナシなんですが納期に追われるだけのウンコプログラマなのでそこまでする時間的余裕がありません。あ、ウソ書きましたすみません。そこまでするスキルがありません。

 この問題、そんな粗い粒度のもんGCDぶっこむなよってハナシなのかもしれませんが、粒度細かすぎればGCDのオーバーヘッドで相殺されちゃうし、ある程度の処理時間があるけど5ミリ秒以内っていうのは何も考えずにGCD使える範囲って実は結構狭いんじゃないか的な。
 また入力データ長によって処理時間が変わるブロックの場合、小さなデータであればそれがどれだけ大量にあってもいいカンジに処理してくれるのに、大きなデータがまとまった数やってきて実行時間が5ミリ秒を超えた途端に無意味に大量にスレッドが作られ始めて全体の動作が緩慢になってしまう。これ、知らずに使ってると結構落とし穴。っていうかおいらがごぼっとハマった。

 そんなこんながあって、同時に処理されるブロックの数が指定数以下になるようdispatch_queueを外側から包むクラスを作成した。外側にもひとつ自前キューを作ってブロックをそこに溜めて、コーヒーサーバにお湯を注ぐかのようにdispatch_queueのブロック実行が完了してから次のブロックをdispatch_queueに注いで同時実行数を指定以下に押さえる真似してます。
 大きなブロックによって処理スレッド数が無意味に増殖していくのを防ぐ他に、最大同時ダウンロード数の指定などなどいろいろ便利に使える予定。

本来なら最大同時実行数の指定はGCD側で対応してもらいたいものである。

おわり

いんさいど おぶ ふぃ〜るどあくせす

おいらは生粋のうんこプログラマでどれくらいうんこかというとコレにものすごい勢いで当てはまるうんこプログラマですが、これでもプログラマの端くれとしてiOS用の地図アプリを作ってたりなんかしまして、極稀に地図描画エンジン部分のところをお褒めいただいたり、どんな実装になってるのかって質問をほんのちょびっと頂いたりするので、うんこがその気になってどんなコードで動いているかをちょっとずつつらつら書いて恥を晒していこうとかいう企画。

を、始める予定

なんだけどうんこプログラマなので目下の仕事を片付けるのが精一杯なのでいつ始まるかは未定(‘A`)