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

1 c++ java performance jvm g++

编辑:考虑到第一个答案我删除了"myexp()"函数,因为bug而不是讨论的要点

我有一段简单的代码,为不同的平台编译并获得不同的性能结果(执行时间):

  • 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;
}
Run Code Online (Sandbox Code Playgroud)

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]);
    }
}
Run Code Online (Sandbox Code Playgroud)

axa*_*lis 5

功能

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

在MSVC中发出警告

warning C4293: '<<' : shift count negative or too big, undefined behavior
Run Code Online (Sandbox Code Playgroud)

更改为:

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

在MSVC中,代码也需要大约4秒.

所以,显然MSVC优化了很多东西,可能是整个myexp()函数(甚至可能还有其他东西取决于这个结果) - 因为它可以(记住,未定义的行为).

所学课程:检查(并修复)警告.


请注意,如果我尝试在func中打印结果,MSVC优化版本会给我(对于每个调用):

tmp: -2147483648
result: 0.000000
Run Code Online (Sandbox Code Playgroud)

即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];
Run Code Online (Sandbox Code Playgroud)

在原始版本中,缺少整个块,即调用log10()显然也已经优化,最后由常量替换(显然-INF,这是结果log10(0.0)- 实际上结果可能也未定义或实现定义).此外,整个myexp()函数被fldz指令替换(基本上,"加载零").所以这解释了额外的速度:)


编辑

关于使用real时的性能差异exp():汇编输出可能会给出一些线索.

特别是,对于MSVC,您可以使用这些附加参数:

/FAs /Qvec-report:2
Run Code Online (Sandbox Code Playgroud)

/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'
Run Code Online (Sandbox Code Playgroud)

原因代码可在此处获得:https://msdn.microsoft.com/en-us/library/jj658585.aspx - 特别是,MSVC似乎无法正确地向量化循环.但是根据汇编列表,它仍然使用SSE2功能(它仍然是一种"矢量化",显着提高了速度).

GCC的类似参数是:

-funroll-loops -ftree-vectorizer-verbose=1
Run Code Online (Sandbox Code Playgroud)

哪给我的结果:

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
Run Code Online (Sandbox Code Playgroud)

所以显然g ++也无法向量化,但是它会循环展开(在程序集中我可以看到循环代码在那里重复了3次),这也可以解释更好的性能.

不幸的是,这是Java缺少AFAIK的地方,因为Java不进行任何矢量化,SSE2或循环展开,因此它比优化的C++版本慢得多.请参见此处:是否有任何JVM的JIT编译器生成使用向量化浮点指令的代码?建议使用JNI以获得更好的性能(即,通过Java应用程序的JNI接口计算C/C++ DLL).