Objective-C++」カテゴリーアーカイブ

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側で対応してもらいたいものである。

おわり

Lambda/Blocks の引数内のObjective-C++クラス派生関係

オブジェクトを引数に取るLamdaやBlockの変数に対して、そのオブジェクトと派生関係にあるクラスのオブジェクトを渡すとどうなるか試してみた。
環境はXCode4.5。

まず、LambdaでC++ classの場合。

一つ目は A0 に A0 を渡してるから当然OK。
二つ目はまったく関係のないクラスのオブジェクトを渡してるから当然エラー。
三つ目はA0を扱う関数変数にその派生クラスを扱う関数を渡している。これはアップキャストにあたるので当然エラー。
四つ目はA1を扱う関数変数にその基底クラスを扱う関数を渡している。これはダウンキャストにあたるので当然OK。
という、至極当たり前な結果になった。

同じ事を Blocks と Objective-C classでやっても同じ結果になる。

さて、ここからが本題。
じゃあ、LambdaでObjective-Cクラスを扱ったらどうなるの? BlocksでC++クラスを扱ったらどうなるの?
(クラス関係は上に出てきたまま)

LambdaでObjective-Cクラスを扱った場合、何故かアップキャストになる代入に対してもエラーが発生しない。ただしクラス関係が全く関連がない場合はきちんとエラーになるのでこの挙動はようわからん。。。
そしてBlocksでC++クラスを扱った場合、ダウンキャストになる代入に対してもエラーになる。。。 これは微妙に困る。
いやLambda使えよ、って言われてもGCDやる時はそうはいかないじゃないですか。

この辺の挙動将来はちゃんとしてくれるだろうか。

ARCでstructの中にObjective-Cインスタンスを入れると出るエラーの回避方法

(2013.05.28)修正

ARCではstructの中にObjective-Cのメンバを入れると怒られる。

これ、Objective-CコンパイラではなくObjective-C++コンパイラを使うようにする、つまりファイルの拡張子の.mを.mmにするとエラーが出なくなる。

C++コンパイラwith ARC だと、struct/class内のObjective-Cインスタンスなメンバーはdestructorで自動的にreleaseされるようになる。コンパイラの管理下に置けるからエラーにならないのね。しかも、メンバ変数にインスタンスを代入すると自動的にretainされる。retainな@property的な挙動になる模様。

なんと素晴らしいことでしょう。

ただARC化するには、これらのARCでのC++クラス/構造体内のObjective-Cメンバ変数の挙動にを踏まえた上で、これまで自前でretainやreleaseしてたところを検証していかなければならないので面倒っちょいは面倒っちょいが、まあ、structのままでARC化する道はあるってことで。