繁体   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