昨日は
夜になって鳥乃が激しく嘔吐し、しまいには血の混じった胃液まで吐き出したので急遽近所の救急病院へ行きました。そこで吐き止めと失われた水分を補給するために点滴してもらっている間に鳥乃はすやすや眠ってしまい、0:00 過ぎに家に帰ってきました。
今日、かかりつけの病院に行って薬をもらってきたんですが、「今日一日は固形物は避けるように」と言われたのに早くもいつもの食い意地を発揮してヨーグルトやプリンをぱくぱく。元気になったのはいいけどちょっと心配だよ…。
不思議な結果
64bit 環境での性能向上率をいろいろ調べていた時のこと。
C で言う long long int、64bit 整数に関する演算速度を調べてみようと、下記のようなテストプログラムを作りました。
#include <stdio.h>
int main()
{
long long int a, b, c, d;
int i;
a = b = c = d = 1;
for ( i=0; i<100000000; i++ )
{
a = d % 0xFFFFFFFFFFFFFFFLL;
b = a + a + 1LL;
c = b * 4LL;
d = c / 2LL;
}
printf("i:%d, a:%lld, b:%lld, c:%lld, d:%lld\n", i, a, b, c, d);
}
まず、このプログラムを今の僕の手元の環境でコンパイル、実行した時の結果は下記の通りとなりました。
kazawa@tpx20:~$ uname -a
Linux tpx20 2.6.3 #1 Sat Mar 6 11:30:13 JST 2004 i686 unknown
kazawa@tpx20:~$ cat /proc/cpuinfo
processor : 0
vendor_id : GenuineIntel
cpu family : 6
model : 8
model name : Pentium III (Coppermine)
stepping : 3
cpu MHz : 597.526
cache size : 256 KB
fdiv_bug : no
hlt_bug : no
f00f_bug : no
coma_bug : no
fpu : yes
fpu_exception : yes
cpuid level : 2
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 sep mtrr pge mca cmov pat pse36 mmx fxsr sse
bogomips : 1179.64
kazawa@tpx20:~$ gcc -v
Reading specs from /usr/lib/gcc-lib/i386-linux/2.95.4/specs
gcc version 2.95.4 20011002 (Debian prerelease)
kazawa@tpx20:~$ icc -V
Intel(R) C++ Compiler for 32-bit applications, Version 7.0 Build 20021021Z
Copyright (C) 1985-2002 Intel Corporation. All rights reserved.
FOR NON-COMMERCIAL USE ONLY
GNU ld version 2.12.90.0.1 20020307 Debian/GNU Linux
Supported emulations:
elf_i386
i386linux
/usr/lib/crt1.o: In function `_start':
/usr/lib/crt1.o(.text+0x18): undefined reference to `main'
kazawa@tpx20:~$ gcc -O3 -o multi_gcc multi.c
kazawa@tpx20:~$ time ./multi_gcc
i:100000000, a:436906, b:873813, c:3495252, d:1747626
real 0m19.619s
user 0m19.201s
sys 0m0.004s
kazawa@tpx20:~$ icc -O3 -mp1 -prec_div -fp_port -rcd -tpp6 -mcpu=pentiumpro -xiMK -o multi_icc multi.c
kazawa@tpx20:~$ time ./multi_icc
i:100000000, a:436906, b:873813, c:3495252, d:1747626
real 0m13.636s
user 0m13.346s
sys 0m0.003s
ここまでは、「ふむふむ、やっぱり Intel コンパイラの方が結構効率いいんだなぁ (最適化オプションが違い過ぎかも)」とか思いつつ普通に考えていました。
実際にはここでさらに 64bit バイナリを作ってテスト…という作業を行っていたのですが、その後戯れに、「Java だとどうだろう?」と思いつき、テストしてみることにしました。
まず、先程の C のプログラムとほぼ同じ内容の下記のようなプログラムを作成します。
public class multi {
public static void main(String[] args) throws Exception {
long a, b, c, d;
int i;
a = b = c = d = 1;
for (i = 0; i < 100000000; i++) {
a = d % 0xfffffffffffffffl;
b = a + a + 1l;
c = b * 4l;
d = c / 2l;
}
System.out.println("i:" + i + ", a:" + a + ", b:" + b + ", c:" + c + ", d:" + d);
}
}
Java の場合は long が 64bit なのでこれで OK なのです。さてその後、普通に javac でコンパイルして実行してみました。
kazawa@tpx20:~$ java -server -version
java version "1.4.2_01"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2_01-b06)
Java HotSpot(TM) Server VM (build 1.4.2_01-b06, mixed mode)
kazawa@tpx20:~$ javac multi.java
kazawa@tpx20:~$ time java -server multi
i:100000000, a:436906, b:873813, c:3495252, d:1747626
real 0m7.485s
user 0m7.223s
sys 0m0.033s
ちょっとびっくりなことに、VM の起動時間まで含まれてしまう time による計測にもかかわらず、icc による結果に比べても倍くらい高速です。なんじゃこりゃーって感じです。
1億回のループ中、中間結果をはしょったりしているんじゃなかろうか、と1千万回に1回中間結果を表示するようにしてみたりもしたんですが、結果は変わりませんでした。当然の如く、得られる数値も全て同一です。
これほど小さなプログラム、それも計算主体のものでこんなに差が出る理由がはっきり言って謎です。なお興味深いことに、テストプログラム中の *4 を *6 に、/2 を /3 に変更して試してみた結果が下記です。
kazawa@tpx20:~$ time ./multi_icc
i:100000000, a:-384307168202464939, b:-768614336404929877, c:-4611686018429579262, d:-1537228672809859754
real 0m31.510s
user 0m30.825s
sys 0m0.007s
kazawa@tpx20:~$ time java -server multi
i:100000000, a:-384307168202464939, b:-768614336404929877, c:-4611686018429579262, d:-1537228672809859754
real 0m32.544s
user 0m31.778s
sys 0m0.027s
icc 版は2倍、java 版は4倍くらい遅くなって、どちらもほとんど変わらない結果となりました (Java が1秒ほど遅いのは VM 起動時のラグだとすると体感的にはちょうどな感じ)。つまりあれですか、かけ算をシフト命令で置き換えられるような時の 64bit 計算の効率が激しく良い、ってこと?しかし、いくら HotSpotVM が動的最適化して効率の良いネイティブコードを生成する、と言ったって、icc が静的に生成するコードよりも効率のいいものを作れるものなんでしょうかね。しかもこんな単純なコードで。
おまけ。HotSpot Server VM でなく Client VM では、
kazawa@tpx20:~$ time java multi
i:100000000, a:436906, b:873813, c:3495252, d:1747626
real 0m58.071s
user 0m56.676s
sys 0m0.040s
てな感じになります。また IBM VM でも
kazawa@tpx20:~$ /usr/local/IBMJava2-141/bin/java -version
java version "1.4.1"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.1)
Classic VM (build 1.4.1, J2RE 1.4.1 IBM build cxia32141-20030522 (JIT enabled: jitc))
kazawa@tpx20:~$ time /usr/local/IBMJava2-141/bin/java multi
i:100000000, a:436906, b:873813, c:3495252, d:1747626
real 0m56.422s
user 0m54.605s
sys 0m0.093s
と、あまり変わらない結果となります。こう見てくると、HotSpot Server VM だけが異常に (C と比べても) 速いことがわかります。なんでなんだろう…?