[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[jfriends-ml 10096] Re: Java 言語で学 ぶデザインパターン入門第 6 回議事録



こんばんは。武川です。
ずいぶん長くなってしまいました。。。

From: Murayama Toshikiyo <murayama@xxxxxxxxxxxxx>
Subject: [jfriends-ml 10095] Re: Java 言語で学ぶデザインパターン入門第 6 回議事録
Date: Sun, 05 May 2002 17:40:42 +0900

> (株)ネットジーンの村山です.
> 
> 断言はできませんが,
> > > StringBuffer b = new StringBuffer();
> > > synchronized (b)
> > > {
> > >   b.append(foo);
> > >   b.append(bar);
> > >   b.append(baz);
> > > }
> これって早くなりますかね?
> 
> モニタの取得/開放が一回ずつ増える分だけ,かえって
> 遅くなりそうな気がしますが.ロジックから見ても,よほど
> 器用な(或いはひねくれた)最適化をしない限り,まず早く
> なることはないと思います.

そう言われるまでなんとなくでしか考えていなかったので、
プログラムを書いて試してみました。

> > > synchronizedの実装にもよるだろうけど、確かに速そうです。
> 実装者の考え方としては,
> 1,モニタを二回以上取得するのは稀だから,二回以上の時が
>   若干遅くなっても一回だけの時に最適化してモニタを実装する.
> 2,二回以上の時も一回だけの時も,同じになるように実装する.
> 
> のどちらかでしょう.2の場合はまだいいんですが,
> 1の場合は明らかに遅くなりますね.

3.既にロックを取得している場合は、ロック獲得処理を行なわない。
という処理の仕方はないのでしょうか?

#僕はそういう処理の仕方もあるかなと思ったので速くなるのかと
#おもってました。

で、以下のプログラムを作って時間を計ってみたところ、
「差はない」
というのが結果でした。

まず環境を載せておきます。

Windows2000Server(SP2)
CPU Pentium IV 1.6GHz
Memory 1024MB
java version "1.4.0"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.0-b92)
Java HotSpot(TM) Client VM (build 1.4.0-b92, mixed mode)

次にプログラムを載せます。

--------------ここから--------------
/** sychronizedしてからappendを呼ぶ */
public class LockTestOuter {
    static int loop= 1024;
    static int max = 1024 * 1024 + 1;
    public static void main(String args[]){
	while(loop < max) {
	    long start = System.currentTimeMillis(); 
	    outSynchronized();
	    long time = System.currentTimeMillis() - start; 
	    System.out.println(loop+" "+ time);
	    loop *= 2;
	}
    }

    public static void outSynchronized(){
	StringBuffer buf = new StringBuffer();
	synchronized(buf){
	    for(int i= 0;i< loop ;i++){
		buf.append("aaa");
	    }
	}

    }

}

--------------ここまで--------------

--------------ここから--------------
/** sychronizedせずにappendを呼ぶ */
public class LockTestInner {
    static int loop= 1024 ;
    static int max = 1024 * 1024 + 1;
    public static void main(String args[]){
	while(loop < max ) {
	    long start = System.currentTimeMillis(); 
	    normal();
	    long time = System.currentTimeMillis() - start; 
	    System.out.println(loop+" "+ time);
	    loop *= 2;
	}
    }

    public static void normal(){
	StringBuffer buf = new StringBuffer();
	for(int i= 0;i< loop ;i++){
	    buf.append("aaa");
	}
    }
}
--------------ここまで--------------

ループをして何度もメソッドを呼んでいるのは、
サイズで変化するのか見たかったのと、HotSpot
は何回かメソッドを呼ばないと最適化しないという
ような記憶(怪しげ)があったからです。

そして結果ですが、プログラムを何度か実行して
一番短かい時間で終わったものを載せています。

E:\project\java>java LockTestOuter
java LockTestOuter
1024 0
2048 0
4096 0
8192 10
16384 0
32768 20
65536 40
131072 70
262144 130
524288 221
1048576 410

E:\project\java>java LockTestInner
java LockTestInner
1024 10
2048 0
4096 0
8192 0
16384 10
32768 11
65536 40
131072 80
262144 120
524288 230
1048576 411

10msのずれが途中で入っているのはGCの影響と考えられます。
結果を見た限りでは、ほとんど差がありません。
というわけで、他のスレッドと競合しない場合は、
synchronizedを外側で宣言しても意味がないということに
なります。

あと、JDK1.1.8_009 で試した結果も載せておきます。

E:\project\java>java LockTestOuter
java LockTestInner
1024 0
2048 0
4096 0
8192 10
16384 0
32768 10
65536 30
131072 40
262144 90
524288 171
java.lang.OutOfMemoryError
	at java.lang.StringBuffer.append(Compiled Code)
	at LockTestInner.normal(Compiled Code)
	at LockTestInner.main(Compiled Code)

E:\project\java>java LockTestOuter
java LockTestOuter
1024 0
2048 0
4096 0
8192 10
16384 0
32768 10
65536 20
131072 51
262144 90
524288 170
java.lang.OutOfMemoryError
	at java.lang.StringBuffer.append(Compiled Code)
	at LockTestOuter.outSynchronized(Compiled Code)
	at LockTestOuter.main(Compiled Code)

サイズが大きいとErrorになりますが、実行速度自体は
JDK1.1.8の方が速いようです。不思議ですね。

今後の調べてみたいこととしては、
・JDK1.0.2のような昔のバージョンでも同じなのか?
・synchronizedがないappendは本当に速いのか?
がありますが、気がむけばしらべてみたいと思います。

なんかおかしい点など合ったら指摘して頂けると嬉しいです。
最後に、こういう面白い機会を作ってくれた村山さんと、高橋
さんにに感謝します。

ではでは。