Java より native の方が遅いってどういうこっちゃねん - 技術編

環境1 - メインマシン

CPU Athlon 64 x2 4400+
Mem 6GB - DDR2 800 DualChannel
OS WinXP x64 SP2
Java 1.6.0_07 32bit版

環境2 - サブマシン

CPU Athlon 1500+
Mem 1.5GB - DDR 400
OS WinXP SP3
Java 1.6.0_07 32bit版

環境4 - ATOM

CPU ATOM 330
Mem 2GB - DDR2 533
OS WinXP x64 SP2
Java 1.6.0_07 32bit版

cpp - native コード

Visual Studio 2003 でビルド

cl
/O2 /GL /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /FD /EHsc /MD /GS /Yu"stdafx.h" /Fp"Release/Bench.pch" /Fo"Release/" /Fd"Release/vc70.pdb" /W3 /nologo /c /Wp64 /Zd /TP
link
/OUT:"Release/Bench.exe" /INCREMENTAL:NO /NOLOGO /DEBUG /PDB:"Release/Bench.pdb" /SUBSYSTEM:CONSOLE /OPT:REF /OPT:ICF /LTCG /MACHINE:X86
// Bench.cpp
#include "stdafx.h"

class Bench
{
public:

    Bench(int nID)
        : m_nRand(::timeGetTime())
        , m_nID(nID)
        , m_nResult(::timeGetTime()){}
    ~Bench(){}

    void Run()
    {
        DWORD dateStart = ::timeGetTime();
        _tprintf(_T("thread %d  start...\n"), m_nID);
        std::vector<int> anArray(ARRAY_SIZE);
        for(int n = 0; n < TIMES; ++n)
        {
            for(int a = 0; a < ARRAY_SIZE; ++a)
            {
                int r1 = my_rand();
                int r2 = my_rand();
                int r3 = my_rand();
                int r4 = my_rand();
                
                // 適当に演算
                anArray[a] += ((r1 + r2 * r4) | (r3 >> (r4 & 0x06))) ^ r1;
                anArray[a] &= 0x7fffffff;
            }
        }
        
        // 何の結果かわからないけど、結果算出
        for(int i = 0; i < ARRAY_SIZE; ++i)
        {
            m_nResult += anArray[i];
        }
        DWORD dateEnd = ::timeGetTime();
        _tprintf(_T("thread %d end / %d ms\n"), m_nID, dateEnd - dateStart);
    }

    int GetResult()
    {
        return m_nResult;
    }

private:

    int my_rand()
    {
        return m_nRand = ((m_nRand * 0x654321) | (m_nRand << 16) ^
            (m_nRand >> 7)) & 0x7fffffff;
    }

private:

    int m_nRand;
    int m_nID;
    int m_nResult;

    static const int TIMES      = 0x8000;
    static const int ARRAY_SIZE = 100000;
};

unsigned int CALLBACK ThreadStart(void* pVoid)
{
    Bench b((int)(INT_PTR)pVoid);
    b.Run();
    return b.GetResult();
}


int _tmain(int argc, _TCHAR* argv[])
{
    ::timeBeginPeriod(1);
    int nCount = _ttoi(argv[1]);

    std::vector<HANDLE> stlHandle(nCount);
    for(int i = 0; i < nCount; ++i)
    {
        unsigned int threadID = 0;
        stlHandle[i] = (HANDLE)_beginthreadex(NULL, 0, &ThreadStart,
            (void*)(INT_PTR)i, CREATE_SUSPENDED, &threadID);
    }

    DWORD dwBegin = ::timeGetTime();
    for(int i = 0; i < nCount; ++i)
    {
        ::ResumeThread(stlHandle[i]);
    }

    ::WaitForMultipleObjects(nCount, &stlHandle[0], true, INFINITE);
    DWORD dwEnd = ::timeGetTime();
    _tprintf(_T("# %dms\n"), dwEnd - dwBegin);

    for(int i = 0; i < nCount; ++i)
    {
        ::CloseHandle(stlHandle[i]);
    }

    ::timeEndPeriod(1);
    return 0;
}
// stdafx.h
#include <windows.h>
#include <process.h>
#include <iostream>
#include <tchar.h>
#include <vector>
#include <string.h>
#include <mmsystem.h>
#pragma comment(lib, "winmm.lib")

Java コード - 前回と一緒

import java.util.Date;

public class Bench extends Thread
{
    static final int TIMES      = 0x8000;
    static final int ARRAY_SIZE = 100000;

    int m_nRand;
    int m_nResult;
    int m_nID;

