Help us understand the problem. What is going on with this article?

2019年の後半。いま、高速で生産性の高い開発言語はなに?!旬の言語 C++, C#, Javascript, Python で比較してみた!

はじめに

みなさんこんにちは。ハーツテクノロジーの James です。この記事は業務の中で得られた知見から書かれています。

この記事の目的は、開発言語別の実行速度と、おおざっぱな行数(ステップ数)を把握するのが目的です。以下の4種類の言語で、同じアルゴリズムを実装し、その実行速度とコード行数を測ります。今回、調べたかった開発言語は以下の5つです。

  • C++ / Visual Studio 2019
  • C++ / MinGW(v8.1.0)
  • Javascript / node.js(v10.16.3)
  • C# / Visual Studio 2019
  • Python / Python(v3.7.5)

C++, C#, Javascript, Python の実行速度の計測

計測に使うアルゴリズムには「ライプニッツの公式を使って円周率を求める」を選びました。短いコードですが、それなりにCPUを酷使するので目的は達成できると考えています。

結果的に、コードは3パターン作成し、計測しました。

  • A, わたし(James)が書いた、小数点以下100桁で計算するコード
  • B, 先輩の山崎さんがリファクタリングした、小数点以下20桁で計算するコード
  • C, 同僚のOさんが書いたシンプルにdoubleのみで計算するコードをさらに山崎さんがリファクタリングしたコード

どれも、ライプニッツの公式を使って円周率を計算するコードです。

パターンA わたし(James)が書いた、小数点以下100桁で計算するコード

コードの解説

小数点以下100桁までを、1e8 回繰り返し計算します。

小数点以下100桁まで計算のためpi(π)とval(加減する値(1-1/3+1/5-1/7+1/9-⋯))を使います。小数点以下の桁数は設定できます。

  • 例)1/3(pi も val と同じ大きさ100桁のint)
int val[] [0] [1] [2] [3] [4] [98] [99] [100]
0 3 3 3 3 3 3 3

1/3と同じ感じの無限小数をvalで保存して、一度は加算(+)一度は減算(-)と繰り返します。piとvalを加減算と同時に一番後から数の上げと下りを計算します。

  • int配列使うの理由

double 型は小数点以下15桁の精度まで計算と表記ができますが、今回はもっと多い桁数を計算して表記したかったので int 配列を作成しました。

コード

A-1. C++(計算時間: VS2019-278.973秒, MinGW-165.098秒)

Leibniz_J.cpp
#include <iostream>
#include <time.h>
#include <cstring>
using namespace std;

#define MAX 101 //小数点以下
int pi[MAX] = { 0 };
int val[MAX] = { 0 };

void val_def(int* q) //calculate val, ‘4/q’を小数点MAXまで計算
{
    int start = 4;
    for (int i = 0; i < MAX; i++) 
    {
        val[i] = start / *q;
        start = (start % *q) * 10;
    }
}
void cal() //calculate pi
{
    int p = -1, q = 1, num = 1;
    while (num <= 1e8) //pi = 4 * (1/1 - 1/3 + 1/5 -  ~ 1/(1e8*2-1))
    {
        val_def(&q);
        p *= -1;
        for (int i = MAX - 1; i >= 0; i--)
        {
            if (p == -1 && pi[i] < val[i])//pi[i]-val[i]時val[i]がpiより高い
            {
                pi[i - 1]--; //piの高桁からpi[i]+10
                pi[i] += 10;
                pi[i] += p * val[i];
            }
            else
                pi[i] += p * val[i];

            if (pi[i] >= 10)//(pi[i] >= 10)時に高桁に数を増加
            {
                pi[i - 1] += pi[i] / 10;
                pi[i] %= 10;
            }
        }
        q += 2;
        num++;
        memset(val, 0, MAX);
    }
}
void print_pi()
{
    printf("%d.", pi[0]);
    for (int i = 1; i < MAX; i++)
    {
        printf("%d", pi[i]);
        if (i % 10 == 0)
            printf("  ");
        if (i % 50 == 0)
            printf("\n  ");
    }
}

