2011-12-16 6 views
10

UIレスポンスの絶え間ない探求では、メインスレッドがブロッキング操作を実行するケースについてより深く理解したいと思います。メインスレッドがブロックするとブレークポイント/ログ/可視性が向上しますか?

「デバッグモード」や余分なコード、フックなどを探しているので、ブレークポイント/ログ/ヒットするものを設定して、私のメインrunloopの終了時にアイドル状態になる以外に、I/O(または実際には何らかの理由で)スレッドが「自発的に」ブロックします。

過去には、ルンループオブザーバーを使用してランロップサイクルの壁掛け時間を調べましたが、これは問題を見る上では有益ですが、確認するまでには、何のために良いアイデアを得るのは遅すぎます。あなたのコードは既にrunloopのサイクルで実行されているので、やっていました。

私はUIKit/AppKitによって実行される操作がメインスレッドのみであることを認識しています。これはI/Oを引き起こし、メインスレッドをブロックする原因になります。ペーストボードは潜在的にブロッキング、メインスレッド専用操作のようですが)何かが何もないよりも優れています。

誰でも良いアイデアはありますか?役に立つと思われるもののようです。理想的なケースでは、アプリケーションのコードがrunloop上でアクティブな間にメインスレッドをブロックすることは決してありません。このようなことは、できるだけそのゴールに近づくのに非常に役立ちます。

答えて

11

私は今週の自分自身の疑問に答えようとしました。記録のために、この努力は非常に複雑なものに変わったので、Kendall Helmstetter Glenの示唆したように、この質問を読んでいるほとんどの人々はおそらく、単にInstrumentsと突き合わせるべきです。群衆のマゾイストのために、読んでください!

問題を再現することから始めるのが一番簡単でした。私は正当なアイドル時間ではありません システムコール/ mach_msg_trapに費やされた時間の長い期間に警告することにしたい

:ここでは私が思いついたものです。 「合法的な アイドル時間」は、machOSからの次のイベントを待っているmach_msg_trapに費やされた時間として定義されます。

また、重要なことに、私は長い時間がかかるユーザーコードについては気にしませんでした。この問題は、InstrumentsのTime Profilerツールを使用して診断し理解するのが非常に簡単です。私はブロックされた時間について特に知りたがっていました。 Time Profilerを使用してブロックされた時間を診断することも可能ですが、その目的で使用することはかなり難しくなっています。同様に、System Trace Instrumentはこのような調査にも役立ちますが、きわめてきめ細かで複雑です。私はもっ​​と簡単なものを求めていました。

get-goから、ここで選択したツールがDtraceであることが明らかでした。 kCFRunLoopAfterWaitingkCFRunLoopBeforeWaitingを発射したCFRunLoopオブザーバーを使い始めました。私のkCFRunLoopBeforeWaitingハンドラへのコールは "正当なアイドルタイム"の開始を示し、kCFRunLoopAfterWaitingハンドラは正当な待機が終了したという私へのシグナルになります。私はDtraceのPIDプロバイダを使用して、正当なアイドルをアイドル状態からブロックする方法としてこれらの関数の呼び出しをトラップします。

このアプローチは私を始めましたが、結局は欠陥があることが判明しました。最大の問題は、多くのAppKit操作が同期であることです。UI内のイベント処理をブロックしますが、実際には呼び出しスタック内のRunLoopを低くします。 RunLoopのこれらのスピンは、その時間中にユーザーがUIとやりとりすることができないため、(私の目的のために)「正当な」アイドルタイムではありません。 RunLoop指向のI/Oを見ているバックグラウンドスレッド上のrunloopを想像してみてください。しかし、メインスレッドでUIがブロックされても、まだブロックされています。例えば、私はIBActionに次のコードを入れて、ボタンからそれをトリガー:

NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL: [NSURL URLWithString: @"http://www.google.com/"] 
                cachePolicy: NSURLRequestReloadIgnoringCacheData 
               timeoutInterval: 60.0];  
NSURLResponse* response = nil; 
NSError* err = nil; 
[NSURLConnection sendSynchronousRequest: req returningResponse: &response error: &err]; 

コード紡糸から実行ループを防止していないこと - のAppKitはsendSynchronousRequest:...コールの内側にあなたのためにそれを回転させる - しかし、それそれが返るまでユーザーがUIとやりとりするのを防ぎます。これは私の心には「正当なアイドル」ではないので、どのアイドルがどのようなものであったかを整理する方法が必要でした。 (CFRunLoopObserverのアプローチでは、最終的な解決策ではないコードの変更が必要になるという欠点もありました)。

私は自分のUI /メインスレッドをステートマシンとしてモデル化することに決めました。 LEGIT_IDLE、RUNNINGまたはBLOCKEDの3つの状態のうちの1つにあり、プログラムが実行されたときにこれらの状態の間を行き来します。私はDtraceのプローブを思いついて、それらの遷移を捕まえることができました。私が実装した最後のステートマシンは、3つの州だけのものよりかなり複雑ですが、それは2万フィートの視点です。