    public Bench(int nID)
    {
        m_nID   = nID;
        m_nRand = (int)(Math.random() * 0x1000);
    }

    private int rand()
    {   // えせ乱数
        return m_nRand = ((m_nRand * 0x654321) | (m_nRand << 16) ^
            (m_nRand >> 7)) & 0x7fffffff;
    }

    public void run()
    {
        Date dateStart = new Date();
        System.out.println("thread " + m_nID + " start...");
        int[] anArray = new int[ARRAY_SIZE];
        for(int n = 0; n < TIMES; ++n)
        {
            for(int a = 0; a < ARRAY_SIZE; ++a)
            {
                int r1 = rand();
                int r2 = rand();
                int r3 = rand();
                int r4 = rand();
                
                // 適当に演算
                anArray[a] += ((r1 + r2 * r4) | (r3 >> (r4 & 0x06))) ^ r1;
                anArray[a] &= 0x7fffffff;
            }
        }
        
        // 何の結果かわからないけど、結果算出
        for(int i = 0; i < ARRAY_SIZE; ++i)
        {
            m_nResult += anArray[i];
        }
        Date dataEnd = new Date();
        System.out.println("thread " + m_nID + " end / " +
            (dataEnd.getTime() - dateStart.getTime()) + "ms");
    }
    
    public static void main(String[] str)
    {
        int nThreadCount = Integer.parseInt(str[0]);

        Bench[] anTests = new Bench[nThreadCount];
        
        for(int i = 0; i < nThreadCount; ++i)
            anTests[i] = new Bench(i);
            
        Date pcStart = new Date();
        for(int i = 0; i < nThreadCount; ++i)
            anTests[i].start();
        for(int i = 0; i < nThreadCount; ++i)
        {
            try
            {
                anTests[i].join();      // コレはひどい
            }
            catch(InterruptedException e)
            {}
        }
        Date pcEnd  = new Date();
        
        System.out.println((pcEnd.getTime() - pcStart.getTime()) + "ms");
    }
};

実行結果

スレッド数→ 1 2 3 4 5 6 7 8
Athlon 64 x2 4400+ / Java 58946.8 60021.8 89898.6 119137.5 148559.2 177933.1 207727.8 236882.9
Athlon 64 x2 4400+ / exe 51185.5 51623.1 76977.6 101638.6 127087.1 151785.5 177005.7 201633.9

http://ir9.jp/hd08/1011_01.png
Athlon 64 x2 4400+ で、javaとexeの実行時間比較

スレッド数→ 1 2 3 4 5 6 7 8
Athlon XP 1500+ / Java 125427.2 250257.6 373717.1 495440.6 619389.1 743142.2 864845.3 988209.5
Athlon XP 1500+ / exe 109388.5 218241.9 326402.9 435133.9 544283.9 654202.8 760538.6 869151

http://ir9.jp/hd08/1011_02.png
Athlon XP 1500+ で、javaとexeの実行時間比較

スレッド数→ 1 2 3 4 5 6 7 8
Atom 330 / Java 55781 58629.5 66132.8 72808.1 101604.5 109785.9 130402.9 147034.5
Atom 330 / exe 122135.5 142766.8 145396.4 159807.4 228494.9 241398 288904.1 326393.5

http://ir9.jp/hd08/1011_00.png
ATOM 330 で、javaとexeの実行時間比較




いやまぁ、Java と VC じゃー正確な比較なんて出来るわけがないのは分かるのですが、それでも Athlon系で Native が早いのであれば、ATOMでも Native が早いだろうと期待していまいます。

ってか、Javaもどこかしらで Native に変換している以上、"JavaVM" というレイヤーが存在する Javaの方が遅くような気がしちゃうわけですが…


しかし、今回はそれが大きく違った…… なぁぜぇだぁ…


気になること - VCの最適化

処理の要となるのは「Run()内の for(ARRAY_SIZE)内」になるわけですが、VCでここのコードがどのように吐かれているのか調べて見たところ、次のようになっていました。(アンループ化されていますね)

            for(int a = 0; a < ARRAY_SIZE; ++a)