int main(void)
{
    double result = 0;

    printf("start\n");
    clock_t begin, end;
    begin = clock();
    cal();
    end = clock();
    print_pi(); //use if you want to see 0 ~ MAX PI value

    result += (double)(end - begin);
    printf("\n");
    printf(" %f sec(not include print time.)", result / CLOCKS_PER_SEC);
}

c++ 1e8.PNG

A-2. C#(計算時間: 458.992秒)

Leibniz_J.cs
using System;

namespace leibniz
{
    class func
    {
        static int MAX = 101; //小数点以下 100まで
        int[] pi = new int[MAX];
        int[] val = new int[MAX];
        public void val_def(int q)
        {
            int start = 4;
            for (int i = 0; i < MAX; i++)
            {
                val[i] = start / q;
                start = (start % q) * 10;
            }
        }
        public void cal()
        {
            int p = -1, q = 1, num = 1;
            while (num <= 1e8) //pi = 4 * (1/1 - 1/3 + 1/5 -  ~ 1/(1e8*2-1))
            {
                val_def(q);
                p *= -1;
                for (int i = MAX - 1; i >= 0; i--)
                {
                    if (p == -1 && pi[i] < val[i])
                    {
                        pi[i - 1]--;
                        pi[i] += 10;
                        pi[i] += p * val[i];
                    }
                    else
                        pi[i] += p * val[i];

                    if (pi[i] >= 10)
                    {
                        pi[i - 1] += pi[i] / 10;
                        pi[i] %= 10;
                    }
                }
                q += 2;
                num++;
                Array.Clear(val, 0, MAX);
            }
        }

        public void print()
        {
            Console.Write(pi[0] + ".");
            for (int i = 1; i < MAX; i++)
            {
                Console.Write(pi[i]);
                if (i % 10 == 0)
                    Console.Write("  ");
                if (i % 50 == 0)
                    Console.Write("\n  ");
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            double result = 0;
            DateTime begin, end;

            begin = DateTime.Now;
            func _func = new func();
            _func.cal();
            end = DateTime.Now;
            result += end.Subtract(begin).TotalSeconds;
            _func.print(); //use if you want to see 0 ~ MAX PI value
            Console.WriteLine("\n " + end.Subtract(begin).TotalSeconds + " sec");
        }
    }
}

c#1e8.PNG

A-3. JavaScript(計算時間: 456.066秒)

Leibniz_J.js
let MAX = 101; ////小数点以下 100まで
let pi = Array.apply(null,new Array(MAX)).map(Number.prototype.valueOf,0);
let val = Array.apply(null,new Array(MAX)).map(Number.prototype.valueOf,0);
let begin = new Date().getTime();
let result = 0;

function val_def(q){
    let start = 4;
    for (let i = 0; i < MAX; i++){
        val[i] = Math.floor(start / q);
        start = (start % q) * 10;
    }
}

function cal(){
    let p = -1, q = 1, num = 1;
    while (num <= 1e8) { //pi = 4 * (1/1 - 1/3 + 1/5 -  ~ 1/(1e8*2-1))
        val_def(q);
        p *= -1;
        for (let i = MAX - 1; i >= 0; i--){
            if (p == -1 && pi[i] < val[i]) {
                pi[i - 1]--;
                pi[i] += 10;
                pi[i] += p * val[i];
            }
            else
                pi[i] += p * val[i];

            if (pi[i] >= 10) {
                pi[i - 1] += parseInt(pi[i] / 10);
                pi[i] %= 10;
            }
        }  
        q += 2;
        num++;
        val = [];
    }
}

function print(){
    process.stdout.write(`${pi[0]}.`);
    for (let i = 1; i < MAX; i++){
        process.stdout.write(`${pi[i]}`);
        if (i % 10 == 0)
            process.stdout.write("   ");
        if (i % 50 == 0)
            process.stdout.write("\n  ");
    }
}

cal();
let end = new Date().getTime();
print(); //use if you want to see 0 ~ MAX PI value
result = (end-begin)/1000;

console.log(`\n${(end-begin)/1000} sec`);

js 1e8.PNG

A-4. Python(計算時間: 1148.174秒)

Leibniz_J.py
import time

MAX = 11 #遅いため小数点以下 10まで
pi = [0] * MAX
val = [0] * MAX

def cal():
    global pi
    global val
    p = -1
    q = 1
    num = 1
    while num <= 1e8:    #pi = 4 * (1/1 - 1/3 + 1/5 -  ~ 1/(1e8*2-1))
        val_def(q)
        val = list(reversed(val))
        p *= -1
        for index,i in enumerate(pi):
            if p == -1 and pi[index] < val[index]:
                pi[index + 1] -= 1
                pi[index] += 10
                pi[index] += p * val[index]
            else:
                pi[index] += p * val[index]

