【问题标题】:C++ vs Java? Why does the ICC generate slower code than VC? [closed]C++与Java?为什么 ICC 生成的代码比 VC 慢? [关闭]
【发布时间】:2012-02-13 17:52:41
【问题描述】:

以下是 C++ 中的一个简单循环。计时器正在使用 QueryPerformanceCounter() 并且非常准确。我发现 Java 占用了 C++ 60% 的时间,这不可能吗?!我在这里做错了什么?即使是严格的别名(这里的代码中没有包含)也无济于事......

long long var = 0;
std::array<int, 1024> arr;
int* arrPtr = arr.data();
CHighPrecisionTimer timer;

for(int i = 0; i < 1024; i++) arrPtr[i] = i;

timer.Start();

for(int i = 0; i < 1024 * 1024 * 10; i++){
    for(int x = 0; x < 1024; x++){
        var += arrPtr[x];
    }
}

timer.Stop();

printf("Unrestricted: %lld us, Value = %lld\n", (Int64)timer.GetElapsed().GetMicros(), var);

这个 C++ 运行大约需要 9.5 秒。我正在使用带有主机处理器优化的 Intel Compiler 12.1(专门针对我的)并且所有内容都已最大化。所以这是最好的英特尔编译器!有趣的是,自动并行化消耗了 70% 的 CPU 而不是 25%,但并没有更快地完成工作;)...

现在我使用下面的 Java 代码进行比较:

    long var = 0;
    int[] arr = new int[1024];

    for(int i = 0; i < 1024; i++) arr[i] = i;

    for(int i = 0; i < 1024 * 1024; i++){
        for(int x = 0; x < 1024; x++){
            var += arr[x];
        }
    }

    long nanos = System.nanoTime();

    for(int i = 0; i < 1024 * 1024 * 10; i++){
        for(int x = 0; x < 1024; x++){
            var += arr[x];
        }
    }

    nanos = (System.nanoTime() - nanos) / 1000;

    System.out.print("Value: " + var + ", Time: " + nanos);

Java 代码通过积极优化和服务器虚拟机调用(无调试)。它在我的机器上运行大约 7 秒(只使用一个线程)。

这是英特尔编译器的故障还是我又太笨了?

[编辑]:好的,事情就是这样……看起来更像是英特尔编译器中的一个错误^^。 [请注意,我在 Intel Quadcore Q6600 上运行,这是相当旧的。并且可能是英特尔编译器在最近的 CPU 上表现得更好,比如 Core i7]

Intel x86 (without vectorization): 3 seconds
MSVC x64: 5 seconds
Java x86/x64 (Oracle Java 7): 7 seconds
Intel x64 (with vectorization): 9.5 seconds
Intel x86 (with vectorization): 9.5 seconds
Intel x64 (without vectorization): 12 seconds
MSVC x86: 15 seconds (uhh)

[编辑]:另一个不错的案例;)。考虑以下简单的 lambda 表达式

#include <stdio.h>
#include <tchar.h>
#include <Windows.h>
#include <vector>
#include <boost/function.hpp>
#include <boost/lambda/bind.hpp>
#include <boost/typeof/typeof.hpp>

template<class TValue>
struct ArrayList
{
private:
    std::vector<TValue> m_Entries;
public:

    template<class TCallback>
    void Foreach(TCallback inCallback)
    {
        for(int i = 0, size = m_Entries.size(); i < size; i++)
        {
            inCallback(i);
        }
    }

    void Add(TValue inValue)
    {
        m_Entries.push_back(inValue);
    }
};

int _tmain(int argc, _TCHAR* argv[])
{
    auto t = [&]() {};


    ArrayList<int> arr;
    int res = 0;

    for(int i = 0; i < 100; i++)
    {
        arr.Add(i);
    }

    long long freq, t1, t2;

    QueryPerformanceFrequency((LARGE_INTEGER*)&freq);
    QueryPerformanceCounter((LARGE_INTEGER*)&t1);

    for(int i = 0; i < 1000 * 1000 * 10; i++)
    {
        arr.Foreach([&](int v) {
            res += i;
        });
    }

    QueryPerformanceCounter((LARGE_INTEGER*)&t2);

    printf("Time: %lld\n", ((t2-t1) * 1000000) / freq);

    if(res == 4950)
        return -1;

    return 0;
}

英特尔编译器再次大放异彩:

MSVC x86/x64: 12 milli seconds
Intel x86/x64: 1 second

嗯?!嗯,我想慢 90 倍也不是坏事……