00401050  mov         eax,dword ptr [esp+1Ch] 
00401054  add         eax,8 
00401057  mov         dword ptr [esp+0Ch],4E20h 
0040105F  nop              
            {
                int r1 = my_rand();
00401060  mov         ecx,dword ptr [ebx] 
00401062  mov         edi,ecx 
00401064  mov         edx,ecx 
00401066  imul        ecx,ecx,654321h 
0040106C  sar         edx,7 
0040106F  shl         edi,10h 
00401072  xor         edi,edx 
00401074  or          edi,ecx 
00401076  and         edi,7FFFFFFFh 
                int r2 = my_rand();
0040107C  mov         edx,edi 
0040107E  imul        edx,edx,654321h 
00401084  mov         ebp,edi 
00401086  shl         ebp,10h 
00401089  mov         ecx,edi 
0040108B  sar         ecx,7 
0040108E  xor         ebp,ecx 
00401090  or          ebp,edx 
00401092  and         ebp,7FFFFFFFh 
                int r3 = my_rand();
00401098  mov         edx,ebp 
0040109A  shl         edx,10h 
0040109D  mov         ecx,ebp 
0040109F  sar         ecx,7 
004010A2  xor         edx,ecx 
004010A4  mov         ecx,ebp 
004010A6  imul        ecx,ecx,654321h 
004010AC  or          edx,ecx 
004010AE  and         edx,7FFFFFFFh 
                int r4 = my_rand();
004010B4  mov         esi,edx 
004010B6  mov         ecx,edx 
004010B8  sar         ecx,7 
004010BB  shl         esi,10h 
004010BE  xor         esi,ecx 
004010C0  mov         ecx,edx 
004010C2  imul        ecx,ecx,654321h 
004010C8  or          esi,ecx 
004010CA  and         esi,7FFFFFFFh 
004010D0  mov         dword ptr [ebx],esi 
                
                // 適当に演算
                anArray[a] += ((r1 + r2 * r4) | (r3 >> (r4 & 0x06))) ^ r1;
004010D2  mov         ecx,esi 
004010D4  imul        esi,ebp 
004010D7  and         ecx,6 
004010DA  sar         edx,cl 
004010DC  add         esi,edi 
004010DE  or          edx,esi 
004010E0  mov         esi,dword ptr [eax-8] 
004010E3  xor         edx,edi 
004010E5  add         esi,edx 
                anArray[a] &= 0x7fffffff;
004010E7  mov         ecx,esi 
004010E9  and         ecx,7FFFFFFFh 
004010EF  mov         dword ptr [eax-8],esi 
004010F2  mov         dword ptr [eax-8],ecx 
004010F5  mov         ecx,dword ptr [ebx]   ; アンループ1回目
004010F7  mov         edi,ecx 
004010F9  mov         edx,ecx 
004010FB  imul        ecx,ecx,654321h 
00401101  sar         edx,7 
00401104  shl         edi,10h 
00401107  xor         edi,edx 
00401109  or          edi,ecx 
0040110B  and         edi,7FFFFFFFh 
00401111  mov         edx,edi 
00401113  imul        edx,edx,654321h 
00401119  mov         ebp,edi 
0040111B  shl         ebp,10h 
0040111E  mov         ecx,edi 
00401120  sar         ecx,7 
00401123  xor         ebp,ecx 
00401125  or          ebp,edx 
00401127  and         ebp,7FFFFFFFh 
0040112D  mov         edx,ebp 
0040112F  shl         edx,10h 
00401132  mov         ecx,ebp 
00401134  sar         ecx,7 
00401137  xor         edx,ecx 
00401139  mov         ecx,ebp 
0040113B  imul        ecx,ecx,654321h 
00401141  or          edx,ecx 
00401143  and         edx,7FFFFFFFh 
00401149  mov         esi,edx 
0040114B  mov         ecx,edx 
0040114D  shl         esi,10h 
00401150  sar         ecx,7 
00401153  xor         esi,ecx 
00401155  mov         ecx,edx 
00401157  imul        ecx,ecx,654321h 
0040115D  or          esi,ecx 
0040115F  and         esi,7FFFFFFFh 
00401165  mov         dword ptr [ebx],esi 
00401167  mov         ecx,esi 
00401169  imul        esi,ebp 
0040116C  and         ecx,6 
0040116F  sar         edx,cl 
00401171  add         esi,edi 
00401173  or          edx,esi 
00401175  mov         esi,dword ptr [eax-4] 
00401178  xor         edx,edi 
0040117A  add         esi,edx 
0040117C  mov         dword ptr [eax-4],esi 
0040117F  mov         ecx,esi 
00401181  and         ecx,7FFFFFFFh 
00401187  mov         dword ptr [eax-4],ecx 
0040118A  mov         ecx,dword ptr [ebx] ; アンループ2回目
0040118C  mov         edi,ecx 
0040118E  mov         edx,ecx 
00401190  imul        ecx,ecx,654321h 
00401196  sar         edx,7 
00401199  shl         edi,10h 
0040119C  xor         edi,edx 
0040119E  or          edi,ecx 
004011A0  and         edi,7FFFFFFFh 
004011A6  mov         edx,edi 
004011A8  imul        edx,edx,654321h 
004011AE  mov         ebp,edi 
004011B0  mov         ecx,edi 
004011B2  sar         ecx,7 
004011B5  shl         ebp,10h 
004011B8  xor         ebp,ecx 
004011BA  or          ebp,edx 
004011BC  and         ebp,7FFFFFFFh 
004011C2  mov         edx,ebp 
004011C4  shl         edx,10h 
004011C7  mov         ecx,ebp 
004011C9  sar         ecx,7 
004011CC  xor         edx,ecx 
004011CE  mov         ecx,ebp 
004011D0  imul        ecx,ecx,654321h 
004011D6  or          edx,ecx 
004011D8  and         edx,7FFFFFFFh 
004011DE  mov         esi,edx 
004011E0  shl         esi,10h 
004011E3  mov         ecx,edx 
004011E5  sar         ecx,7 
004011E8  xor         esi,ecx 
004011EA  mov         ecx,edx 
004011EC  imul        ecx,ecx,654321h 
004011F2  or          esi,ecx 
004011F4  and         esi,7FFFFFFFh 
004011FA  mov         dword ptr [ebx],esi 
004011FC  mov         ecx,esi 
004011FE  imul        esi,ebp 
00401201  and         ecx,6 
00401204  sar         edx,cl 
00401206  add         esi,edi 
00401208  or          edx,esi 
0040120A  mov         esi,dword ptr [eax] 
0040120C  xor         edx,edi 
0040120E  add         esi,edx 
00401210  mov         ecx,esi 
00401212  and         ecx,7FFFFFFFh 
00401218  mov         dword ptr [eax],esi 
0040121A  mov         dword ptr [eax],ecx 
0040121C  mov         ecx,dword ptr [ebx] ; アンループ3回目
0040121E  mov         edi,ecx 
00401220  mov         edx,ecx 
00401222  imul        ecx,ecx,654321h 
00401228  shl         edi,10h 
0040122B  sar         edx,7 
0040122E  xor         edi,edx 
00401230  or          edi,ecx 
00401232  and         edi,7FFFFFFFh 
00401238  mov         ebp,edi 
0040123A  shl         ebp,10h 
0040123D  mov         ecx,edi 
0040123F  sar         ecx,7 
00401242  xor         ebp,ecx 
00401244  mov         edx,edi 
00401246  imul        edx,edx,654321h 
0040124C  or          ebp,edx 
0040124E  and         ebp,7FFFFFFFh 
00401254  mov         edx,ebp 
00401256  shl         edx,10h 
00401259  mov         ecx,ebp 
0040125B  sar         ecx,7 
0040125E  xor         edx,ecx 
00401260  mov         ecx,ebp 
00401262  imul        ecx,ecx,654321h 
00401268  or          edx,ecx 
0040126A  and         edx,7FFFFFFFh 
00401270  mov         esi,edx 
00401272  mov         ecx,edx 
00401274  sar         ecx,7 
00401277  shl         esi,10h 
0040127A  xor         esi,ecx 
0040127C  mov         ecx,edx 
0040127E  imul        ecx,ecx,654321h 
00401284  or          esi,ecx 
00401286  and         esi,7FFFFFFFh 
0040128C  mov         dword ptr [ebx],esi 
0040128E  mov         ecx,esi 
00401290  imul        esi,ebp 
00401293  and         ecx,6 
00401296  sar         edx,cl 
00401298  add         esi,edi 
0040129A  or          edx,esi 
0040129C  mov         esi,dword ptr [eax+4] 
0040129F  xor         edx,edi 
004012A1  add         esi,edx 
004012A3  mov         ecx,esi 
004012A5  and         ecx,7FFFFFFFh 
004012AB  mov         dword ptr [eax+4],esi 
004012AE  mov         dword ptr [eax+4],ecx 
004012B1  mov         ecx,dword ptr [ebx] ; アンループ4回目
004012B3  mov         edi,ecx 
004012B5  mov         edx,ecx 
004012B7  imul        ecx,ecx,654321h 
004012BD  sar         edx,7 
004012C0  shl         edi,10h 
004012C3  xor         edi,edx 
004012C5  or          edi,ecx 
004012C7  and         edi,7FFFFFFFh 
004012CD  mov         edx,edi 
004012CF  imul        edx,edx,654321h 
004012D5  mov         ebp,edi 
004012D7  shl         ebp,10h 
004012DA  mov         ecx,edi 
004012DC  sar         ecx,7 
004012DF  xor         ebp,ecx 
004012E1  or          ebp,edx 
004012E3  and         ebp,7FFFFFFFh 
004012E9  mov         edx,ebp 
004012EB  shl         edx,10h 
004012EE  mov         ecx,ebp 
004012F0  sar         ecx,7 
004012F3  xor         edx,ecx 
004012F5  mov         ecx,ebp 
004012F7  imul        ecx,ecx,654321h 
004012FD  or          edx,ecx 
004012FF  and         edx,7FFFFFFFh 
00401305  mov         esi,edx 
00401307  shl         esi,10h 
0040130A  mov         ecx,edx 
0040130C  sar         ecx,7 
0040130F  xor         esi,ecx 
00401311  mov         ecx,edx 
00401313  imul        ecx,ecx,654321h 
00401319  or          esi,ecx 
0040131B  and         esi,7FFFFFFFh 
00401321  mov         ecx,esi 
00401323  mov         dword ptr [ebx],esi 
00401325  imul        esi,ebp 
00401328  and         ecx,6 
0040132B  sar         edx,cl 
0040132D  add         esi,edi 
0040132F  or          edx,esi 
00401331  mov         ecx,dword ptr [esp+0Ch] 
00401335  xor         edx,edi 
00401337  mov         edi,dword ptr [eax+8] 
0040133A  add         edi,edx 
0040133C  mov         esi,edi 
0040133E  and         esi,7FFFFFFFh 
00401344  mov         dword ptr [eax+8],edi 
00401347  mov         dword ptr [eax+8],esi 
0040134A  add         eax,14h 
0040134D  dec         ecx  
0040134E  mov         dword ptr [esp+0Ch],ecx 
00401352  jne         Bench::Run+60h (401060h) 
00401358  dec         dword ptr [esp+10h] 
0040135C  jne         Bench::Run+50h (401050h) 
            }
        }