            if i >= 10:
                pi[index + 1] += pi[index] // 10
                pi[index] %= 10
        q += 2
        num += 1
        val = [0] * MAX
    pi = list(reversed(pi));

def val_def(q):
    global val
    start = 4
    for index,i in enumerate(val):
        val[index] = start // q
        start = (start % q)*10

def print_pi():
    global pi
    print("%d." % pi[0],end="")
    for index,i in enumerate(pi):
        if index == 0:
            continue
        print(i,end="");
        if (index % 10) == 0:
            print(" ",end="")
        if (index % 50) == 0:
            print("  ")
    print("")

if __name__ == '__main__':
    print("start")
    begin = time.time()
    cal()
    end = time.time()
    print_pi()
    print(end - begin," sec")

python 1e8.PNG

パターンB, 先輩の「山崎さん」がリファクタリングした、小数点以下20桁で計算するコード

コードの解説

小数点以下20桁までを、分母が 1e9 になるまで繰り返し計算します。

コード

B-1. C++(計算時間: VS2019-216.595秒, MinGW-134.391秒)

Leibniz_Y.cpp
#include <stdio.h>  // printf()
#include <time.h>   // clock()

#define MAX 21 //小数点以下 20桁
int pi[MAX] = { 0 };

void pi_add( long long  _q, int _a=1 )
{
    long long start = 4;        // _q は 1e9 まで来る想定なので long 32bit では足りない
    for ( int i=0; i<MAX; i++ )
    {
        pi[i] += (start / _q) * _a;
        start = (start % _q) * 10;
    }
}

int main()
{
    printf( "start\n" );
    auto begin = clock();    // 実行時間を計測

    for ( long long q=1; q<=1e9 ; ) // pi = 4/1 - 4/3 + 4/5 - 4/7 + ... 4/1e9
    {
        pi_add( q ); // 加算
        q += 2;

        pi_add( q, -1 );  // 減算
        q += 2;
    }

    // 計算のあと、各桁を 0 - 9 の範囲に調整する
    for ( int i=MAX-1; i>0; i-- )   // 0以下の値は、上の桁から拝借してくる
    {
        for ( ; pi[i] < 0 ; ) {
            pi[i-1] -= 1;
            pi[i]   += 10;
        }
    }
    for ( int i=MAX-1; i>0; i-- )   // 10以上の値は、上の桁に送る
    {
        if ( pi[i] >= 10 ) {
            pi[i-1] += pi[i] / 10 ;
            pi[i]   %= 10 ;
        }
    }

    // 表示
    printf( "%d.", pi[0]);
    for ( int i = 1; i < MAX; i++)
    {
        printf( "%d", pi[i] );
        if ( i % 10 == 0 ) printf( "  " );
    }
    printf( "\n" );

    printf( " %f sec.", (double)(clock() - begin) / CLOCKS_PER_SEC );    // 実行時間を表示

    return 0 ;
}

1029 cpp o2.PNG

B-2. C#(計算時間: 287.459秒)

Leibniz_Y.cs
using System;

namespace leibniz
{
   class Program
   {
       static int MAX = 21;
       static int[] pi = new int[MAX];

       static void pi_add(long _q, int _a = 1)
       {
           long start = 4;
           for (int i = 0; i < MAX; i++)
           {
               pi[i] += (int)(start / _q) * _a;
               start = (start % _q) * 10;
           }
       }

