LoginSignup
5
6

C++ はどれくらい速いのか?:フィボナッチ数列の演算で比較してみる

Last updated at Posted at 2023-08-22

C++ はどれくらい速いのか?:フィボナッチ数列の演算で比較してみる

こんにちは、@studio_meowtoon です。今回は、WSL の Ubuntu 環境で C++ プログラムを実行する方法などを紹介します。
cpp_on_ubuntu.png

目的

Windows 11 の Linux でクラウド開発します。

こちらから記事の一覧がご覧いただけます。

実現すること

Ubuntu 22.04 上で、C++ を含む複数のプログラミング言語を使用して、フィボナッチ数列を求める関数の実行速度を比較します。

フィボナッチ数列の計算時間では、言語の性能を評価するのは難しいことをあらかじめご承知ください。また各言語での実装内容は、シンプルさを優先していることと、コンパイラや実行環境で異なる結果になることを予めご理解ください。

比較する言語

No 言語/環境 実行形式 コンパイラ/実行環境
1 C++ ネイティブ g++ 11.3.0
2 C ネイティブ gcc 11.4.0
3 Java JITコンパイル openjdk version "17.0.7"
4 Java ネイティブ GraalVM 22.3.1 Java 17 CE
5 C#.NET JITコンパイル dotnet 7.0.202
6 Python インタプリタ python 3.10.6
7 Node.js インタプリタ node v19.8.1
8 Go ネイティブ go1.18.1 linux/amd64
9 Rust ネイティブ rustc 1.71.1
おまけ シェルスクリプト インタプリタ GNU bash version 5.1.16(1)-release

フィボナッチ数を求める仕様

以下のような再帰的処理を各言語で実装します。

C++ 版の関数
// フィボナッチ数列を再帰的な方法で計算する関数
int fibonacci(int n) {
    if (n == 0 || n == 1) {
        return n;
    } else {
        return fibonacci(n - 2) + fibonacci(n - 1);
    }
}

この記事では、フィボナッチ数列を再帰的な方法で計算する関数の性能には触れません。ご了承ください。

演算時間を取得する手順

プロジェクトの作成

プロジェクトフォルダを作成します。
※ ~/tmp/fib-comp をプロジェクトフォルダとします。

$ mkdir -p ~/tmp/fib-comp
$ cd ~/tmp/fib-comp

C++ の処理時間を測定

環境構築手順を開きます。

g++ をインストールします。

$ sudo apt update
$ sudo apt install g++

確認します。

$  g++ --version
g++ (Ubuntu 11.3.0-1ubuntu1~22.04.1) 11.3.0

cpp ファイルを作成します。

$ vim fibonacci.cpp

ファイルの内容

fibonacci.cpp ※抜粋
// フィボナッチ数列を再帰的な方法で計算する関数
int fibonacci(int n) {
    if (n == 0 || n == 1) {
        return n;
    } else {
        return fibonacci(n - 2) + fibonacci(n - 1);
    }
}
コード全体を表示します。
fibonacci.cpp
#include <chrono>
#include <iomanip>
#include <iostream>

using namespace std;
using namespace std::chrono;

const int TERM = 44;  // フィボナッチ数列の項数を定義

// フィボナッチ数列を再帰的な方法で計算する関数
int fibonacci(int n) {
    if (n == 0 || n == 1) {
        return n;
    } else {
        return fibonacci(n - 2) + fibonacci(n - 1);
    }
}

int main() {
    // 関数の実行時間を測定
    auto start_time = high_resolution_clock::now();
    int result = fibonacci(TERM);
    auto end_time = high_resolution_clock::now();

    // 実行時間を秒に変換して表示
    auto duration_microseconds = duration_cast<microseconds>(end_time - start_time).count();
    double duration_seconds = static_cast<double>(duration_microseconds) / 1e6;
    cout << "Result: " << result << endl;
    cout << "Time: " << fixed << setprecision(5) << setfill('0') << duration_seconds << " seconds" << endl;

    return 0;
}
ビルドして実行する手順を開きます。

