4

私の質問はばかげているように見えるかもしれませんが、なぜこの非常に単純なコードでパフォーマンスが向上するのか、非常に興味があります。

アセンブリコードは次のとおりです。

__asm {
    mov eax, 0
    mov ecx, 0
    jmp startloop
    notequal:
    inc eax
    mov ecx, eax
    sub ecx, 2
    startloop:
    cmp eax, 2000000000
    jne notequal
};

これはCコードです:

long x = 0;
long ii = 0;
for(; ii < 2000000000; ++ii)
{
    x = ii - 2;
};

C コードは、i5 2500k マシンで完了するのに約 1060 ミリ秒 (リリース ビルドで) かかり、アセンブリは 780 ミリ秒で終了します。速度が最大 25% 向上します。25% の差が大きいため、なぜこの結果が得られるのかわかりません。コンパイラは、私が書いたものと同等のアセンブリ コードを生成するほどスマートではありませんか?

ところで、MSVC 2010 を使用しています。

ありがとう


これは、MSVC によって生成されている (asm) コードです。

$LL3@main:
; Line 36
    lea esi, DWORD PTR [eax-2]
    inc eax
    cmp eax, 2000000000             ; 77359400H
    jl  SHORT $LL3@main

この場合、リー命令は何をしますか?

更新 2


皆様、本当にありがとうございました。Nehalem xeon cpu でこのコードをテストしたところ、結果は同じです。理由は不明ですが、asm コードは Sandy ブリッジでより高速に実行されるようです。

4

3 に答える 3

2

非asmバージョンを比較しました:

#include <iostream>
#include <chrono>

int main() {
    auto start = std::chrono::high_resolution_clock::now();

    long x = 0;
    long ii = 0;
    for(; ii < 2000000000; ++ii)
    {
        x = ii - 2;
    };

    auto finish = std::chrono::high_resolution_clock::now();
    std::cout << (finish-start).count() << '\n';
    std::cout << x << ii << '\n';
}

asm バージョンの場合:

#include <iostream>
#include <chrono>

int main() {
    auto start = std::chrono::high_resolution_clock::now();

    asm (R"(
         mov $0, %eax
         mov $0, %ecx
         jmp startloop
         notequal:
         inc %eax
         mov %eax,%ecx
         sub $2,%ecx
         startloop:
         cmp $2000000000,%eax
         jne notequal
    )");

    auto finish = std::chrono::high_resolution_clock::now();
    std::cout << (finish-start).count() << '\n';
}

clang 3.1 の使用

最適化をオンにすると、asm バージョンでは約 1.4 秒かかり、非 asm バージョンでは 45 ナノ秒かかりました。これにより、アセンブリ バージョンは約 3200 万パーセント遅くなります。

非 asm バージョン用に生成されたアセンブリは次のとおりです。

movl    $1999999997, %esi       ## imm = 0x773593FD
callq   __ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEElsEl
movq    %rax, %rdi
movl    $2000000000, %esi       ## imm = 0x77359400
callq   __ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEElsEl
于 2012-04-09T23:57:27.560 に答える
2

@modelnine のコメントは正しいleaです。これは、ループ内の割り当てを簡素化するために使用されています。あなたが持っている:

x = ii - 2;

そして、lea(実効アドレスのロード)命令は効果的に行っています:

esi = &(*(eax - 2));

&*が互いに打ち消し合う (これは重要です。この場合、逆参照はeaxおそらく問題を引き起こします)。したがって、次のようになります。

esi = eax - 2;

Cコードがやろうとしていたこととまったく同じです。

于 2012-04-09T23:27:20.880 に答える
1

試してみませんgcc -Ofastgcc -O1

そしてここにティーザーがあります: gcc -Q -Ofast --help=optimizers、gnuマニュアルからです!

ここに比較があります:

section .text
global _start

_start:
    mov eax, 0
    mov ecx, 0
    jmp startloop
    notequal:
    inc eax
    mov ecx, eax
    sub ecx, 2
    startloop:
    cmp eax, 2000000000
    jne notequal

    int     0x80

    mov     ebx,0
    mov     eax,1
    int     0x80

私が得たもの1.306msと、Cのタイミングは次のとおりです。

real    0m0.001s
user    0m0.000s
sys     0m0.000s

gcc -O1時限の使用は次のとおりでした:

real    0m1.295s
user    0m1.262s
sys     0m0.006s

実際にコードを実行します。

MSVC の場合、/O2 または /O1 コンパイル オプションを使用して同様の結果を得ることができます。詳細はこちらhttp://msdn.microsoft.com/en-us/library/k1ack8f1.aspx

于 2012-04-09T23:57:03.333 に答える