4コア8スレッドCPUを手に入れたので試してみたら不思議な結果に

インテルCore i7 2600Kという4コア8スレッドのCPUを手にいれたので、並列処理でどうなるか試してみたら、面白い結果に。

Intel CPU Core i7 i7-2600K 3.4GHz 8M LGA1155 SandyBridge BX80623I72600K

Intel CPU Core i7 i7-2600K 3.4GHz 8M LGA1155 SandyBridge BX80623I72600K


ソースは最後に載せますが、1億5千万件のint数値を複数回合計するという処理を行って、時間を計測してみました。
メモリは8GBで、VMオプションには-Xmx2048mを指定しています。
Java VMは、JDK7ea b140です。


で、Ubuntu10.04の32bit版で実行した結果。

 --- result ---
1 threads:15.451201(s)
2 threads:8.184938(s)
4 threads:6.324816(s)
6 threads:6.993831(s)
8 threads:6.587393(s)

1スレッドから2スレッドにすると倍近くに、2スレッドから4スレッドでも1.3倍程度に速くなってます。ただ、6スレッド・8スレッドでは速度改善はありませんでした。条件分岐もなくただ足し算繰り返すだけでなのでHT効きづらいのかなーと思いますが。

※ 32bitなので、メモリは3.5GBくらいしか認識していませんが、メモリの余裕はありました。


まあ、これだけなら、4コアまで伸びたけどハイパースレッドきかなかったねーとなるわけですけど、OSかえたら面白い結果になりました。
Windows7 64bit版でやってみました。

 --- result ---
1 threads:6.930770(s)
2 threads:6.020648(s)
4 threads:6.175338(s)
6 threads:6.133848(s)
8 threads:6.160824(s)

なんだか、1スレッドのときからUbuntu 32bitでの4スレッドと同レベルの速度です。で、2スレッドで1.15倍ほど速度改善しますが、あとはむしろ悪くなります。


これが、Windows7がエラいのか、64bitにしたからなのかが気になりました。
ということで、Ubuntu 10.04 64bit版をUSBメモリに入れてみて試してみました。

 --- result ---
1 threads:6.862185(s)
2 threads:5.982360(s)
4 threads:6.061102(s)
6 threads:6.126239(s)
8 threads:6.152392(s)

Windows7 64bitとだいたい同じで、ちょっと速いかなーくらいの結果になっています。


この、Ubuntu 32bitとUbuntu 64bitでの結果を統一的に説明できるような原因が、まったくわからんのですけど、だれか教えてえらい人


追記1 コメントのおかださんの結果のグラフです
6コア12スレッド!
SUM_TIMES = 30になっていたときの結果なので、時間は短くなっていますが、2600K 3.4GHzだと2スレッド以上で速くなったときに1.8秒だったので、倍近く速い!
HTは効いてませんね。


ソースです。
JDK7の数値のアンダーバー区切りのおかげで、大きい数値が見やすいです。
集計は10試行の最初3試行を捨てて、最大値と最小値を省いた5試行の平均になっています。
最初SUM_TIMES = 30になってたのですが、計測時と同様100に修正しました。試すときは30でも傾向かわらないので、そっちのほうが時間くわなくていいと思います。

package sample;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Random;

public class ParallelSum {
    public static final int DATA_COUNT = 150_000_000;//データ数
    static final int COUNT = 10;//計測回数
    static final int AVOID = 3;//時間の集計をしない回数
    private static final int[] THREAD_COUNTS = {1, 2, 4, 6, 8};
    static final int SUM_TIMES = 100;//合計を行う回数


    public static void main(String[] args) {
        //データ作成
        int[] data = new int[DATA_COUNT];
        Random r = new Random(505);
        r.nextInt();//最初の値は捨てておく
        for(int i = 0; i < data.length; ++i){
            data[i] = r.nextInt(100);
        }
        //計測実行
        Map<Integer, Double> times = new LinkedHashMap<>();
        for(int n : THREAD_COUNTS){
            long min = Long.MAX_VALUE;//処理時間の最小
            long max = Long.MIN_VALUE;//処理時間の最大
            long timeTotal = 0;
            for(int i = 0; i < COUNT; ++i){
                System.out.print("start." + i);
                long t = proc(data, n);//処理の呼び出し
                if(i < AVOID) continue;//最初の数回は集計に含めない
                timeTotal += t;
                if(min > t) min = t;
                if(max < t) max = t;
            }
            //最大値と最小値を除いた平均処理時間を得る
            timeTotal -= min;
            timeTotal -= max;
            double t = timeTotal / (COUNT - AVOID - 2.) / 1000_000_000;
            times.put(n, t);
            System.out.printf("n:%d t:%f%n", n, t);
        }

        //全体の処理結果を表示
        System.out.println("--- result ---");
        for(int n : times.keySet()){
            System.out.printf("%d threads:%f(s)%n", n, times.get(n));
        }
    }

    /**
     * 配列の数値を合計する時間を計測
     * @param data 集計データ
     * @param n スレッド数
     * @return 計算時間(ナノ秒)
     */
    static long proc(final int[] data, int n){
        long startTime = System.nanoTime();
        long total = 0;

        final long[] results = new long[n];
        //処理スレッド起動
        Thread[] threads = new Thread[n];
        for(int t = 0; t < n; ++t){
            final int tt = t;
            final int start = (int) ((long)data.length * t / n);
            final int end = (int) ((long)data.length * (t + 1) / n);
            (threads[t] = new Thread(new Runnable() {
                @Override
                public void run() {
                    //集計処理
                    long subtotal = 0;
                    for(int j = 0; j < SUM_TIMES; ++j){
                        for(int i = start; i < end; ++i){
                            subtotal += data[i];
                        }
                    }
                    results[tt] = subtotal;
                }
            })).start();
        }
        //処理の終了を待機して結果を集計
        try {
            for(int t = 0; t < n; ++t){
                threads[t].join();
                total += results[t];
            }
        } catch (InterruptedException ex) {
        }
  
        //処理時間を計測
        long time = System.nanoTime() - startTime;
        System.out.printf(" summary:%d time:%f%n", total, time / 1000_000_000.);
        return time;
    }
}