       static void Main(string[] args)
       {
           Console.WriteLine("start");
           DateTime begin = DateTime.Now;

           for (long q = 1; q <= 1e9;)// pi = 4/1 - 4/3 + 4/5 - 4/7 + ... 4/1e9
           {
               pi_add(q);
               q += 2;

               pi_add(q, -1);
               q += 2;
           }

           for (int i = MAX - 1; i > 0; i--)   // 0以下の値は、上の桁から拝借してくる
           {
               for (; pi[i] < 0;)
               {
                   pi[i - 1] -= 1;
                   pi[i] += 10;
               }
           }
           for (int i = MAX - 1; i > 0; i--)   // 10以上の値は、上の桁に送る
           {
               if (pi[i] >= 10)
               {
                   pi[i - 1] += pi[i] / 10;
                   pi[i] %= 10;
               }
           }

           Console.Write(pi[0] + ".");
           for (int i = 1; i < MAX; i++)
           {
               Console.Write(pi[i]);
               if (i % 10 == 0)
                   Console.Write("  ");
               if (i % 50 == 0)
                   Console.Write("\n  ");
           }
           Console.WriteLine("\n{0} sec.", DateTime.Now.Subtract(begin).TotalMilliseconds / 1000); //時間表示
       }
   }
}

1028 c# 1e9.PNG

B-3. JavaScript(計算時間: 123.56秒)

Leibniz_Y.js
console.log( 'start' )
const begin = Date.now()    // 実行時間を計測

const MAX = 21 // 小数点以下 20桁
var pi = Array( MAX ).fill( 0 )

function pi_add( _q, _a=1 ) // 加算
{
    let start = 4
    for ( let i=0; i<MAX; i++ )
    {
        pi[i] += Math.floor( start / _q ) * _a
        start = ( start % _q ) * 10
    }
}

for ( let q=1; q<=1e9 ; ) // pi = 4/1 - 4/3 + 4/5 - 4/7 + ... 4/1e9
{
    pi_add( q ) // 加算
    q += 2

    pi_add( q, -1 )  // 減算
    q += 2
}

// 計算のあと、各桁を 0 - 9 の範囲に調整する
for ( let i=MAX-1; i>0; i-- )   // 0以下の値は、上の桁から拝借してくる
{
    for ( ; pi[i] < 0 ; )
    {
        pi[i-1] -= 1
        pi[i]   += 10
    }
}
for ( let i=MAX-1; i>0; i-- )   // 10以上の値は、上の桁に送る
{
    if ( pi[i] >= 10 ) {
        pi[i-1] += parseInt( pi[i] / 10 )
        pi[i]   %= 10
    }
}

// 表示
var r = `${pi[0]}.`
for ( let i=1; i<MAX; i++ )
{
    r += `${pi[i]}` + ( (i % 10 == 0) ? '  ' : '' )
}
console.log( r )

console.log( `${ ( Date.now() - begin ) / 1000 } sec.` )    // 実行時間を表示

1028 js 1e9.PNG

B-4. Python(計算時間: 3722.021秒)

Leibniz_Y.py
import time

print( "start" )
begin = time.time() # 実行時間を計測

MAX = 21 # 小数点以下 20桁
pi  = [0] * MAX

def pi_add( q, a=1 ):
    start = 4
    for i in range( 0, MAX ) :
        pi[i] += start // q * a
        start = (start % q) * 10

q = 1
while q <= 1e9:     # pi = 4/1 - 4/3 + 4/5 - 4/7 + ... 4/1e9
    pi_add( q )     # 加算
    q += 2
    pi_add( q, -1 ) # 減算
    q += 2

# 計算のあと、各桁を 0 - 9 の範囲に調整する
for i in range( MAX-1, 0, -1 ) :   # 0以下の値は、上の桁から拝借してくる
    while pi[i] < 0 :
        pi[i-1] -= 1
        pi[i]   += 10

for i in range( MAX-1, 0, -1 ) :  # 10以上の値は、上の桁に送る
    if pi[i] >= 10 :
        pi[i-1] += pi[i] // 10
        pi[i]   %= 10

# 表示
print( "%d." % pi[0], end="" )
for i in range( 1, MAX ) :
    print( pi[i], end="" )
    if ( i % 10 ) == 0 :
        print( "  ", end="" )
print( "" )

print( time.time() - begin, " sec." )    # 実行時間を表示

1025 py 1e9.PNG

パターンC, 同僚の「Oさん」が書いたシンプルにdoubleのみで計算するコードをさらに山崎さんがリファクタリングしたコード

3回(小数点以下double(15)まで、1e9まで計算)

コード

C-1. C++(計算時間: VS2019-2.6秒, MinGW-0.608秒)

Leibniz_O.cpp
#include <stdio.h>  // printf()
#include <time.h>   // clock()

int main()
{
    printf( "start\n" );
    auto begin = clock();    // 実行時間を計測

    double l = 0.0;

    for ( double n=1; n<1e9; ) // l = 1/1 - 1/3 + 1/5 - 1/7 + ... 1/1e9
    {
        l += 1.0 / n ;
        n += 2 ;
        l -= 1.0 / n ;
        n += 2 ;
    }

    printf( "%1.16f\n", l * 4 );

    printf( " %f sec.", (double)(clock() - begin) / CLOCKS_PER_SEC );    // 実行時間を表示

    return 0;
}

1029 cpp o2 short.PNG

C-2. C#(計算時間: 1.847秒)

Leibniz_O.cs
using System;

namespace Leibniz
{
    class Program
    {
        public static void Main()
        {
            System.Console.WriteLine("start");
            System.DateTime begin = System.DateTime.Now; // 実行時間を計測
            double l = 0.0;

            for (double n = 1; n < 1e9;) // l = 1/1 - 1/3 + 1/5 - 1/7 + ... 1/1e9
            {
                l += 1.0 / n;
                n += 2;
                l -= 1.0 / n;
                n += 2;
            }

            System.Console.WriteLine(l * 4);
            System.Console.WriteLine("{0} sec", System.DateTime.Now.Subtract(begin).TotalMilliseconds / 1000);
        }
    }
}

1028 cs short code.PNG

C-3. JavaScript(計算時間: 0.756秒)

Leibniz_O.js
console.log( 'start' )
const begin = Date.now()    // 実行時間を計測

var l = 0.0
for ( let n=1; n<1e9; )     // l = 1/1 - 1/3 + 1/5 - 1/7 + ... 1/1e9
{
    l += 1.0 / n
    n += 2
    l -= 1.0 / n
    n += 2
}

console.log( l * 4 )

console.log( `${ ( Date.now() - begin ) / 1000 } sec.` )    // 実行時間を表示

1029 js short.PNG

C-4. Python(計算時間: 121.914秒)

Leibniz_O.py
import time

print( "start" )
begin = time.time() # 実行時間を計測

l = 0.0
n = 1
while n<1e9 :       # l = 1/1 - 1/3 + 1/5 - 1/7 + ... 1/1e9
    l += 1.0 / n
    n += 2
    l -= 1.0 / n
    n += 2

print( l * 4 )

print( time.time() - begin, " sec." )    # 実行時間を表示

1028 py short code.PNG

結果

実行環境