上記のように、どちらのケースもmach_msg_trap()__CFRunLoopRunになるので、正当なアイドル状態から悪いアイドル状態を選別するのは簡単ではありませんでした。コールスタックに単純なアーティファクトが1つ見つかりませんでしたが、その違いを確実に伝えるために使用できました。ある機能の簡単なプローブが私を助けてくれるとは思われません。私は正当なアイドル状態と悪いアイドル状態の様々なインスタンスでスタックの状態を見るためにデバッガを使用しました。

#0 in mach_msg 
#1 in __CFRunLoopServiceMachPort 
#2 in __CFRunLoopRun 
#3 in CFRunLoopRunSpecific 
#4 in RunCurrentEventLoopInMode 
#5 in ReceiveNextEventCommon 
#6 in BlockUntilNextEventMatchingListInMode 
#7 in _DPSNextEvent 
#8 in -[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:] 
#9 in -[NSApplication run] 
#10 in NSApplicationMain 
#11 in main 

は、だから私は私が到着した時に確立するネストされた/チェーンのpidプローブの束を設定するために努め:私は合法的アイドル時に、私は(一見確実に)このようなコールスタックを参照してくださいねと判断しました、その後、この状態を残した。残念なことに、何らかの理由でDtraceのpidプロバイダは、任意のシンボルすべてに対してエントリとリターンの両方を普遍的に調べることができないようです。具体的には、pid000:*:__CFRunLoopServiceMachPort:returnまたはpid000:*:_DPSNextEvent:returnでプローブを取得できませんでした。詳細は重要ではありませんが、他のさまざまな動きを見て特定の状態を把握することで、私が入力されて正規のアイドル状態から抜け出たときに(やはり一見信頼できる)確立することができました。

次に、RUNNINGとBLOCKEDの違いを調べるためのプローブを決定しなければなりませんでした。それは少し楽だった。最後に、私はBSDシステムコール(Dtraceのシステムコールプローブを使用)とmach_msg_trap()(pidプローブを使用)を呼び出して、legit idleの期間中BLOCKEDになることはないと考えました。 (私はDtraceのmach_trapプローブを見ましたが、私が望んでいなかったので、pidプローブを使用しました。)

最初は、実際に測定するためにDtrace schedプロバイダrealブロックされた時間(スレッドがスケジューラによって中断された時間)がかなり複雑になりました。私は自分自身に考えました。「もし私がカーネル内にいたら、スレッドが実際に眠っているかどうか?それはユーザーにとってはまったく同じです。ブロックされています。したがって、最終的なアプローチはすべての時間を(syscalls || mach_msg_trap()) && !legit_idleで測定し、そのブロック時間を呼び出します。

この時点では、(例えば、sleep(5)への呼び出しのような)長い持続時間の単一のカーネル呼び出しをキャッチするのは簡単です。しかし、より多くの場合、UIスレッドのレイテンシは、カーネルへの複数回の呼び出し(read()やselect()の何百回もの呼び出しを考える)に蓄積される小さな待ち時間に起因することが多いため、イベントループの1回のパスにおける全体のシステムコール量または​​時間が特定のしきい値を超えました。私は、さまざまなタイマーを設定し、各状態で費やされた蓄積された時間を記録し、ステートマシンのさまざまな状態にスコープし、BLOCKED状態から移行したときにアラートをダンプし、しきい値を超えました。この方法は明らかに誤解の対象となるデータを生成するか、赤ちゃん全体(つまり、ランダムな比較的迅速なシステムコールで、警告しきい値を超えてしまう)が発生する可能性があります。

最後に、DtraceスクリプトはステートマシンをD変数に保ち、説明されたプローブを使用してステート間の遷移を追跡し、ステートマシンが(ステートマシンが)特定の条件に基づいて移行状態に移行します。私は、ディスクI/O、ネットワークI/O、スリープ()の呼び出しを行い、合法的な待機に関連するデータから注意をそらすことなく、これら3つのケースすべてを捕まえることができた、 。これはまさに私が探していたものでした。

この解決策は明らかに非常に脆弱であり、徹底的にひどいがほとんどすべての点でそうです。 :)私や他の誰にとっても役立つかもしれませんが、楽しい演習だったので、私はその話をして、Dtraceの結果としてのスクリプトを共有すると思っていました。たぶん誰かがそれが役に立つと思うかもしれません。また、Dtraceスクリプトの作成に関して相対的な​​ということを告白しなければならないので、私は何百万もの間違ったことをしたと確信しています。楽しい!

ラインに投稿するにはあまりにも大きかったので、親切にこっち@Catfish_Manによってホストされています:MainThreadBlocking.d

+0

私はまだこれを実行する試していないが、関係なく、それが動作するかどう、これはおそらく最高です私が見たスタックオーバーフローの答え。私の一言は、あなたがダウンロードしたサイトには何もしないで.exeをダウンロードするようなことはできないということです。 OSXではあまり役に立ちません;) –

1

これは本当にタイムプロファイラの仕事の種類です。私はスレッドごとにコードで時間が費やされている場所を知ることができると思うので、コードを実行して潜在的にUIをブロックしていたものについて答えを得るために、

関連する問題