ビルドします。

$ g++ -o fibonacci_cpp fibonacci.cpp -O2

実行します。

$ ./fibonacci_cpp

出力結果

Result: 701408733
Time: 0.92987 seconds
実行結果の詳細をみます。
回数 時間(秒) 備考
1 0.92823
2 0.93151
3 0.94174 MAX
4 0.92752 MIN

4回の実行結果から最大値と最小値を取り除いて、残りの2回の結果の平均値を求めています。※小数点5桁以下は切り捨て

C の処理時間を測定

環境構築手順を開きます。

gcc をインストールします。

$ sudo apt update
$ sudo apt install gcc

確認します。

$ gcc --version
gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0

c ファイルを作成します。

$ vim fibonacci.c

ファイルの内容

fibonacci.c ※抜粋
// フィボナッチ数列を再帰的な方法で計算する関数
int fibonacci(int n) {
    if (n == 0 || n == 1) {
        return n;
    } else {
        return fibonacci(n - 2) + fibonacci(n - 1);
    }
}
コード全体を表示します。
fibonacci.c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

const int TERM = 44;  // フィボナッチ数列の項数を定義

// フィボナッチ数列を再帰的な方法で計算する関数
int fibonacci(int n) {
    if (n == 0 || n == 1) {
        return n;
    } else {
        return fibonacci(n - 2) + fibonacci(n - 1);
    }
}

int main() {
    // 関数の実行時間を測定
    clock_t start_time = clock();
    int result = fibonacci(TERM);
    clock_t end_time = clock();

    // 実行時間を秒に変換して表示
    double duration_seconds = (double)(end_time - start_time) / 1e6;
    printf("Result: %d\n", result);
    printf("Time: %.5lf seconds\n", duration_seconds);

    return 0;
}
ビルドして実行する手順を開きます。

ビルドします。

$ gcc -o fibonacci_c fibonacci.c -O2

実行します。

$ ./fibonacci_c

出力結果

Result: 701408733
Time: 0.91076 seconds
実行結果の詳細をみます。
回数 時間(秒) 備考
1 0.91894 MAX
2 0.90905 MIN
3 0.91053
4 0.91099

4回の実行結果から最大値と最小値を取り除いて、残りの2回の結果の平均値を求めています。

Java の処理時間を測定

環境構築手順を開きます。

jdk をインストールします。

$ sudo apt update
$ sudo apt install openjdk-17-jdk

確認します。

$ java -version
openjdk version "17.0.7" 2023-04-18
OpenJDK Runtime Environment (build 17.0.7+7-Ubuntu-0ubuntu122.04.2)
OpenJDK 64-Bit Server VM (build 17.0.7+7-Ubuntu-0ubuntu122.04.2, mixed mode, sharing)

java ファイルを作成します。

$ vim Fibonacci.java

ファイルの内容

Fibonacci.java ※抜粋
// フィボナッチ数列を再帰的な方法で計算する関数
static int fibonacci(int n) {
    if (n == 0 || n == 1) {
        return n;
    } else {
        return fibonacci(n - 2) + fibonacci(n - 1);
    }
}
コード全体を表示します。
Fibonacci.java
import static java.lang.System.*;

public class Fibonacci {
    static final int TERM = 44;  // フィボナッチ数列の項数を定義

    // フィボナッチ数列を再帰的な方法で計算する関数
    static int fibonacci(int n) {
        if (n == 0 || n == 1) {
            return n;
        } else {
            return fibonacci(n - 2) + fibonacci(n - 1);
        }
    }

    public static void main(String[] args) {
        // 再帰的な方法での計算時間を測定
        long start_time = nanoTime();
        int result = fibonacci(TERM);
        long end_time = nanoTime();

        // 実行時間を秒に変換して表示
        double duration_seconds = (end_time - start_time) / 1e9;
        out.println("Result: " + result);
        out.printf("Time: %.5f seconds%n", duration_seconds);
    }
}
ビルドして実行する手順を開きます。

