常用腳本的一份性能測試

測試方法

計算任務(wù):如果某數(shù)字,各位數(shù)的立方之和等于它自身,則符合條件殖演。找出 500 萬以內(nèi)所有符合條件的數(shù)锦秒。
為了測試語言本身,而非算法廉羔,上述計算不考慮剪枝判斷,用最硬的辦法做。
上述任務(wù)運行五次轴捎,測量總耗時,除以五蚕脏,作為平均耗時侦副。耗時越低越好。

測試結(jié)果

語言 代碼寫法 環(huán)境或者虛擬機 是否 JIT 耗時(秒)
C++ int Visual Studio 2019, x64, Release 0.038
C++ double, mod Visual Studio 2019, x64, Release 0.423
C++ double, without mod Visual Studio 2019, x64, Release 0.231
C++ (AVX2) int Visual Studio 2019, x64, Release 0.031
C++ (AVX2) double, without mod Visual Studio 2019, x64, Release 0.021
C++ (emscripten, WebAssembly) int emscripten 2.0.29, -O3, node.js 14.17.3 Yes 0.129
C++ (emscripten, WebAssembly) int emscripten 2.0.29, -O3, node.js 14.17.3, --jitless 報錯
C++ (emscripten, asm.js) int emscripten 2.0.29, -O3, node.js 14.17.3 Yes 0.064
C++ (emscripten, asm.js) int emscripten 2.0.29, -O3, node.js 14.17.3, --jitless 3.201
python int CPython 2.7.5 3.923
python int CPython 3.9.1 3.810
python int pypy2.7-v7.3.5-win64 Yes 0.094
python int pypy3.7-v7.3.5-win64 Yes 0.094
javascript int node.js 14.17.3 Yes 0.097
javascript int node.js 14.17.3 --jitless 1.721
javascript double, mod node.js 14.17.3 Yes 0.202
javascript double, mod node.js 14.17.3 --jitless 2.215
javascript double, without mod node.js 14.17.3 Yes 0.221
javascript double, without mod node.js 14.17.3 --jitless 2.244
lua double, mod lua 5.4.3 2.477
lua double, mod lua jit 2.0.5 Yes 0.115
lua double, mod lua jit 2.0.5 - without jit 1.298
lua double, without mod lua 5.4.3 2.183
lua double, without mod lua jit 2.0.5 Yes 0.118
lua double, without mod lua jit 2.0.5 - without jit 1.276

結(jié)論

  • 開啟 JIT 的情況下驼鞭,各個腳本語言的性能都接近秦驯。最快到最慢依次是 python, js, lua
    要注意 node.js 如果用了 math.floor 會變慢。lua 看起來沒有這個問題挣棕。
  • 關(guān)閉 JIT 的情況下译隘,各個腳本語言的性能差異大。最快到最慢依次是 lua, js, python
    在本次測試中洛心,luajit 即使關(guān)閉了 JIT 仍然比 lua 快很多固耘。
    但這僅僅是一個小測試,可能需要更多的測試去評估綜合性能词身。
  • C++ 當然是最快的厅目,腳本難以望其項背,即便是 JIT 也有數(shù)倍的差距法严。
    而且 C++ 可以采用 SIMD 可能進一步提升损敷。
    但 C++ 用不好的情況下,可能不如腳本 JIT深啤。這就比較尷尬尷尬拗馒。
  • 腳本是有天花板的。
    如果感覺腳本慢溯街,最好的辦法是用 C++ 改寫诱桂,而不是換一種腳本語言。
    換言之呈昔,選擇腳本語言時访诱,一般不應(yīng)該把性能作為主要參考。
  • emscripten 把 C++ 編譯為“腳本”韩肝。技術(shù)上是行得通触菜,但是不至于真的用 C++ 來寫邏輯。
    相比腳本哀峻,寫 C++ 的工作量更大涡相。
    用 emscripten 編譯之后沒有明顯的性能優(yōu)勢哲泊。特別是在禁止 JIT 的平臺上。
    所以是吃力不討好催蝗。
    emscripten 目前的價值切威,是方便把 C++ 代碼移植到 javascript 生態(tài)”牛看起來并不適合正常開發(fā)先朦。

細節(jié)描述