我不太确定这是否适用: 好的,基于对这个线程的回答:众所周知,英特尔编译器(我也知道,但我只是没想到他们会放弃对他们的处理器的支持)在不支持的处理器上性能很差编译器“知道”,比如 AMD 处理器,甚至可能是像我这样过时的英特尔处理器......所以如果有人使用最近的英特尔处理器可以尝试一下,那就太好了;)。

这是英特尔编译器的 x64 输出:

    std::array<int, 1024> arr;
    int* arrPtr = arr.data();
    QueryPerformanceFrequency((LARGE_INTEGER*)&freq);
000000013F05101D  lea         rcx,[freq]  
000000013F051022  call        qword ptr [__imp_QueryPerformanceFrequency (13F052000h)]  

    for(int i = 0; i < 1024; i++) arrPtr[i] = i;
000000013F051028  mov         eax,4  
000000013F05102D  movd        xmm0,eax  
000000013F051031  xor         eax,eax  
000000013F051033  pshufd      xmm1,xmm0,0  
000000013F051038  movdqa      xmm0,xmmword ptr [__xi_z+28h (13F0521A0h)]  
000000013F051040  movdqa      xmmword ptr arr[rax*4],xmm0  
000000013F051046  paddd       xmm0,xmm1  
000000013F05104A  movdqa      xmmword ptr [rsp+rax*4+60h],xmm0  
000000013F051050  paddd       xmm0,xmm1  
000000013F051054  movdqa      xmmword ptr [rsp+rax*4+70h],xmm0  
000000013F05105A  paddd       xmm0,xmm1  
000000013F05105E  movdqa      xmmword ptr [rsp+rax*4+80h],xmm0  
000000013F051067  add         rax,10h  
000000013F05106B  paddd       xmm0,xmm1  
000000013F05106F  cmp         rax,400h  
000000013F051075  jb          wmain+40h (13F051040h)  

    QueryPerformanceCounter((LARGE_INTEGER*)&t1);
000000013F051077  lea         rcx,[t1]  
000000013F05107C  call        qword ptr [__imp_QueryPerformanceCounter (13F052008h)]  
            var += arrPtr[x];