このあたりのコードが「低速」な原因を作り出しているに違いないわけですが…さて、その低速理由は……? …うーん、よく分からない。

atomは「インオーダー」になった故に、命令を並列実行できる「ペアリング」が出来てないのかしら? と思ってみるも、それだったら Java もうまくペアリング出来ないハズなので、違う気も…。それとも JavaVMがクラスロード時になんか賢くなんかやってる…? うーん…

Native が怠る理由…うーん…。 今のおいらには、他の可能性が思いつかない……




ざんねん!! わたしの ぼうけんは ここで おわってしまった!!




誰か詳しく調べてください…… (他力本願

余談 - ペアリング具合を検証してみる

ペアリングはほとんど出来てない臭いです。にわか知識でどうなるか確認してみたのですが…… v の出番がほとんど無し。 atomパイプラインが2つ あるとのことですが、片方のパイプラインはスカスカなんだろうなぁ、このコードは。(ぉ

# こういうコードの場合は、特にMultiThreadingが役に立つのだろう。多分。

0040105F  nop
    {
        int r1 = my_rand();
00401060  mov         ecx,dword ptr [ebx]  // u
00401062  mov         edi,ecx              // u - ecxがかぶる
00401064  mov         edx,ecx              // v
00401066  imul        ecx,ecx,654321h      // u
0040106C  sar         edx,7                // u
0040106F  shl         edi,10h              // u - sarとペアになれない
00401072  xor         edi,edx              // u - ediがかぶる
00401074  or          edi,ecx              // u - ediがかぶる
00401076  and         edi,7FFFFFFFh        // u - ediがかぶる

  :
  :
  :

        anArray[a] += ((r1 + r2 * r4) | (r3 >> (r4 & 0x06))) ^ r1;
004010D2  mov         ecx,esi 
004010D4  imul        esi,ebp   // u
004010D7  and         ecx,6     // u
004010DA  sar         edx,cl    // u - ecx/clがかぶる
004010DC  add         esi,edi   // v
004010DE  or          edx,esi   // u
004010E0  mov         esi,dword ptr [eax-8] // v? - esiが心配(何
004010E3  xor         edx,edi               // u
004010E5  add         esi,edx       // u - edxがかぶる
        anArray[a] &= 0x7fffffff;
004010E7  mov         ecx,esi       // u - esiがかぶる
004010E9  and         ecx,7FFFFFFFh // u - ecxがかぶる
004010EF  mov         dword ptr [eax-8],esi // v
004010F2  mov         dword ptr [eax-8],ecx // u

【参考にさせていただきました】