是否用 JIT ,要做出區(qū)分犬缨。因為 iOS 目前基本上不能用 JIT喳魏。
不過逐漸開始放開了,特別是對于 javascript怀薛。

本計算刺彩,首要區(qū)別在于數(shù)值的類型。int 會比 double 快枝恋。
lua 和 javascript 是用 double创倔,但虛擬機可能視情況選擇用 int。
而 python 和 C++ 則同時提供了 int 和 double 供程序員選擇焚碌。
首先用 C++ 在 int 和 double 的情況下分別測試畦攘。

1. C++

一開始代碼是按 int 寫的。
在這個版本里十电,除以常數(shù) 10念搬,其實在編譯之后根本不含有除法指令,除法被改為了乘法摆出,速度有所提高。

            int x = v % 10;
00007FF7B94A1090  mov         eax,0CCCCCCCDh  
00007FF7B94A1095  mul         eax,ecx  
00007FF7B94A1097  shr         edx,3  
00007FF7B94A109A  lea         eax,[rdx+rdx*4]  
00007FF7B94A109D  add         eax,eax  
00007FF7B94A109F  sub         ecx,eax  
00007FF7B94A10A1  mov         r8d,ecx  
            v = (v / 10);
00007FF7B94A10A4  mov         ecx,edx  

然后直接翻譯為 double首妖,發(fā)現(xiàn):fmod 耗時占 76%偎漫,floor 耗時占 19%。
基于此有缆,又做了一個避免求余數(shù)的版本象踊。

為了繼續(xù)提高,又做了 AVX 的版本棚壁。
可以看到在 AVX 中杯矩,double 會比 int 更快。即便 double 是四個四個處理袖外,而 int 是八個八個處理史隆。
因為 AVX 的乘除法,int 會比 double 慢曼验。特別是除法泌射,AVX 沒有整數(shù)除法指令粘姜,編譯器提供了一個函數(shù)來實現(xiàn)除法。

// 0.038 秒
__declspec(noinline)
void test_int(int N)
{
    for (int i = 1; i <= N; ++i) {
        int s = 0;
        int v = i;
        while (v > 0) {
            int x = v % 10;
            v = (v / 10);
            s += x * x * x;
        }

        if (s == i) {
            printf("%d\n", s);
        }
    }
}

// 0.423 秒
__declspec(noinline)
void test_double_with_fmod(int N)
{
    double NN = N;
    for (double i = 1; i <= NN; ++i) {
        double s = 0.0;
        double v = i;
        while (v > 0) {
            double x = fmod(v, 10.0);
            v = floor(v / 10);
            s += x * x * x;
        }

        if (s == i) {
            printf("%d\n", i);
        }
    }
}

// 0.231 秒
__declspec(noinline)
void test_double_without_fmod(int N)
{
    double NN = N;
    for (double i = 1; i <= NN; ++i) {
        double s = 0.0;
        double v = i;
        while (v > 0) {
            double v2 = floor(v / 10);
            double x = v - v2 * 10;
            v = v2;
            s += x * x * x;
        }

        if (s == i) {
            printf("%d\n", i);
        }
    }
}

// 0.021 秒
__declspec(noinline)
void test_avx2_double(int N)
{
    __declspec(align(16)) double val_arr[] = { 1.0, 2.0, 3.0, 4.0 };

    __m256d val = _mm256_load_pd(val_arr);
    __m256d four = _mm256_set1_pd(4.0);
    __m256d ten = _mm256_set1_pd(10.0);

    for (int i = 1; i <= N; i += 4, val = _mm256_add_pd(val, four)) {
        __m256d s = _mm256_setzero_pd();

        __m256d v = val;
        for (int d = 1; d <= i + 3; d *= 10) {
            __m256d v2 = _mm256_round_pd(_mm256_div_pd(v, ten), _MM_FROUND_FLOOR);
            __m256d x = _mm256_sub_pd(v, _mm256_mul_pd(v2, ten));
            v = v2;
            s = _mm256_add_pd(_mm256_mul_pd(_mm256_mul_pd(x, x), x), s);
        }

        int eq = _mm256_movemask_pd(_mm256_cmp_pd(val, s, _CMP_EQ_OQ));
        if (eq != 0) {
            for (int v = 0; v < 4; ++v) {
                if ((eq & (1 << v)) != 0) {
                    printf("%d\n", i + v);
                }
            }
        }
    }
}