  • CPU : Intel Core i7-7500U 2.70GHZ
  • RAM : 8 GB
  • SSD : 250 GB
  • OS : Windows 10 Pro

実行速度(秒)

code VS2019/C++ MinGW/C++ node.js/Javascript VS2019/C# Python
A 278.973 165.098 456.066 458.992 1148.174
B 216.595 134.391 123.56 287.459 3722.021
C 2.6 0.608 0.756 1.847 121.914

graph1.PNG

やはり C++ がよい成績を出しました。でも、2番目に Javascript が速いです。これは予想外。。。Python は3位のC#より3倍以上、最大で約100倍遅いです。

graph2.PNG

追加で、MinGW と Visual Studio の速度の違いも意味があると思います。どちらも「O2」最適化オプションで計測しました。

コード量(行数)

コード量はコメント業も空白行も含む純粋な行数です。好みの書き方に左右されるので、あくまでも参考値です。

code C++ Javascript C# Python
A 75 56 78 59
B 59 51 63 41
C 24 15 25 16

graph-length.PNG

コード量は C++, C# が長めで Python, Javascript 短めに思います。

3つとも Javascript と Python は長さ面で似ていましたが、速度面では反対の感じに結果がでました。

総評

この計測の前に g++(C++ compiler) で「-O2」オプションを使わずに計測しました。その計測のときにはJavascriptが速度面で1位でした。

でも最適化オプション「-O2」を使ったあとで結果が変わりました。

しかし速度だけでなくコード量を一緒に見れば Javascript が合理的な言語だと思いました。

追加テスト

ABC、3つの中で1つ(B,山崎さんのコード)だけ Javascriptが速い結果が出ました。それもなぜJavascriptが速いかが解決してないので簡単なテストをしました。
ループの速度が違うのでループを1回は1e5反復,2回は1e6反復して5回1e9までテストしました。(オプションで -O2使う)

ループ速度

コード