000000013F051082  movdqa      xmm1,xmmword ptr [__xi_z+38h (13F0521B0h)]  

    for(int i = 0; i < 1024 * 1024 * 10; i++){
000000013F05108A  xor         eax,eax  
            var += arrPtr[x];
000000013F05108C  movdqa      xmm0,xmmword ptr [__xi_z+48h (13F0521C0h)]  
    long long var = 0, freq, t1, t2;
000000013F051094  pxor        xmm6,xmm6  
        for(int x = 0; x < 1024; x++){
000000013F051098  xor         r8d,r8d  
            var += arrPtr[x];
000000013F05109B  lea         rdx,[arr]  
000000013F0510A0  xor         ecx,ecx  
000000013F0510A2  movq        xmm2,mmword ptr arr[rcx]  
        for(int x = 0; x < 1024; x++){
000000013F0510A8  add         r8,8  
            var += arrPtr[x];
000000013F0510AC  punpckldq   xmm2,xmm2  
        for(int x = 0; x < 1024; x++){
000000013F0510B0  add         rcx,20h  
            var += arrPtr[x];
000000013F0510B4  movdqa      xmm3,xmm2  
000000013F0510B8  pand        xmm2,xmm0  
000000013F0510BC  movq        xmm4,mmword ptr [rdx+8]  
000000013F0510C1  psrad       xmm3,1Fh  
000000013F0510C6  punpckldq   xmm4,xmm4  
000000013F0510CA  pand        xmm3,xmm1  
000000013F0510CE  por         xmm3,xmm2  
000000013F0510D2  movdqa      xmm5,xmm4  
000000013F0510D6  movq        xmm2,mmword ptr [rdx+10h]  
000000013F0510DB  psrad       xmm5,1Fh  
000000013F0510E0  punpckldq   xmm2,xmm2  
000000013F0510E4  pand        xmm5,xmm1  
000000013F0510E8  paddq       xmm6,xmm3  
000000013F0510EC  pand        xmm4,xmm0  
000000013F0510F0  movdqa      xmm3,xmm2  
000000013F0510F4  por         xmm5,xmm4  
000000013F0510F8  psrad       xmm3,1Fh  
000000013F0510FD  movq        xmm4,mmword ptr [rdx+18h]  
000000013F051102  pand        xmm3,xmm1  
000000013F051106  punpckldq   xmm4,xmm4  
000000013F05110A  pand        xmm2,xmm0  
000000013F05110E  por         xmm3,xmm2  
000000013F051112  movdqa      xmm2,xmm4  
000000013F051116  paddq       xmm6,xmm5  
000000013F05111A  psrad       xmm2,1Fh  
000000013F05111F  pand        xmm4,xmm0  
000000013F051123  pand        xmm2,xmm1  
        for(int x = 0; x < 1024; x++){
000000013F051127  add         rdx,20h  
            var += arrPtr[x];
000000013F05112B  paddq       xmm6,xmm3  
000000013F05112F  por         xmm2,xmm4  
        for(int x = 0; x < 1024; x++){
000000013F051133  cmp         r8,400h  
            var += arrPtr[x];
000000013F05113A  paddq       xmm6,xmm2  
        for(int x = 0; x < 1024; x++){
000000013F05113E  jb          wmain+0A2h (13F0510A2h)  

    for(int i = 0; i < 1024 * 1024 * 10; i++){
000000013F051144  inc         eax  
000000013F051146  cmp         eax,0A00000h  
000000013F05114B  jb          wmain+98h (13F051098h)  
        }
    }

    QueryPerformanceCounter((LARGE_INTEGER*)&t2);
000000013F051151  lea         rcx,[t2]  
000000013F051156  call        qword ptr [__imp_QueryPerformanceCounter (13F052008h)]  

    printf("Unrestricted: %lld ms, Value = %lld\n", ((t2-t1)*1000/freq), var);
000000013F05115C  mov         r9,qword ptr [t2]  
    long long var = 0, freq, t1, t2;
000000013F051161  movdqa      xmm0,xmm6  

    printf("Unrestricted: %lld ms, Value = %lld\n", ((t2-t1)*1000/freq), var);
000000013F051165  sub         r9,qword ptr [t1]  
000000013F05116A  lea         rcx,[string "Unrestricted: %lld ms, Value = %"... (13F0521D0h)]  
000000013F051171  imul        rax,r9,3E8h  
000000013F051178  cqo  
000000013F05117A  mov         r10,qword ptr [freq]  
000000013F05117F  idiv        rax,r10  
    long long var = 0, freq, t1, t2;
000000013F051182  psrldq      xmm0,8  

    printf("Unrestricted: %lld ms, Value = %lld\n", ((t2-t1)*1000/freq), var);
000000013F051187  mov         rdx,rax  
    long long var = 0, freq, t1, t2;
000000013F05118A  paddq       xmm6,xmm0  
000000013F05118E  movd        r8,xmm6  

    printf("Unrestricted: %lld ms, Value = %lld\n", ((t2-t1)*1000/freq), var);
000000013F051193  call        qword ptr [__imp_printf (13F052108h)]  

而这个是 MSVC x64 构建的程序集:

int _tmain(int argc, _TCHAR* argv[])
{
000000013FF61000  push        rbx  
000000013FF61002  mov         eax,1050h  
000000013FF61007  call        __chkstk (13FF61950h)  
000000013FF6100C  sub         rsp,rax  
000000013FF6100F  mov         rax,qword ptr [__security_cookie (13FF63000h)]  
000000013FF61016  xor         rax,rsp  
000000013FF61019  mov         qword ptr [rsp+1040h],rax  
    long long var = 0, freq, t1, t2;
    std::array<int, 1024> arr;
    int* arrPtr = arr.data();
    QueryPerformanceFrequency((LARGE_INTEGER*)&freq);
000000013FF61021  lea         rcx,[rsp+28h]  
000000013FF61026  xor         ebx,ebx  
000000013FF61028  call        qword ptr [__imp_QueryPerformanceFrequency (13FF62000h)]  

    for(int i = 0; i < 1024; i++) arrPtr[i] = i;
000000013FF6102E  xor         r11d,r11d  
000000013FF61031  lea         rax,[rsp+40h]  
000000013FF61036  mov         dword ptr [rax],r11d  
000000013FF61039  inc         r11d  
000000013FF6103C  add         rax,4  
000000013FF61040  cmp         r11d,400h  
000000013FF61047  jl          wmain+36h (13FF61036h)  

    QueryPerformanceCounter((LARGE_INTEGER*)&t1);
000000013FF61049  lea         rcx,[rsp+20h]  
000000013FF6104E  call        qword ptr [__imp_QueryPerformanceCounter (13FF62008h)]  
000000013FF61054  mov         r11d,0A00000h  
000000013FF6105A  nop         word ptr [rax+rax]  

    for(int i = 0; i < 1024 * 1024 * 10; i++){
        for(int x = 0; x < 1024; x++){
000000013FF61060  xor         edx,edx  
000000013FF61062  xor         r8d,r8d  
000000013FF61065  lea         rcx,[rsp+48h]  
000000013FF6106A  xor         r9d,r9d  
000000013FF6106D  mov         r10d,100h  
000000013FF61073  nop         word ptr [rax+rax]  
            var += arrPtr[x];
000000013FF61080  movsxd      rax,dword ptr [rcx-8]  
000000013FF61084  add         rcx,10h  
000000013FF61088  add         rbx,rax  
000000013FF6108B  movsxd      rax,dword ptr [rcx-14h]  
000000013FF6108F  add         r9,rax  
000000013FF61092  movsxd      rax,dword ptr [rcx-10h]  
000000013FF61096  add         r8,rax  
000000013FF61099  movsxd      rax,dword ptr [rcx-0Ch]  
000000013FF6109D  add         rdx,rax  
000000013FF610A0  dec         r10  
000000013FF610A3  jne         wmain+80h (13FF61080h)  

    for(int i = 0; i < 1024 * 1024 * 10; i++){
        for(int x = 0; x < 1024; x++){
000000013FF610A5  lea         rax,[rdx+r8]  
000000013FF610A9  add         rax,r9  
000000013FF610AC  add         rbx,rax  
000000013FF610AF  dec         r11  
000000013FF610B2  jne         wmain+60h (13FF61060h)  
        }
    }

    QueryPerformanceCounter((LARGE_INTEGER*)&t2);
000000013FF610B4  lea         rcx,[rsp+30h]  
000000013FF610B9  call        qword ptr [__imp_QueryPerformanceCounter (13FF62008h)]  

    printf("Unrestricted: %lld ms, Value = %lld\n", ((t2-t1)*1000/freq), var);
000000013FF610BF  mov         rax,qword ptr [rsp+30h]  
000000013FF610C4  lea         rcx,[string "Unrestricted: %lld ms, Value = %"... (13FF621B0h)]  
000000013FF610CB  sub         rax,qword ptr [rsp+20h]  
000000013FF610D0  mov         r8,rbx  
000000013FF610D3  imul        rax,rax,3E8h  
000000013FF610DA  cqo  
000000013FF610DC  idiv        rax,qword ptr [rsp+28h]  
000000013FF610E1  mov         rdx,rax  
000000013FF610E4  call        qword ptr [__imp_printf (13FF62138h)]  

    return 0;
000000013FF610EA  xor         eax,eax  

英特尔编译器配置为没有矢量化、64 位、最高优化(这速度非常慢,12 秒):

000000013FC0102F  lea         rcx,[freq]  

    double var = 0; long long freq, t1, t2;
000000013FC01034  xorps       xmm6,xmm6  
    std::array<double, 1024> arr;
    double* arrPtr = arr.data();
    QueryPerformanceFrequency((LARGE_INTEGER*)&freq);
000000013FC01037  call        qword ptr [__imp_QueryPerformanceFrequency (13FC02000h)]  

    for(int i = 0; i < 1024; i++) arrPtr[i] = i;
000000013FC0103D  mov         eax,2  
000000013FC01042  mov         rdx,100000000h  
000000013FC0104C  movd        xmm0,eax  
000000013FC01050  xor         eax,eax  
000000013FC01052  pshufd      xmm1,xmm0,0  
000000013FC01057  movd        xmm0,rdx  
000000013FC0105C  nop         dword ptr [rax]  
000000013FC01060  cvtdq2pd    xmm2,xmm0  
000000013FC01064  paddd       xmm0,xmm1  
000000013FC01068  cvtdq2pd    xmm3,xmm0  
000000013FC0106C  paddd       xmm0,xmm1  
000000013FC01070  cvtdq2pd    xmm4,xmm0  
000000013FC01074  paddd       xmm0,xmm1  
000000013FC01078  cvtdq2pd    xmm5,xmm0  
000000013FC0107C  movaps      xmmword ptr arr[rax*8],xmm2  
000000013FC01081  paddd       xmm0,xmm1  
000000013FC01085  movaps      xmmword ptr [rsp+rax*8+60h],xmm3  
000000013FC0108A  movaps      xmmword ptr [rsp+rax*8+70h],xmm4  
000000013FC0108F  movaps      xmmword ptr [rsp+rax*8+80h],xmm5  
000000013FC01097  add         rax,8  
000000013FC0109B  cmp         rax,400h  
000000013FC010A1  jb          wmain+60h (13FC01060h)  

    QueryPerformanceCounter((LARGE_INTEGER*)&t1);
000000013FC010A3  lea         rcx,[t1]  
000000013FC010A8  call        qword ptr [__imp_QueryPerformanceCounter (13FC02008h)]  

    for(int i = 0; i < 1024 * 1024 * 10; i++){
000000013FC010AE  xor         eax,eax  
        for(int x = 0; x < 1024; x++){
000000013FC010B0  xor         edx,edx  
            var += arrPtr[x];
000000013FC010B2  lea         ecx,[rdx+rdx]  
        for(int x = 0; x < 1024; x++){
000000013FC010B5  inc         edx  
        for(int x = 0; x < 1024; x++){
000000013FC010B7  cmp         edx,200h  
            var += arrPtr[x];
000000013FC010BD  addsd       xmm6,mmword ptr arr[rcx*8]  
000000013FC010C3  addsd       xmm6,mmword ptr [rsp+rcx*8+58h]  
        for(int x = 0; x < 1024; x++){
000000013FC010C9  jb          wmain+0B2h (13FC010B2h)  

    for(int i = 0; i < 1024 * 1024 * 10; i++){
000000013FC010CB  inc         eax  
000000013FC010CD  cmp         eax,0A00000h  
000000013FC010D2  jb          wmain+0B0h (13FC010B0h)  
        }
    }

    QueryPerformanceCounter((LARGE_INTEGER*)&t2);
000000013FC010D4  lea         rcx,[t2]  
000000013FC010D9  call        qword ptr [__imp_QueryPerformanceCounter (13FC02008h)]  

英特尔编译器没有矢量化、32 位和最高优化(这显然是现在的赢家,运行大约 3 秒,组装看起来更好):

00B81088  lea         eax,[t1]  
00B8108C  push        eax  
00B8108D  call        dword ptr [__imp__QueryPerformanceCounter@4 (0B82004h)]  
00B81093  xor         eax,eax  
00B81095  pxor        xmm0,xmm0  
00B81099  movaps      xmm1,xmm0  
        for(int x = 0; x < 1024; x++){
00B8109C  xor         edx,edx  
            var += arrPtr[x];
00B8109E  addpd       xmm0,xmmword ptr arr[edx*8]  
00B810A4  addpd       xmm1,xmmword ptr [esp+edx*8+40h]  
00B810AA  addpd       xmm0,xmmword ptr [esp+edx*8+50h]  
00B810B0  addpd       xmm1,xmmword ptr [esp+edx*8+60h]  
        for(int x = 0; x < 1024; x++){
00B810B6  add         edx,8  
00B810B9  cmp         edx,400h  
00B810BF  jb          wmain+9Eh (0B8109Eh)  

    for(int i = 0; i < 1024 * 1024 * 10; i++){
00B810C1  inc         eax  
00B810C2  cmp         eax,0A00000h  
00B810C7  jb          wmain+9Ch (0B8109Ch)  

    double var = 0; long long freq, t1, t2;
00B810C9  addpd       xmm0,xmm1  
        }
    }

    QueryPerformanceCounter((LARGE_INTEGER*)&t2);
00B810CD  lea         eax,[t2]  
00B810D1  push        eax  
00B810D2  movaps      xmmword ptr [esp+4],xmm0  
00B810D7  call        dword ptr [__imp__QueryPerformanceCounter@4 (0B82004h)]  
00B810DD  movaps      xmm0,xmmword ptr [esp]

【问题讨论】:

  • 我不是这两种语言的专家,但您正在将直接数组与 std::array 类进行比较。直线阵列会更快,这是否令人惊讶?
  • @Mitch:我想std::array 是一个非常薄的原生数组包装器。
  • 我不确定这是否会产生重大影响,但您的处理器是 32 位还是 64 位?在 C++ 中,int 的大小取决于平台,但 Java 将 int 定义为 32 位。
  • @Chris:看起来就是这样。 C++ 代码编译为int 为 64 位。 @thesaint:尝试打印 sizeof(int) 以查看您使用的整数类型。
  • @casperOne:或者暂时搁置一下。如果他们不断被删除和移动,我不确定评论部分有什么意义。只需闭上眼睛,等我们解决问题并澄清问题,然后再进来执行您需要的任意规则。

标签: java c++ performance


【解决方案1】:

当你消除了不可能的事情后,剩下的无论多么不可能都是事实。

你一方面有一些数据,另一方面有一个假设(C++ 总是比 Java 快)。当数据告诉你的情况并非如此时,为什么还要要求人们证明你的假设是正确的?

如果您希望从 JVM 获取程序集以比较正在运行的内容,则命令行选项为“-XX:+PrintOptoAssembly”,但您需要下载调试 jvm 才能执行此操作。查看程序集至少会告诉您为什么一个比另一个快。

【讨论】:

  • 人们坚持认为 Java 很慢,但自从十几年前他们发明了 JIT 以来,它并没有太大的不同。
  • +1 福尔摩斯的名言。
  • 并不是说 Java 会很慢 ;)。我们在谈论 Java 快 40%,这是不可能的!最多它应该和 C++ 一样快,即使这样也会令人惊讶,事实上它不是(在几秒钟内看到上面我的编辑)
  • MSVC x64 比 Java 快。在这种情况下,结果表明编译器比语言更多。
  • @TimCooper - 这些基准几乎总是告诉你更多关于编译器的信息,而不是语言。发帖人似乎没有意识到这一点,而是指的是“Java”,实际上他应该指的是热点或任何实现。
【解决方案2】:

我认为 Java 编译器实现了 JITC(即时编译,或一些更新的技术)以接近原生编译器的速度,并且可以推断出您的数组不会改变,因此可以将常量折叠应用于内部循环.. .

【讨论】:

  • @thesaint:不完全是,JIT 可以做诸如内联虚拟调用之类的事情,这是任何编译时优化都无法做到的。它原则上需要运行时编译。 (这当然与问题无关。)
  • @GMan:我不敢苟同。 Profile-Guided-Optimization 正是这样做的。它将“查看”每个虚拟调用可能有哪些对象实例,并发出反映该知识的适当代码。就像任何虚拟机一样。
  • @thesaint:错误。根本无法在编译时优化此代码以比运行时编译更快:Base* x = load_some_dynamic_thing(); for (;;) { x-&gt;dynamic_dispatch(); }。无论您的编译时优化有多好,您都永远不会摆脱那个虚函数调用,因为编译器无法猜测运行时将要做什么(考虑动态链接等等)。相反,VM 确切地知道它要做什么,因为它正在运行它。它可以内联代码并重新编译。
  • 我认为争论这个没有多大意义。任何具有 Profile-Guided 优化的现代 C++ 编译器都将检测可疑的“x->dynamic()”调用,并查看(或者可能没有,但 Java VM 也不走运)“x”指向的对象指针最多某种类型的“T”的时间。然后它将内联一些分支预测,或者假设假设“x”的对象类型是“T”的预测“热路径”。并且虚拟呼叫惩罚仅适用于 x 属于“T”以外的其他类型。
  • 支持 GMans 观点的一个例子:考虑一个具有三个难度级别的游戏,以及针对每个难度级别/敌人类型生成一个符合接口的不同类型对象的实现。在编译时,您无法知道将选择什么难度级别,但是一旦游戏开始,JIT 就会看到只有 EasyBeast 对象在使用中。同样,基于配置文件的优化也无法优化,因为开发人员会在不同难度级别上配置游戏。
【解决方案3】:

我怀疑罪魁祸首是简单的循环展开。替换

var += arrPtr[x];

var += arrPtr[x++];
var += arrPtr[x++];
var += arrPtr[x++];
var += arrPtr[x];

并观察 C++ 版本的运行速度有多快。

【讨论】:

  • 如果你看一下汇编输出,你会发现这已经完成了,尽管它非常晦涩......
【解决方案4】:

为了记录,我在我的盒子 (x86_64 linux) 上运行了这两个代码,使用 std::array 的 C++,一个普通的 int[1024] 以及为了完整性还使用 long 而不是 int。 Java (open-jdk 1.6) 为 3.8 秒,C++ (int) 为 3.37 秒,C++ (long) 为 3.9 秒。我的编译器是 g++ 4.5.1。也许只是英特尔的编译器没有想象中那么好。

【讨论】:

  • 很高兴知道 Linux 已经迎头赶上 ;)。上次我将 GCC 与 MSVC 进行比较(相当多年前),它的性能远不及性能......
  • @thesaint:实际上我发现 MSVC 做得这么好更令人惊讶,因为我能找到的关于该主题的所有内容都表明 MSVC 在优化代码方面比 gcc 和 icc 差得多(在这两者之间) gcc 最近似乎以微弱优势获胜)。当然,当涉及到小样本的性能时,如果一个编译器错过了一项特定的优化,或者对目标平台上的时间不够了解,那么可能会有很大的差异
  • @thesaint : Daniel 没有比较 他的 机器上的 MSVC,它可能 比 GCC 快。就像其他 cmets 提到的那样,这个例子太简单了,无法证明。也许你应该尝试更复杂的算法和更多的约束(即对象......)
  • 我以后一定会这样做的。这只是对完全不同的东西的一些简单测试(设计一个缓存感知对象容器),如果英特尔编译器不会执行得这么慢,我永远不会启动这个线程;)
  • @Grizzly 说到 MSVC 在优化方面表现异常出色......这可能是一个有趣的阅读:stackoverflow.com/questions/8389648/…
【解决方案5】:

这个例子很简单,不同的语言不应该有区别,而且很愚蠢,不能证明任何事情。循环可以由编译器优化为一个简单的赋值,或者在整个迭代次数内运行,或者一些迭代可能被展开......我不确定你为什么决定编写那个测试程序,但它不对语言进行任何测试,因为一旦执行了逻辑优化,一切都归结为完全相同的程序集。

另外,关于英特尔编译器的性能,很大程度上取决于具体的硬件和编译器版本。编译器生成不同版本的代码,并且倾向于为 AMD 处理器生成可怕的代码。即使对于英特尔,如果它无法识别特定的处理器,它也会退回到安全的慢速模式。

【讨论】:

  • 这很好。我想英特尔在他们最新的编译器中已经放弃了对 Q6600 的支持。否则就没有意义了。但也许有最新英特尔处理器的人可以检查一下?!
  • +1 以获得最准确的答案:所讨论的基准是如此愚蠢以至于无关紧要。
  • @DeadMg:我不同意。如果编译器无法为琐碎的示例生成优化代码,那么它如何为复杂的示例生成呢?我的意思是没有什么。它恰恰相反。 Java 尤其擅长不涉及对象的代码。一旦事情变得糟糕,Java就会崩溃。其他编译器也是如此。如果基础不及格,就无法掌握复杂的内容^^。
  • @thesaint 您应该致力于优化您的程序和与您的程序类似的代码模式。在您的程序中,用于检测您的测量的代码大于您正在检测的代码。循环的成本大于加法操作的成本,例如,如果编译器运行内部循环,然后将外部循环转换为乘法,它将为这个特定示例生成非常快的代码,但这并不能保证在任何其他情况下,即使迭代次数发生变化,它也不会生效。如果需要,Google for 循环展开。
  • @DeadMg:关键是这不需要特殊情况优化。这个循环非常简单,编译器可以通过使用循环展开、自动矢量化、并行化和类似的东西来发光。如果这三个循环变量在没有太多逻辑的情况下都失败了,那么如果它对更多变量和更复杂的逻辑工作得更好,那将是非常令人惊讶和不合逻辑的。编译器不需要为此进行优化。这是一种常见的模式,编译器需要能够检测和处理。就是这样……
【解决方案6】:

tl;dr:您在这里看到的似乎是 ICC 尝试矢量化循环失败

让我们从 MSVC x64 开始:

这是关键循环:

$LL3@main:
movsxd  rax, DWORD PTR [rdx-4]
movsxd  rcx, DWORD PTR [rdx-8]
add rdx, 16
add r10, rax
movsxd  rax, DWORD PTR [rdx-16]
add rbx, rcx
add r9, rax
movsxd  rax, DWORD PTR [rdx-12]
add r8, rax
dec r11
jne SHORT $LL3@main

您在此处看到的是编译器展开的标准循环。 MSVC 展开到 4 次迭代,并将 var 变量拆分为四个寄存器:r10rbxr9r8。然后在循环结束时,将这 4 个寄存器加在一起。

这是重新组合 4 个总和的地方:

lea rax, QWORD PTR [r8+r9]
add rax, r10
add rbx, rax
dec rdi
jne SHORT $LL6@main

请注意,MSVC 目前不进行自动矢量化。


现在让我们看看你的 ICC 输出的一部分:

000000013F0510A2  movq        xmm2,mmword ptr arr[rcx]  
000000013F0510A8  add         r8,8  
000000013F0510AC  punpckldq   xmm2,xmm2  
000000013F0510B0  add         rcx,20h  
000000013F0510B4  movdqa      xmm3,xmm2  
000000013F0510B8  pand        xmm2,xmm0  
000000013F0510BC  movq        xmm4,mmword ptr [rdx+8]  
000000013F0510C1  psrad       xmm3,1Fh  
000000013F0510C6  punpckldq   xmm4,xmm4  
000000013F0510CA  pand        xmm3,xmm1  
000000013F0510CE  por         xmm3,xmm2  
000000013F0510D2  movdqa      xmm5,xmm4  
000000013F0510D6  movq        xmm2,mmword ptr [rdx+10h]  
000000013F0510DB  psrad       xmm5,1Fh  
000000013F0510E0  punpckldq   xmm2,xmm2  
000000013F0510E4  pand        xmm5,xmm1  
000000013F0510E8  paddq       xmm6,xmm3  

...

您在这里看到的是 ICC 尝试对这个循环进行矢量化。这与 MSVC 的做法类似(拆分为多个总和),但使用 SSE 寄存器,每个寄存器有两个总和。

但事实证明,矢量化的开销恰好超过了矢量化的好处。

如果我们逐一执行这些指令,我们可以看到 ICC 如何尝试对其进行矢量化:

//  Load two ints using a 64-bit load.  {x, y, 0, 0}
movq        xmm2,mmword ptr arr[rcx]  

//  Shuffle the data into this form.
punpckldq   xmm2,xmm2           xmm2 = {x, x, y, y}
movdqa      xmm3,xmm2           xmm3 = {x, x, y, y}

//  Mask out index 1 and 3.
pand        xmm2,xmm0           xmm2 = {x, 0, y, 0}

//  Arithmetic right-shift to copy sign-bit across the word.
psrad       xmm3,1Fh            xmm3 = {sign(x), sign(x), sign(y), sign(y)}

//  Mask out index 0 and 2.
pand        xmm3,xmm1           xmm3 = {0, sign(x), 0, sign(y)}

//  Combine to get sign-extended values.
por         xmm3,xmm2           xmm3 = {x, sign(x), y, sign(y)}
                                xmm3 = {x, y}

//  Add to accumulator...
paddq       xmm6,xmm3

所以它只是为了矢量化而进行了一些非常混乱的解包。混乱来自需要仅使用 SSE 指令将 32 位整数符号扩展为 64 位。

SSE4.1 实际上为此提供了PMOVSXDQ 指令。但要么目标机器不支持 SSE4.1,要么 ICC 不够聪明,无法在这种情况下使用它。

但重点是:

英特尔编译器正在尝试向量化循环。但是增加的开销似乎超过了首先矢量化它的好处。因此为什么它更慢。


编辑:更新 OP 的结果:

  • ICC x64 无矢量化
  • 带有矢量化的 ICC x86

您将数据类型更改为double。所以现在它是浮点数。不再有困扰整数版本的那些丑陋的符号填充移位。

但由于您禁用了 x64 版本的矢量化,它显然变慢了。

带矢量化的 ICC x86:

00B8109E  addpd       xmm0,xmmword ptr arr[edx*8]  
00B810A4  addpd       xmm1,xmmword ptr [esp+edx*8+40h]  
00B810AA  addpd       xmm0,xmmword ptr [esp+edx*8+50h]  
00B810B0  addpd       xmm1,xmmword ptr [esp+edx*8+60h]  
00B810B6  add         edx,8  
00B810B9  cmp         edx,400h  
00B810BF  jb          wmain+9Eh (0B8109Eh)  

这里不多 - 标准矢量化 + 4x 循环展开。

没有矢量化的 ICC x64:

000000013FC010B2  lea         ecx,[rdx+rdx]  
000000013FC010B5  inc         edx  
000000013FC010B7  cmp         edx,200h  
000000013FC010BD  addsd       xmm6,mmword ptr arr[rcx*8]  
000000013FC010C3  addsd       xmm6,mmword ptr [rsp+rcx*8+58h]  
000000013FC010C9  jb          wmain+0B2h (13FC010B2h)  

无矢量化 + 仅 2 次循环展开。

在所有条件相同的情况下,禁用矢量化将损害这种浮点情况下的性能。

【讨论】:

  • +1 用于此分析 ;)。但是问题仍然存在,为什么英特尔编译器不知道它的速度较慢。我的意思是我们不是在谈论一些百分之几。与 MSVC 相比,这几乎是 100% 的巨大差异!
  • +1,在没有优化的情况下尝试 ICC 可能会产生令人惊讶的结果。
  • @thesaint 编译器并不完美。尽管它们可能在 99% 的时间里超过人类。它们仍然是由人类编写的。所以这并不意味着他们总是做出最好的优化选择。我见过很多编译器(ICC、GCC、MSVC)做了一些我无法解释的非常愚蠢的事情。
  • 好的,但我只是认为至少有这样简单的例子,它们应该提供最好(正确)的答案......
  • 我刚刚在 ICC 11 中测试了这个,它实际上更快。这里发生的事情是,ICC 12 添加了一个新的(且强大的)转换来帮助矢量化。现在很清楚,我已经查看了程序集。它完成了我通常必须手动完成的事情。由于这是新功能,英特尔可能尚未完成此功能的调整。因此,您在新的优化功能中遇到了麻烦。
【解决方案7】:

我看到你正在运行以下循环

for(int i = 0; i < 1024 * 1024; i++){
        for(int x = 0; x < 1024; x++){
            var += arr[x];
        }
    }

Java 代码中的两次;而在 C++ 代码中; 这可能会带来缓存预热,从而使 Java 代码最终比 C++ 执行得更快。

【讨论】:

  • 运行两次是为了让它被 JIT 命中。通常您也希望在 C 中预热缓存,但在这种情况下,差异太大,无法仅用缓存来解释。
  • 缓存未命中不是问题,我通过 VTune Analyzer 运行了这个。此外,for 循环需要很长时间,因为一点缓存预热可以解释这些结果。正如你现在所看到的,事实并非如此。这很简单,编译器失败!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-09
  • 1970-01-01
  • 2012-02-19
  • 2017-11-04
  • 2020-08-29
  • 2011-03-24
  • 2010-12-19
相关资源
最近更新 更多