[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[jfriends-ml 10110] Re: Java 言語で学 ぶデザインパターン入門第 6 回議事録
村山です.
> モニタはロックとそれを獲得しようとするスレッドの待機セット
> で、オブジェクトが持っているものですから、
> スレッドが取得しようとするのはロックではないでしょうか?
並列処理における排他制御としては「ロック」「セマフォ」
「モニタ」は全て別物です.単なるロック(ミューテックスロック)
は最も原始的な排他制御機構で,ロック変数のON/OFFをatomic
(不可分)に行う処理(通常はOSのシステムコール)なはずです.
#で,多分いいはず.
ただ,もっと一般的な意味で「ロック」という単語使うこともない
わけではないので,必ずしも間違いとは言い切れません.まあ
「インスタンス」のことを「オブジェクト」と呼ぶようなもので,
間違いじゃないけど誤解を招きかねない表現なんで,避けた方が
無難だと思います.
#あるいは「指し示すもの」という意味で「ポインタ」を使うとか.
> あと、同じスレッドが同じロックを獲得するのにデッドロック
> になるかどうかは、ロックの実装によるはなしで概念ではない
> と思うのですが、どうなんでしょうか?
> #pThredとかはだめなんでしたっけ?
通常「ロック」は単にON/OFFなんで,どのスレッドが取得したとか
は全く認識しません.異なるスレッドだろうが同じスレッドだろうが,
二度目を取ろうとするとロックを獲得できず,下手するとその時点で
待ちに入ってデッドロックになります.待ち状態に入ったが最後,
自分が持ってるロックを開放する術はないですからね.
これがJavaのsynchronizedメソッドなんかだと,同じスレッドで
何回獲得しても,自動的にカウンタのインクリメントをして
対応してくれるので,シングルスレッドによるデッドロックなんて
のは起こりません.おまけに例外が発生した場合でも,必要に応じて
自動的にモニタの開放をしてくれますし.これに対し「ロック」の
場合はこれら全てを自分で管理する必要があります.
#C言語のポインタバグなんかもそうですが,
#Javaの場合,そういった初歩的なバグが絶対起こらないので,
#Javaから入った人にはこの苦労はわからないでしょうね.
> そうなのかよくわからないので、試してみました。
中略
> もちろん、僕のマシンは1CPUです。うーん。なんでこないだのプログラム
> は差がつかなかったのに、今度はついたのかわかりません。
> #しかも平均しても2.5倍くらいはやくなっている。。。。
正直言って,この結果は予想してませんでした.
一つの可能性ですが,大域的な最適化を行っているのでは.
---
b.append(foo);
b.append(bar);
b.append(baz);
の場合,
aload_1
getfield #10
invokevirtual #20
getfield #11
invokevirtual #20
getfield #12
invokevirtual #20
って感じになると思いますが,これがインライン展開されて,
aload_1
getfield #10
モニタ取得
appendの処理
モニタ開放
getfield #11
モニタ取得
appendの処理
モニタ開放
getfield #12
モニタ取得
appendの処理
モニタ開放
となって,
「getfieldくらいなら中にいれちゃっても,まあいいか」
と判断して,
#入れるのはそれほど問題ないが,出すのはバグになると思う.
aload_1
モニタ取得
getfield #10
appendの処理
モニタ開放
モニタ取得
getfield #11
appendの処理
モニタ開放
モニタ取得
getfield #12
appendの処理
モニタ開放
そうすると連続する取得と開放は無意味だし,この程度
なら連続して実行しても許容範囲だと判断して,
aload_1
モニタ取得
getfield #10
appendの処理
getfield #11
appendの処理
getfield #12
appendの処理
モニタ開放
となってる「かも」しれません.この場合は前後にモニタを
くっつけても,同様にして取り除かれるので,全体としての
速度は同じになるでしょう.
ところが,
for (int i=0; i<v.size(); i++) {
String s = (String) v.elementAt(i);
if (s.length() > maxLen) {
maxLen = s.length();
longest = s;
}
}
ではそういう風な処理はできないんですよね.順番を入れ換えると
アルゴリズムも変わってしまい,バグが出る恐れがあるので.
で,これに対する最適化も,「v.elementAt(i);」の前後に跨る
ような最適化は不可能になる.簡単に言えば次の1,2,3の部分
はそれぞれ独立に最適化するしかない.
#クラスファイルレベルじゃ,必ずしもこの順序で並んでいる
#わけではない.あくまでイメージとして捕らえてください.
1, for (int i=0; i<v.size(); i++) {
2, String s = (String) v.elementAt(i);
3, if (s.length() > maxLen) {
maxLen = s.length();
longest = s;
}
}
これが,次のようになると,
synchronized (v) {
for (int i=0; i<v.size(); i++) {
String s = (String) v.elementAt(i);
if (s.length() > maxLen) {
maxLen = s.length();
longest = s;
}
}
「v.elementAt(i)」の段階でモニタを保持していることが保証
されているし,synchronized文は全部まとめて実行してもロジックの
上で問題がないことが保証されるので,ループ全体の最適化も可能に
なる.(と思う.)
たとえば
1,v.elementAt(i)のインライン展開
2,冗長なモニタの取得/開放処理の削除
3,モニタ取得のその前後に跨る,ループも含めた最適化
#「ソフトウエアパイプライニング」とか「ループアンローリング」
#とかいう有名な手法があったと思う.
なんて感じで最適化するのでは.
まあ,こういうことが起こるのも,同期の取り方自体もアルゴリズム
の一部だからですね.アルゴリズムが異なるのだから,処理時間
が異なっても当然でしょう.
#上のようなことまで考えて「最適化」しても,JavaVMが変わると
#元の木阿弥.「実装依存の最適化」は無駄な努力以外の何物
#でもないと思う.
ところで,
b.append(foo);
b.append(bar);
b.append(baz);
c.append(foo2);
c.append(bar2);
c.append(baz2);
と
b.append(foo);
c.append(foo2);
b.append(bar);
c.append(bar2);
b.append(baz);
c.append(baz2);
だと,やっぱり性能が変わったりするんですかね?
#素直にs1 = foo+bar+baz;とか書いた方がましかも.(-_-)
#だいいち,こっちの方が書くのが楽.
> パフォーマンスチューニングの道は険しいみたいです。
#うーん.まさにFINAL FRONTIER.
あ,ちなみにJalapenoの記事はお勧めです.
http://www-6.ibm.com/jp/developerworks/java/jalape-vm-index-j.html
結構難易度は高めですが,これが理解できない人は下手な
最適化はしない方が無難だと思います.