  • C++
test.cpp
#include <iostream>
#include <time.h>

int main(void)
{
    int i = 0;
    double val = 1e5; //ループ 回数
    while (i < 5)
    {
        double a = 0;//これのtypeを変える
        int j = 0;
        double result = 0.0;
        while(j < 5)
        {
            a = 0;
            auto begin = clock();
            for (int q = 1; q <= val; q++)//これのtypeを変える
            {
                a++;
            }
            result += clock() - begin;
            j++;
        }
        printf("long long q ~ 10^%d, double a = %10.0f: %.3f sec \n", i + 5, a, (result / 5) / CLOCKS_PER_SEC);
        i++;
        val *= 10; //ループ 回数増加(i=0:1e5, i=1:1e6, i=2:1e7, i=3:1e8, i=4:1e9)
    }
    return 0;
}
MinGW(double a) MinGW(int a)
double a-MinGW.PNG int a-MinGW.PNG
VS2019(double a) VS2019(int a)
double a.PNG int a.PNG
  • Javascript
test.js
let i = 0
let val = 1e5 //ループ 回数
while (i < 5)
{
    let a = 0
    let j = 0
    let result = 0.0
    while (j < 5)
    {
        a = 0;
        let begin = Date.now()
        for (let q = 1; q <= val; q++)
        {
            a++
        }
        result += Date.now() - begin
        j++
    }

    console.log(`10^${i+5}: ${((result/5) / 1000).toFixed(3)} sec, a = ${a}`)
    i++
    val *= 10 //ループ 回数増加(i=0:1e5, i=1:1e6, i=2:1e7, i=3:1e8, i=4:1e9)
}

javascript.PNG

結果

10^9反復する場合だけ結果を整理します。(単位: 秒)

int q double q long long q
MinGW - int a 0.791 1.182 0.622
MinGW - double a 1.166 1.192 1.188
VS2019 - int a 4.188 1.374 4.332
VS2019 - double a 4.855 1.365 5.582
Javascript - type関係ない 1.019 - -

10^9.PNG

総合的にVS2019はループカウント(q)のデータタイプがdouble時には一番速かったけどそれも10^7からはJavascriptより遅くなりました。
でもMinGWはdoubleを使う時だけ(aかqかは関係ない)10^7からJavascriptより遅くなりましたが、doubleを使用しない時(aとq全体)にはJavascriptより全体の場合で速くなりました。
VS2019がループカウント(q)をdoubleで使う時だけMinGWと同じ実行時間が出ましたが、intとlong longを使う時にはVS2019がMinGWより3~4倍ぐらい遅くなりました。

まとめ

まだ結果について明快な解答がありません。でも個人的にJavascriptが結構いい言語ということを知りました。

このあと Python がどこまで発展するかまだわからないが、今は速度が必要で、資金など資源が不足ぎみなら C++ より Javascript を使うのもいいと思いました。

みなさまのご意見もお聞かせください。よろしくお願いいたします。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away