// 0.031 秒
__declspec(noinline)
void test_avx2_int(int N)
{
    int val_arr[] = { 1, 2, 3, 4, 5, 6, 7, 8 };

    __m256i val = _mm256_loadu_si256(reinterpret_cast<__m256i*>(val_arr));
    __m256i eight = _mm256_set1_epi32(8);
    __m256i ten = _mm256_set1_epi32(10);

    for (int i = 1; i <= N; i += 8, val = _mm256_add_epi32(val, eight)) {
        __m256i s = _mm256_setzero_si256();

        __m256i v = val;
        for (int d = 1; d <= i + 7; d *= 10) {
            __m256i v2 = _mm256_div_epi32(v, ten);
            __m256i x = _mm256_sub_epi32(v, _mm256_mullo_epi32(v2, ten));
            v = v2;
            s = _mm256_add_epi32(_mm256_mullo_epi32(_mm256_mullo_epi32(x, x), x), s);
        }

        int eq = _mm256_movemask_epi8(_mm256_cmpeq_epi32(val, s));
        if (eq != 0) {
            for (int v = 0; v < 8; ++v) {
                if ((eq & (1 << (v*4))) != 0) {
                    printf("%d\n", i + v);
                }
            }
        }
    }
}

2. C++ and emscripten

這種奇怪的組合允許動態(tài)加載 C++ 模塊熔酷。
意味著 C++ 代碼可以做成 patch 外放孤紧,而且如果用 asm.js 的話還不用擔心權(quán)限問題,就像腳本一樣拒秘。
實測發(fā)現(xiàn) asm.js 竟然是 JIT 里最快的号显。但是關(guān)掉 jit 之后就很慢,甚至遠不如直接寫 js躺酒。
WebAssembly 速度不如 asm.js押蚤,是我沒想到的。
原因不太清楚阴颖。但事實如此活喊。
總之這些測試結(jié)果比較違反直覺,可能是因為我不熟悉吧量愧。

另外:WebAssembly 在關(guān)閉 JIT 的情況下是無法使用的钾菊,需要注意。

// compile to WebAssembly: emcc main.cpp -O3
// compile to asm.js: emcc main.cpp -O3 -s WASM=0

#include <chrono>
#include <cstdio>

void test_calculate_int(int N)
{
    for (int i = 1; i <= N; ++i) {
        int s = 0;
        int v = i;
        while (v > 0) {
            int x = v % 10;
            v = (v / 10);
            s += x * x * x;
        }

        if (s == i) {
            printf("%d\n", s);
        }
    }
}

int main()
{
    std::chrono::high_resolution_clock::time_point t1, t2;

    t1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 5; ++i)
    {
        test_calculate_int(5000000);
    }
    t2 = std::chrono::high_resolution_clock::now();
    printf("execution cost: %.3f seconds\n", double(std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count()) / 5.0 / 1000.0);
    return 0;
}

3. python

python 本身有 int偎肃,不必使用 double 類型了煞烫。只測試了一種寫法。

import timeit
import platform

version = platform.python_version_tuple()[0]
if version == '2':
    my_range = xrange
else:
    my_range = range

def test(N):
    for i in my_range(1, N + 1):
        s = 0
        v = i
        while v > 0:
            x = v % 10
            v //= 10
            s += x * x * x

        if s == i:
            print(s)

cost = timeit.timeit(lambda: test(5000000), number=5)
print(cost / 5)

4. javascript

javascript 雖然標準規(guī)定了數(shù)值計算用 double累颂。
但 node.js 虛擬機在一些情況下會幫你選擇用 int滞详。
例如:某變量只作為 for 循環(huán),每次 ++i紊馏。
又例如:某變量每次賦值的時候都是用 |0 這樣的操作來取整料饥。
本例中,把 Math.floor(x) 改為 x | 0 就能觀察到性能的成倍提升朱监。

注意這里的 test_double 比 C++ 寫的 double 版本更快岸啡。
原因是 C++ 在 for 循環(huán)的變量 i 也用了 double 類型,而這里的 i 被 nodejs 做成了 int 類型赫编。

