簡體   English   中英

Java vs C ++(g ++)vs C ++(Visual Studio)性能

[英]Java vs C++ (g++) vs C++ (Visual Studio) performance

編輯:考慮到第一個答案,我刪除了“ myexp()”函數,與錯誤而不是討論的重點

我有一段簡單的代碼,並針對不同的平台進行了編譯,並獲得了不同的性能結果(執行時間):

  • Java 8 / Linux:3.5秒

    執行命令: java -server Test

  • C ++ / gcc 4.8.3:6.22秒

    編譯選項: O3

  • C ++ / Visual Studio 2015:1.7秒

    編譯器選項: /Og /Ob2 /Oi

似乎VS具有這些附加選項,不適用於g ++編譯器。

我的問題是:為什么Visual Studio(帶有那些編譯器選項)相對於Java和C ++這么快(帶有O3優化,我認為這是最先進的)?

您可以在下面找到Java和C ++代碼。

C ++代碼:

#include <cstdio>
#include <ctime>
#include <cstdlib>
#include <cmath>


static unsigned int g_seed;

//Used to seed the generator.
inline void fast_srand( int seed )
{
    g_seed = seed;
}

//fastrand routine returns one integer, similar output value range as C lib.
inline int fastrand()
{
    g_seed = ( 214013 * g_seed + 2531011 );
    return ( g_seed >> 16 ) & 0x7FFF;
}

int main()
{
    static const int NUM_RESULTS = 10000;
    static const int NUM_INPUTS  = 10000;

    double dInput[NUM_INPUTS];
    double dRes[NUM_RESULTS];

    fast_srand(10);

    clock_t begin = clock();

    for ( int i = 0; i < NUM_RESULTS; i++ )
    {
        dRes[i] = 0;

        for ( int j = 0; j < NUM_INPUTS; j++ )
        {
           dInput[j] = fastrand() * 1000;
           dInput[j] = log10( dInput[j] );
           dRes[i] += dInput[j];
        }
     }


    clock_t end = clock();

    double elapsed_secs = double(end - begin) / CLOCKS_PER_SEC;

    printf( "Total execution time: %f sec - %f\n", elapsed_secs, dRes[0]);

    return 0;
}

Java代碼:

import java.util.concurrent.TimeUnit;


public class Test
{

    static int g_seed;

    static void fast_srand( int seed )
    {
        g_seed = seed;
    }

    //fastrand routine returns one integer, similar output value range as C lib.
    static int fastrand()
    {
        g_seed = ( 214013 * g_seed + 2531011 );
        return ( g_seed >> 16 ) & 0x7FFF;
    }


    public static void main(String[] args)
    {
        final int NUM_RESULTS = 10000;
        final int NUM_INPUTS  = 10000;


        double[] dRes = new double[NUM_RESULTS];
        double[] dInput = new double[NUM_INPUTS];


        fast_srand(10);

        long nStartTime = System.nanoTime();

        for ( int i = 0; i < NUM_RESULTS; i++ )
        {
            dRes[i] = 0;

            for ( int j = 0; j < NUM_INPUTS; j++ )
            {
               dInput[j] = fastrand() * 1000;
               dInput[j] = Math.log( dInput[j] );
               dRes[i] += dInput[j];
            }
        }

        long nDifference = System.nanoTime() - nStartTime;

        System.out.printf( "Total execution time: %f sec - %f\n", TimeUnit.NANOSECONDS.toMillis(nDifference) / 1000.0, dRes[0]);
    }
}

功能

static inline double myexp( double val )
{
    const long tmp = (long)( 1512775 * val + 1072632447 );
    return double( tmp << 32 );
}:

在MSVC中發出警告

warning C4293: '<<' : shift count negative or too big, undefined behavior

更改為:

static inline double myexp(double val)
{
    const long long tmp = (long long)(1512775 * val + 1072632447);
    return double(tmp << 32);
}

該代碼在MSVC中也需要大約4秒鍾的時間。

因此,顯然MSVC在那里優化了很多東西,可能是整個myexp()函數(可能還取決於此結果,甚至還有其他東西)-因為它可以(記住未定義的行為)。

上一課:同時檢查(並修復)警告。


請注意,如果我嘗試在func中打印結果,則MSVC優化版本會為我(對於每個調用):

tmp: -2147483648
result: 0.000000

也就是說,MSVC優化了未定義的行為以始終返回0。查看程序集輸出以了解由於此而進行了其他優化的情況可能也很有趣。


因此,在檢查程序集之后,固定版本具有以下代碼:

; 52   :             dInput[j] = myexp(dInput[j]);
; 53   :             dInput[j] = log10(dInput[j]);

    mov eax, esi
    shr eax, 16                 ; 00000010H
    and eax, 32767              ; 00007fffH
    imul    eax, eax, 1000
    movd    xmm0, eax
    cvtdq2pd xmm0, xmm0
    mulsd   xmm0, QWORD PTR __real@4137154700000000
    addsd   xmm0, QWORD PTR __real@41cff7893f800000
    call    __dtol3
    mov edx, eax
    xor ecx, ecx
    call    __ltod3
    call    __libm_sse2_log10_precise

; 54   :             dRes[i] += dInput[j];

在原始版本中,缺少了整個塊,即,對log10()的調用顯然也已優化,並且在末尾被常量替換(顯然-INF ,這是log10(0.0) -結果可能也未定義或實現定義)。 同樣,整個myexp()函數也由fldz指令代替(基本上是“加載零”)。 這樣就解釋了額外的速度:)


編輯

關於使用實數exp()時的性能差異:程序集輸出可能會提供一些線索。

特別是對於MSVC,您可以利用這些附加參數:

/FAs /Qvec-report:2

/FAs生成程序集列表(以及源代碼)

/Qvec-report:2提供有關向量化狀態的有用信息:

test.cpp(49) : info C5002: loop not vectorized due to reason '1304'
test.cpp(45) : info C5002: loop not vectorized due to reason '1106'

原因代碼在這里可用: https : //msdn.microsoft.com/en-us/library/jj658585.aspx-特別是,MSVC似乎無法正確向量化循環。 但是根據組裝清單,它仍然使用SSE2函數(這仍然是一種“向量化”,可以顯着提高速度)。

GCC的類似參數為:

-funroll-loops -ftree-vectorizer-verbose=1

這給我的結果:

Analyzing loop at test.cpp:42
Analyzing loop at test.cpp:46
test.cpp:30: note: vectorized 0 loops in function.
test.cpp:46: note: Unroll loop 3 times

因此,顯然g ++也無法向量化,但是它確實可以展開循環(在程序集中,我可以看到循環代碼在那里重復了3次),這也可以解釋更好的性能。

不幸的是,這是Java缺少AFAIK的地方,因為Java不會進行任何矢量化,SSE2或循環展開,因此它比優化的C ++版本要慢得多。 參見此處的示例JVM的JIT編譯器是否生成使用矢量化浮點指令的代碼? 建議使用JNI以獲得更好的性能(例如,通過Java應用程序的JNI接口在C / C ++ DLL中進行計算)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM