読者です 読者をやめる 読者になる 読者になる

職業プログラマの休日出勤

職業プログラマによる日曜自宅プログラミングや思考実験の成果たち。リアル休日出勤が発生すると更新が滞りがちになる。記事の内容は個人の意見であり、所属している(いた)組織の意見ではない。

java.lang.Runnable と java.lang.Thread の速度比較

Java

タイトルだけを見て「そもそも用途が違うじゃろがボケェ」と言うのはちょっと待って欲しい(笑)。

SwingのGUI部品

イマドキ使ってる人がどれほど居られるのかは分からないけれど、最近ちょっとJavaGUIフレームワークである Swing に触れる機会があった。
SwingではGUI部品が thread-safe ではないため、GUI部品に対する操作を全て Event Dispatch Thread(以下EDT)で実行する必要がある。これは大昔のGUIアプリケーションで言うところの「イベントループ」をぐるぐる回しているスレッド、に相当する。即ち、GUI部品を操作している間はあらゆるGUI操作がブロックされることになるのだ。
脇道に逸れるのはここまでにしよう。それでは、EDT以外のスレッドからGUI部品を操作するにはどうすれば良いのか? その答えが javax.swing.SwingUtilities の2つのインスタンスメソッド「invokeAndWait」と「invokeLater」だ。どちらもRunnableなオブジェクトを引数に取り、そのRunnableオブジェクトの内容を実行してくれる。前者のメソッドは同期的に、後者のメソッドは非同期的に実行してくれる。詳細は公式のJavaDocを参照されたし。

疑問

とあるコードを読んでいると、以下のような箇所があった。

	SwingUtilities.invokeAndWait(new Thread() {
		public void run() {
			// 何か処理
		}
	});

invokeAndWaitが取る引数って java.lang.Runnable 型なのに、instantiationにオーバーヘッドのありそうな java.lang.Thread をわざわざ採用するのってバカなんじゃないの?!と思った。そこで、以下のような実験をすることにした。

実験

概要
  • java.lang.Runnable または java.lang.Thread のどちらかによる anonymous class の instantiation を行う。
  • 上記の instantiation の結果のオブジェクトを用いて、javax.swing.SwingUtilities#invokeAndWait を呼び出したり、invokeLater を呼び出したり、はたまた何もしなかったり(これは instantiation のみの所要時間の計測)。

を10万回×20セット実行し、その所要時間を比較する。

事前の予想

そりゃあもちろん、java.lang.Runnable を使った方が速いでしょ! …と思っていた。

実験に使ったコード

ここに貼ろうかと思ったけど、100行近いコードになったから、githubに置いた。
https://github.com/motooka/JavaTests/blob/master/test/SwingSpeedTest.java

結果その1:instantiationのみの速度比較

java.lang.Thread での anonymous class では10万回で平均118ms(ミリ秒)だったのに対し、java.lang.Runnable では平均1msに満たなかった。やはり java.lang.Thread の instantiation にはオーバーヘッドがあるということが確認できた。

結果その2:invokeAndWait実行時の速度比較

java.lang.Thread での anonymous class では10万回で平均2198ms(ミリ秒)だったのに対し、java.lang.Runnable では平均2180msとなった。僅かに Runnable の方が速いが、これは誤差の範囲で、ほぼ同じ結果と見た方が良さそうだ。これは意外。何が起きているのだろうか…?

結果その3:invokeLater実行時の速度比較

java.lang.Runnable で実験したら、java.lang.OutOfMemoryError で死んだww
実行待ちのキューに数万件も突っ込んだら、そりゃ死ぬかww
これに対し、java.lang.Thread で実験した時は無事に終了した。instantiationで手間取っている間に実行待ちのキューを上手く消化できていっているのだと思われる。Runnableの時のは所謂「輻輳」が発生しているのだろう。

今後の調査

  • SwingUtilities#invokeAndWaitの中では何をやっているのか、ソースを読んでみたい。
  • SwingUtilities#invokeLaterを大量に呼び出したときは死んでしまう訳だが、実行待ちのキューの長さを知ることはできるのかどうか調べてみたい。役には立たない気もするが。(そもそも大量に呼び出しちゃう時点で重大な設計ミスだろうし)

検証環境