ビルドします。

$ javac Fibonacci.java

実行します。

$ java Fibonacci

出力結果

Result: 701408733
Time: 2.70795 seconds
実行結果の詳細をみます。
回数 時間(秒) 備考
1 2.72754 MAX
2 2.71025
3 2.70565
4 2.69725 MIN

4回の実行結果から最大値と最小値を取り除いて、残りの2回の結果の平均値を求めています。※小数点5桁以下は切り捨て

Java (GraalVM ネイティブイメージ) の処理時間を測定

環境構築については以下の記事でご確認いただけます。

ビルドして実行する手順を開きます。

ネイティブイメージビルドします。

$ native-image -o Fibonacci_java Fibonacci -O2

実行します。

$ ./Fibonacci_java

出力結果

Result: 701408733
Time: 3.28288 seconds
実行結果の詳細をみます。
回数 時間(秒) 備考
1 3.29400
2 3.35933 MAX
3 3.27176
4 3.25840 MIN

4回の実行結果から最大値と最小値を取り除いて、残りの2回の結果の平均値を求めています。※小数点5桁以下は切り捨て

C#.NET の処理時間を測定

環境構築手順を開きます。

dotnet-sdk をインストールします。

$ sudo apt update
$ sudo apt install dotnet-sdk-7.0

確認します。

$ dotnet --list-sdks
7.0.202 [/usr/share/dotnet/sdk]
$ dotnet --version
7.0.202

cs ファイルを作成します。

$ vim Fibonacci.cs

ファイルの内容

Fibonacci.cs ※抜粋
// フィボナッチ数列を再帰的な方法で計算する関数
static int fibonacci(int n) {
    if (n == 0 || n == 1) {
        return n;
    } else {
        return fibonacci(n - 2) + fibonacci(n - 1);
    }
}
コード全体を表示します。
Fibonacci.cs
using static System.Console;
using static System.DateTime;

public class Program {
    const int TERM = 44;  // フィボナッチ数列の項数を定義

    // フィボナッチ数列を再帰的な方法で計算する関数
    static int fibonacci(int n) {
        if (n == 0 || n == 1) {
            return n;
        } else {
            return fibonacci(n - 2) + fibonacci(n - 1);
        }
    }

    public static void Main(string[] args) {
        // 再帰的な方法での計算時間を測定
        long start_time = Now.Ticks;
        int result = fibonacci(TERM);
        long end_time = Now.Ticks;

        // 実行時間を秒に変換して表示
        double duration_seconds = (end_time - start_time) / 1e7;
        WriteLine($"Result: {result}");
        WriteLine($"Time: {duration_seconds:f5} seconds");
    }
}
ビルドして実行する手順を開きます。

Fibonacci.csproj ファイルを作成します。

$ vim Fibonacci.csproj

ファイルの内容

Fibonacci.csproj
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0</TargetFramework>
  </PropertyGroup>
</Project>

ビルドします。

$ dotnet build Fibonacci.csproj -c Release -o out

実行します。

$ dotnet out/Fibonacci.dll

出力結果

Result: 701408733
Time: 2.92658 seconds
実行結果の詳細をみます。
回数 時間(秒) 備考
1 2.94822 MAX
2 2.91312 MIN
3 2.92032
4 2.93285

4回の実行結果から最大値と最小値を取り除いて、残りの2回の結果の平均値を求めています。※小数点5桁以下は切り捨て

Python の処理時間を測定

環境構築手順を開きます。

通常 Ubuntu 22.04 には、Python はプリインストールされています。

確認します。

$ python3 --version
Python 3.10.6

py ファイルを作成します。

$ vim fibonacci.py

ファイルの内容