另外:如果避免求余巡蘸,并不會像 C++ 避免求余那樣導致成倍的性能差異。
可能是因為虛擬機看到 v = i 然后 x = v % 10 而 i 是整數(shù)擂送,所以這里用了整數(shù)求余了悦荒。
而 Math.floor 由于是內(nèi)置函數(shù),不會再被 JIT 優(yōu)化嘹吨,仍然老老實實的調(diào)用了 C 函數(shù)搬味。

另外:把 Math.floor 緩存為局部變量,并沒有觀測到性能提升。這跟 lua 不太一樣身腻。

function test_int(N) {
    for (let i = 1; i <= N; ++i) {
        let s = 0;
        let v = i | 0;
        while (v > 0) {
            let x = v % 10;
            v = (v / 10) | 0;
            s += x * x * x;
        }

        if (s == i) {
            console.log(s);
        }
    }
}

function test_double(N) {
    for (let i = 1; i <= N; ++i) {
        let s = 0;
        let v = i;
        while (v > 0) {
            let x = v % 10;
            v = Math.floor(v / 10);
            s += x * x * x;
        }

        if (s == i) {
            console.log(s);
        }
    }
}

function test_double_without_mod(N) {
    for (let i = 1; i <= N; ++i) {
        let s = 0;
        let v = i;
        while (v > 0) {
            let v2 = Math.floor(v / 10);
            let x = v - v2 * 10;
            s += x * x * x;
            v = v2;
        }

        if (s == i) {
            console.log(s);
        }
    }
}

let t1, t2;

t1 = Date.now();
for (let i = 0; i < 5; ++i) {
    test_int(5000000);
}
t2 = Date.now();

console.log(`cost time: ${(t2 -t1) / 5 / 1000}`);

5. lua

luajit 跟 node.js 類似产还,也會嘗試使用 int 類型代替 double 類型。
但是官方的 lua 則不會嘀趟。
因此脐区,在使用和不使用求余的情況下,官方的 lua 會看出性能差異她按,而 luajit 基本沒有差異牛隅。

另外:luajit 即便關(guān)掉了 JIT,仍然會比官方 lua 快得多酌泰。這可能因為它嘗試了 int媒佣。

function test_mod(N)
    local floor = math.floor

    for i = 1, N do
        local v = i
        local s = 0
        while v > 0 do
            x = v % 10
            s = s + x * x * x
            v = floor(v / 10)
        end

        if s == i then
            print(s)
        end
    end
end

function test_without_mod(N)
    local floor = math.floor

    for i = 1, N do
        local v = i
        local s = 0
        while v > 0 do
            local v2 = floor(v / 10)
            x = v - v2 * 10
            s = s + x * x * x
            v = v2
        end

        if s == i then
            print(s)
        end
    end
end

for i = 1, 5 do
    test_mod(5000000)
end
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市陵刹,隨后出現(xiàn)的幾起案子默伍,更是在濱河造成了極大的恐慌,老刑警劉巖衰琐,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件也糊,死亡現(xiàn)場離奇詭異,居然都是意外死亡羡宙,警方通過查閱死者的電腦和手機狸剃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來狗热,“玉大人钞馁,你說我怎么就攤上這事∧涔危” “怎么了僧凰?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長熟丸。 經(jīng)常有香客問我训措,道長,這世上最難降的妖魔是什么虑啤? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮架馋,結(jié)果婚禮上狞山,老公的妹妹穿的比我還像新娘。我一直安慰自己叉寂,他們只是感情好萍启,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般勘纯。 火紅的嫁衣襯著肌膚如雪局服。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天驳遵,我揣著相機與錄音淫奔,去河邊找鬼。 笑死堤结,一個胖子當著我的面吹牛唆迁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播竞穷,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼唐责,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了瘾带?” 一聲冷哼從身側(cè)響起鼠哥,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎看政,沒想到半個月后朴恳,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡帽衙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年菜皂,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片厉萝。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡恍飘,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出谴垫,到底是詐尸還是另有隱情章母,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布翩剪,位于F島的核電站乳怎,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏前弯。R本人自食惡果不足惜蚪缀,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望恕出。 院中可真熱鬧询枚,春花似錦、人聲如沸浙巫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至渊抄,卻和暖如春尝胆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背护桦。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工含衔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人嘶炭。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓抱慌,卻偏偏與公主長得像,于是被迫代替她去往敵國和親眨猎。 傳聞我的和親對象是個殘疾皇子抑进,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容