2015 01 17 23 03 [c] CPU 分支預測對於程式的影響(2)

上一篇文章是測試MinGW64 gcc編譯的效果.
這篇則是要來試試看Vistual C++ 2013的效果.
據Stack Overflow上面文章是說,
VC++ 2010即使是加/Ox, 也是無法產生conditional move(CMOV)指令.
但是我在Stack Overflow上有看到一篇文章,
是說VS2012 C++產生的conditional move有問題,
雖然只是一場誤會. 但還是有產生CMOV的指令.
那我就好奇了,那Vistual C++ 2013會不會變聰明呢?
一樣是用上一篇的C版本來編譯執行,
一開始還是只是用/O2,
32bits執行檔的執行結果是:
=== test1 ===
10.500000       sum = 312275400000

After Sorted...

=== test1 ===
2.893000        sum = 312275400000

64bits執行檔的執行結果是:
=== test1 ===
13.217000       sum = 312275400000

After Sorted...

=== test1 ===
3.542000        sum = 312275400000

看起來兩者差距不大,
而且還是32bits快了一點.
那直接開/Ox來編譯呢?
結果如下:
=== test1 ===
13.218000       sum = 312275400000

After Sorted...

=== test1 ===
3.540000        sum = 312275400000

不管是64bits或是32bits真的是沒差~~~
可是VS2013 C++應該是有支援CMOV指令,
可能是因為寫法的關係,
編譯器不認為需要用CMOV指令,
那我來改個寫法試試看.
=======================

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void test1(int *data, int arraySize, unsigned int num)
{
    clock_t start;
    long long sum = 0;
    int v = 0;
    double elapsedTime;
    unsigned int i;
    int c;

    start = clock();

    for (i = 0; i < num; ++i) {
        // Primary loop
        for (c = 0; c < arraySize; ++c) {
            if (data[c] >= 128) {
                sum += data[c];
            }
        }
    }

    elapsedTime = (double) (clock() - start) / CLOCKS_PER_SEC;
    printf("=== %s ===\n", __FUNCTION__);
    printf("%lf\t", elapsedTime);
    printf("sum = %lld\n", sum);
}

void test2(int *data, int arraySize, unsigned int num)
{
    clock_t start;
    long long sum = 0;
    int v = 0;
    double elapsedTime;
    unsigned int i;
    int c;
    start = clock();

    for (i = 0; i < num; ++i) {
        // Primary loop
        for (c = 0; c < arraySize; ++c) {
            v = 0;
            if (data[c] >= 128) {
                v = data[c];
            }
            sum += v;
        }
    }

    elapsedTime = (double) (clock() - start) / CLOCKS_PER_SEC;
    printf("=== %s ===\n", __FUNCTION__);
    printf("%lf\t", elapsedTime);
    printf("sum = %lld\n", sum);
}

#define arraySize  32768
int main()
{
    int data[arraySize];
    int i;

    for (i = 0; i < arraySize; ++i)
        data[i] = rand() % 256;

    test1(data, arraySize, 100000);
    test2(data, arraySize, 100000);

    test1(data, arraySize, 100000);
    test2(data, arraySize, 100000);

    return 0;
}

=======================
function test1 就是原本的code,

            if (data[c] >= 128) {
                sum += data[c];
            }

而 function test2 則是改了寫法.

            v = 0;
            if (data[c] >= 128) {
                v = data[c];
            }
            sum += v;

因為知道CMOV是用來執行某個條件才搬移資料,
所以故意寫成這樣,
乍看之下, 應該會覺得test2() 會變慢,
因為會比test1()多做搬移的動作,也會多了一些加法運算.
不過在VS2013用/O2編譯,
執行結果如下:
=== test1 ===
13.212000       sum = 312275400000
=== test2 ===
3.134000        sum = 312275400000
=== test1 ===
13.215000       sum = 312275400000
=== test2 ===
3.132000        sum = 312275400000

 

用test2的方法比test1快多了.
差不多就是用gcc -O3的速度.
開disassembly視窗來看,
000000013FAE1120 33 D2                xor         edx,edx  
000000013FAE1122 41 81 38 80 00 00 00 cmp         dword ptr [r8],80h  
000000013FAE1129 41 0F 4D 10          cmovge      edx,dword ptr [r8] 
的確用了CMOV指令.

所以VS2013 C++是會用CMOV指令,
只是前提是你的程式碼要寫的讓編譯器認得,
它才會使用CMOV指令.
以後看到這種有點笨笨的寫法,
別以為別人程式寫的爛,
說不定他是為了讓編譯器認得~~~

後來又開了 /Ox 和 /arch:SSE2 也都沒什麼差別.
因為我的CPU是AMD,也沒辦法試其他的指令集.

那麼回到MinGW64 gcc呢?
還是用-O3去編譯, 跑出來的結果如下:
$ ./a.exe
=== test1 ===
3.997000        sum = 312275400000
=== test2 ===
1.298000        sum = 312275400000
=== test1 ===
3.993000        sum = 312275400000
=== test2 ===
1.299000        sum = 312275400000

什麼? 那用 -O2呢?
=== test1 ===
13.218000       sum = 312275400000
=== test2 ===
4.112000        sum = 312275400000
=== test1 ===
13.218000       sum = 312275400000
=== test2 ===
4.116000        sum = 312275400000

看樣子gcc也是需要認到某種寫法,才能最佳化.
即使CPU不夠強,
有個好的Compiler和好的寫法,
是有辦法跑出好的效果的. ^__^