fibonacci.py ※抜粋
# フィボナッチ数列を再帰的な方法で計算する関数
def fibonacci(n):
    if n == 0 or n == 1:
        return n
    else:
        return fibonacci(n - 2) + fibonacci(n - 1)
コード全体を表示します。
fibonacci.py
import time

TERM = 44  # フィボナッチ数列の項数を定義

# フィボナッチ数列を再帰的な方法で計算する関数
def fibonacci(n):
    if n == 0 or n == 1:
        return n
    else:
        return fibonacci(n - 2) + fibonacci(n - 1)

def main():
    # 関数の実行時間を測定
    start_time = time.time()
    result = fibonacci(TERM)
    end_time = time.time()

    # 実行時間を秒に変換して表示
    duration_seconds = end_time - start_time
    print("Result:", result)
    print("Time: {:.5f} seconds".format(duration_seconds))

if __name__ == "__main__":
    main()
実行する手順を開きます。

実行します。

$ python3 fibonacci.py

出力結果

Result: 701408733
Time: 162.69341 seconds
実行結果の詳細をみます。
回数 時間(秒) 備考
1 166.18202
2 162.69341
3 176.12160 MAX
4 160.30293 MIN

4回の実行結果から最大値と最小値を取り除いて、残りの2回の結果の平均値を求めています。※小数点5桁以下は切り捨て

Node.js の処理時間を測定

環境構築手順を開きます。

Node.js の公式パッケージリポジトリを追加します。

$ curl -fsSL https://deb.nodesource.com/setup_19.x | sudo -E bash -

Node.js をインストールします。

$ sudo apt update
$ sudo apt install nodejs

確認します。

$ node -v
v19.8.1
$ npm -v
9.5.1

js ファイルを作成します。

$ vim fibonacci.js

ファイルの内容

fibonacci.js ※抜粋
// フィボナッチ数列を再帰的な方法で計算する関数
function fibonacci(n) {
    if (n === 0 || n === 1) {
        return n;
    } else {
        return fibonacci(n - 2) + fibonacci(n - 1);
    }
}
コード全体を表示します。
fibonacci.js
const TERM = 44;  // フィボナッチ数列の項数を定義

// フィボナッチ数列を再帰的な方法で計算する関数
function fibonacci(n) {
    if (n === 0 || n === 1) {
        return n;
    } else {
        return fibonacci(n - 2) + fibonacci(n - 1);
    }
}

function main() {
    // 関数の実行時間を測定
    const start_time = process.hrtime.bigint();
    const result = fibonacci(TERM);
    const end_time = process.hrtime.bigint();

    // 実行時間をミリ秒に変換して表示
    const duration_ms = Number(end_time - start_time) / 1e9;
    console.log("Result:", result);
    console.log("Time:", duration_ms.toFixed(5), "seconds");
}

main();
実行する手順を開きます。

実行します。

$ node fibonacci.js

出力結果

Result: 701408733
Time: 6.31335 seconds
実行結果の詳細をみます。
回数 時間(秒) 備考
1 6.31413
2 6.32342 MAX
3 6.28792 MIN
4 6.31257

4回の実行結果から最大値と最小値を取り除いて、残りの2回の結果の平均値を求めています。※小数点5桁以下は切り捨て

Go の処理時間を測定

環境構築手順を開きます。

Go をインストールします。

$ sudo apt update
$ sudo apt install golang

確認します。

$ go version
go version go1.18.1 linux/amd64

go ファイルを作成します。

$ fibonacci.go

ファイルの内容

fibonacci.go ※抜粋
// フィボナッチ数列を再帰的な方法で計算する関数
func fibonacci(n int) int {
	if n == 0 || n == 1 {
		return n
	} else {
		return fibonacci(n-2) + fibonacci(n-1)
	}
}
コード全体を表示します。
fibonacci.go
package main

import (
	"fmt"
	"time"
)

const TERM = 44 // フィボナッチ数列の項数を定義

// フィボナッチ数列を再帰的な方法で計算する関数
func fibonacci(n int) int {
	if n == 0 || n == 1 {
		return n
	} else {
		return fibonacci(n-2) + fibonacci(n-1)
	}
}

func main() {
	// 関数の実行時間を測定
	start_time := time.Now()
	result := fibonacci(TERM)
	end_time := time.Now()

	// 実行時間を秒に変換して表示
	duration := end_time.Sub(start_time).Seconds()
	fmt.Println("Result:", result)
	fmt.Println("Time:", fmt.Sprintf("%.5f", duration), "seconds")
}
ビルドして実行する手順を開きます。

ビルドします。

$ go build -o fibonacci_go fibonacci.go

実行します。

$ ./fibonacci_go

出力結果

Result: 701408733
Time: 2.76093 seconds
実行結果の詳細をみます。
回数 時間(秒) 備考
1 2.75142
2 2.79774 MAX
3 2.75108 MIN
4 2.77044

4回の実行結果から最大値と最小値を取り除いて、残りの2回の結果の平均値を求めています。※小数点5桁以下は切り捨て

Rust の処理時間を測定

環境構築手順を開きます。

Rust をインストールします。

$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
1) Proceed with installation (default)
2) Customize installation
3) Cancel installation
> 1

Rust の環境設定を .bashrc ファイルに追記します。

$ vim ~/.bashrc

一番下へ次の内容を追加します。

# Rust Environment
source "$HOME/.cargo/env"

設定を反映します。

$ source ~/.bashrc

確認します。

$ rustc --version
rustc 1.71.1 (eb26296b5 2023-08-03)

rs ファイルを作成します。

$ vim fibonacci.rs

ファイルの内容

fibonacci.rs ※抜粋
// フィボナッチ数列を再帰的な方法で計算する関数
fn fibonacci(n: i32) -> i32 {
    if n == 0 || n == 1 {
        return n;
    } else {
        return fibonacci(n - 2) + fibonacci(n - 1);
    }
}
コード全体を表示します。
fibonacci.rs
use std::time::Instant;

const TERM: i32 = 44;  // フィボナッチ数列の項数を定義

// フィボナッチ数列を再帰的な方法で計算する関数
fn fibonacci(n: i32) -> i32 {
    if n == 0 || n == 1 {
        return n;
    } else {
        return fibonacci(n - 2) + fibonacci(n - 1);
    }
}

fn main() {
    // 関数の実行時間を測定
    let start_time = Instant::now();
    let result = fibonacci(TERM);
    let end_time = Instant::now();

    // 実行時間を秒に変換して表示
    let duration = end_time - start_time;
    let duration_seconds = duration.as_secs_f64();
    println!("Result: {}", result);
    println!("Time: {:.5} seconds", duration_seconds);
}
ビルドして実行する手順を開きます。

ビルドします。

$ rustc -C opt-level=2 -o fibonacci_rs fibonacci.rs

実行します。

$ ./fibonacci_rs

出力結果

Result: 701408733
Time: 1.72496 seconds
実行結果の詳細をみます。
回数 時間(秒) 備考
1 1.75236
2 1.75924 MAX
3 1.71937 MIN
4 1.72496

4回の実行結果から最大値と最小値を取り除いて、残りの2回の結果の平均値を求めています。※小数点5桁以下は切り捨て

シェルスクリプト の処理時間を測定

sh ファイルを作成します。

$ vim fibonacci.sh

ファイルの内容

fibonacci.sh ※抜粋
# フィボナッチ数列を再帰的な方法で計算する関数
fibonacci() {
    n=$1
    if [ $n -eq 0 ] || [ $n -eq 1 ]; then
        echo $n
    else
        echo $(expr $(fibonacci $(expr $n - 2)) + $(fibonacci $(expr $n - 1)))
    fi
}
コード全体を表示します。
fibonacci.sh
#!/bin/sh

TERM=44  # フィボナッチ数列の項数を定義

# フィボナッチ数列を再帰的な方法で計算する関数
fibonacci() {
    n=$1
    if [ $n -eq 0 ] || [ $n -eq 1 ]; then
        echo $n
    else
        echo $(expr $(fibonacci $(expr $n - 2)) + $(fibonacci $(expr $n - 1)))
    fi
}

# 関数の実行時間を測定
start_time=$(date +%s%N)
result=$(fibonacci $TERM)
end_time=$(date +%s%N)

# 実行時間を秒に変換して表示
duration_nanoseconds=$(expr $end_time - $start_time)
duration_seconds=$(awk "BEGIN {print $duration_nanoseconds / 1e9}")
echo "Result: $result"
echo "Time: $duration_seconds seconds"
実行する手順を開きます。

実行します。

$ chmod +x fibonacci.sh
$ ./fibonacci.sh

出力結果

※ 計測断念: 遅すぎる😭

シェルスクリプトでは、一般的なプログラム言語と同じような書き方をすると効率的でない場合があります。

計測した結果を比較

実行環境

項目 内容
プロセッサ Intel(R) Core(TM) i7-10750H CPU @ 2.60GHz 2.59 GHz
実装 RAM 16.0 GB
システムの種類 64 ビット オペレーティング システム、x64 ベース プロセッサ
OS Ubuntu 22.04.1 LTS (Windows 11 WSL)

計測結果

順位 言語/コンパイラ/環境 実行形式 処理時間(秒) Java JITコンパイルを 1.0 とした割合
1 C (gcc 11.4.0) ネイティブ 0.91076 0.336
2 C++ (g++ 11.3.0) ネイティブ 0.92987 0.344
3 Rust (rustc 1.71.1) ネイティブ 1.72496 0.637
4 Java (openjdk version "17.0.7") JITコンパイル 2.70795 1.000
5 Go (go1.18.1 linux/amd64) ネイティブ 2.76093 1.018
6 C#.NET (dotnet 7.0.202) JITコンパイル 2.92658 1.080
7 Java (GraalVM 22.3.1 Java 17 CE) ネイティブ 3.28288 1.210
8 Node.js (node v19.8.1) インタプリタ 6.31335 2.324
9 Python (python 3.10.6) インタプリタ 162.69341 60.063

各言語で4回の実行結果から最大値と最小値を取り除いて、残りの2回の結果の平均値を求めています。※小数点5桁以下は切り捨て

結果からわかること

キーワード 考察
ネイティブコンパイルの優位性 C や C++、Rust などのネイティブコンパイル言語が、JIT コンパイルやインタプリタを使用する言語に比べて処理速度が速いことが示されています。Go に関しては今回の実装では優位性は見られませんでした。
インタプリタの遅さ インタプリタを使用する Python や Node.js では処理時間が比較的長くなっており、実行速度が他の言語より遅いことが示されています。

Java では、GraalVM ネイティブイメージビルドにより高速化が期待されましたが、再帰的なフィボナッチ数演算に関しては今回の計測結果においては、JIT コンパイル最適化の方がネイティブイメージ実行よりも高速でした。

まとめ

  • 再帰的なフィボナッチ数演算に関しては、C、C++ はダントツで高速でした😮
  • また、各言語でのプログラム実行方法や、時間の処理について学ぶことができました。

本記事は、Ubuntu 22.04 上でのプログラム実行速度比較に焦点を当て、フィボナッチ数列の求め方を通じて異なるプログラミング言語のパフォーマンスを調査しました。クラウド環境におけるアプリケーション開発者にとって、言語選定の際の参考となれば幸いです。

どうでしたか? Windows 11 の Linux で、クラウド向けの開発環境を手軽に構築することができます。ぜひお試しください。今後もさまざまな言語や開発環境の情報などを紹介していきますので、ぜひお楽しみにしてください。

関連コンテンツ

5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6