C++,Java,Python,Go,Rust…いろいろあるプログラミング言語の違いのひとつが、処理速度の違い。
- C++は速い
- Pythonは遅い
- RustはC++並み
など、いろいろ言われます。
でも、そもそもなんで、プログラミング言語ごとに、処理速度が違ってくるの?やってることそんなに違うのでしょうか?
※2023/12/31 JavaScriptコード修正し時間を再計測しました(速度向上)。ご指摘くださった方ありがとうございました。
プログラミング言語の処理速度になぜ違いがでるのか
全く同じ結果を出すコードを書いたとしても、プログラミング言語によって処理速度は変わってきます。
実際の例で紐解きましょう。11個の言語を比較してみます。
- C++
- Java
- C#
- Python
- JavaScript
- PHP
- Ruby
- Swift
- Kotlin
- Go
- Rust
どの言語が速くて、どの言語が遅いのか。違いはどこにあるのか。
コンピューターは、ソースコードをそのまま読めるわけじゃない
まず重要なこととして、どのプログラミング言語のコードも、コンピューターは直接理解できません。
コンピューターが理解できるのは、マシン語のみです。マシン語とは、こんなコード。
7001 0000 7002 0000 1212 800d 1220 800C …
まあ、訳分からないですね。とうてい人には読めない代物です。
なのでプログラムが動くときは、こんなステップを踏みます。
- 「人が理解できる言語」でコードを書き
- それを「マシン語に翻訳」する
この「人が理解できる言語」が、すなわちプログラミング言語です。
翻訳のやりかたによる速度の違い
マシン語への翻訳という作業は、日本人とアメリカ人が会話するシチュエーションでイメージできます。
会話するとき、翻訳の仕方に2種類あるのは、想像つくでしょう。
- 同時翻訳 日本語で話すと、ほぼ同時に英語に翻訳する
- 一括翻訳 日本語で書いた内容を、あとでまとめて英語に翻訳する
マシン語への翻訳の仕方で、プログラミング言語は2種類に大別されます。
同時翻訳方式をインタープリタ方式、一括翻訳方式をコンパイラ方式、といいます。
インタープリタ型:すぐ実行できるが遅い
インタープリタ型の言語と特徴は、この通り。
インタープリタ方式 |
---|
該当する言語:Python, JavaScript, PHP, Ruby |
翻訳方法:同時翻訳方式 |
処理速度:遅い |
実行するとき:すぐに実行できる |
Pythonなどの言語は、コードを書いたらすぐに実行できます。これはプログラミング言語のエンジンが、コードを1行づつマシン語に翻訳しながら実行してくれるためです。
すぐに実行はできますが、処理速度は遅くなる。毎ステップ翻訳しているからですね。
コンパイラ型:速いが事前コンパイル必要
コンパイラ型の言語と特徴は、この通り。
コンパイラ方式 |
---|
該当する言語:C++, Java, C#, Swift, Kotlin, Go, Rust |
翻訳方式:一括翻訳方式 |
処理速度:速い |
実行するとき:まずコンパイル(=翻訳)が必要 |
C++などの言語は、コードを書いたら、まずコンパイルする必要があります。このコンパイルの過程で、マシン語に翻訳しているわけですね。
翻訳が終わったら、実行ファイルができます。これはマシン語になっているので、高速に動作するわけです。
正確には、コンパイルしたときに、完全なマシン語になる場合と、中間的な言語になる場合があります。JavaやC#は中間コードになります。
どちらが優れているという話じゃない
では、インタープリタ型とコンパイル型、どちらが優れているの?
というと、そういう話ではないことは分かるでしょう。要は使い道によって使い分ける、ということです。
11言語で処理速度を計測してみる
では実際に、11種類のプログラミング言語で、どれくらい処理速度が違うのか、計測してみましょう。
ライプニッツ公式を計算
題材に使うのは、ライプニッツの公式(Leibniz formula)。円周率πを計算するプログラムです。
ライプニッツの公式とは、以下のような式。
$$ {\displaystyle 1-{\frac {1}{3}}+{\frac {1}{5}}-{\frac {1}{7}}+{\frac {1}{9}}-\cdots ={\frac {\pi }{4}}} $$
1,-1/3,+1/5,…と、繰り返し計算していきます。繰り返し数が多いほど、ほんとうの円周率πの値に近づきます。
計算を1億回繰り返してみます。とほうもない数!でもコンピューターなら余裕です。
開発および実行環境として、LinuxのDockerImageを使います。その他スペックは以下の通り。
OS | Ubuntu22.04 (Window11上のWSL2) |
---|---|
CPU | Ryzen5 |
開発環境 | DockerHubより各Imageを取得 (2023/11時点のlatestバージョン) |
各言語のソースコードとDocker実行コマンドは次の通りです。
やや専門的な内容なので、興味のある言語を見てください。
C++
![C++](http://plainprogram.com/wp-content/uploads/2023/05/c-plusplus-e1698462696379.png)
古くからある古典的な言語。処理速度を追求するならC++と言われますが…
C++ソースコードと実行コマンド
ベンチマークを取るには、C++11から追加されたchronoを使うと便利です。
#include <iostream>
#include <chrono>
#include <cmath>
#include <iomanip>
// ライプニッツ公式計算
double CalculateLeibnizFormula(int n) {
double pi_approx = 0.0;
double sign = 1;
for (int i = 0; i < n; i++) {
pi_approx += sign / (2.0 * i + 1.0);
sign = -sign;
}
pi_approx *= 4.0;
return pi_approx;
}
int main() {
// 計算する項数(1億回)
const int n = 100000000;
// 計測開始時間
const auto start_time = std::chrono::high_resolution_clock::now();
// ライプニッツ公式計算
const double pi_approx = CalculateLeibnizFormula(n);
// 誤差
const double calculation_error = fabs(M_PI - pi_approx);
// 計測終了時間
const auto end_time = std::chrono::high_resolution_clock::now();
// 計算時間
const auto elapsed_millisec
= std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time).count();
std::cout << "円周率近似値 : " << pi_approx << std::endl;
std::cout << "計算誤差 : " << calculation_error << std::endl;
std::cout << "経過時間[msec]: " << elapsed_millisec << std::endl;
return 0;
}
LinuxでのC++コンパイラといったら、gcc。最適化のため-Oオプションを入れます。
docker run --rm -v "$PWD":/usr/src/leibniz -w /usr/src/leibniz gcc g++ -O leibniz.cpp -o leibniz_cpp
./leibniz_cpp
Java
![Java](http://plainprogram.com/wp-content/uploads/2023/05/java-e1698462520920.png)
Webサーバーサイドの主力であるJava。昔は相当遅かったが、改良を重ねて速くなっています。
Javaソースコードと実行コマンド
import java.time.Duration;
import java.time.Instant;
public class Leibniz {
// ライプニッツ公式計算
public static double calculateLeibnizFormula(int n) {
double piApprox = 0.0;
double sign = 1.0;
for (int i = 0; i < n; i++) {
piApprox += sign / (2.0 * i + 1.0);
sign = -sign;
}
piApprox *= 4.0;
return piApprox;
}
public static void main(String[] args) {
// 計算する項数(1億回)
final int n = 100000000;
// 計測開始時間
Instant startTime = Instant.now();
// ライプニッツ公式計算
final double piApprox = Leibniz.calculateLeibnizFormula(n);
// 誤差
final double calculationError = Math.abs(Math.PI - piApprox);
// 計測終了時間
Instant endTime = Instant.now();
// 計算時間
Duration elapsed = Duration.between(startTime, endTime);
System.out.println("円周率近似値 : " + piApprox);
System.out.println("計算誤差 : " + calculationError);
System.out.println("経過時間[msec]: " + elapsed.toMillis());
}
}
Javaのビルドには、JDK(Java Development Kit)が必要です。ここではOpenJDKを使います。JDKには、AdoptOpenJDK,Eclipse Temurin,Amazon Correttoなど色々ありますが、基本的にはOpenJDKと同じです。
docker run --rm -v "$PWD":/usr/src/leibniz -w /usr/src/leibniz openjdk javac Leibniz.java
C#
![](http://plainprogram.com/wp-content/uploads/2023/05/c-sharp-e1698462724906.png)
Javaの対抗馬としてMicrosoftが開発した言語です。.NET Coreの登場でLinuxでも動くようになりました。
C#ソースコードと実行コマンド
using System;
class Program
{
// ライプニッツ公式計算
static double CalculateLeibnizFormula(int terms)
{
double piApprox = 0.0;
double sign = 1.0;
for (int i = 0; i < terms; i++)
{
piApprox += sign / (2.0 * i + 1.0);
sign = -sign;
}
piApprox *= 4.0;
return piApprox;
}
static void Main()
{
// 計算する項数(1億回)
const int n = 100000000;
//計測開始
var sw = new System.Diagnostics.Stopwatch();
sw.Start();
// ライプニッツ公式計算
double piApprox = CalculateLeibnizFormula(n);
// 誤差
double calculationError = Math.PI - piApprox;
// 計測停止
sw.Stop();
Console.WriteLine("円周率近似値 : {0}", piApprox);
Console.WriteLine("計算誤差{0} : {0}", calculationError);
Console.WriteLine("経過時間[msec]: {0}", sw.ElapsedMilliseconds);
}
}
ここでは、.NET FrameworkのLinuxオープン実装である、Monoを使います。
docker run --rm -v "$PWD":/usr/src/leibniz -w /usr/src/leibniz mono mcs leibniz.cs
./leibniz.exe
Python
![](http://plainprogram.com/wp-content/uploads/2023/05/python-e1698462762805.png)
AIの登場で一気に普及したPython。オールマイティに使える言語です。
Pythonソースコードと実行コマンド
import math
import time
# ライプニッツ公式計算
def calculate_leibniz_formula(n):
pi_approx = 0
sign = 1
for i in range(n):
pi_approx += sign / (2 * i + 1)
sign = -sign
pi_approx *= 4
return pi_approx
# 計算する項数(1億回)
n = 100000000
# 計測開始時間
start_time = time.monotonic()
# ライプニッツ公式計算
pi_approx = calculate_leibniz_formula(n)
# 誤差
calculation_error = math.pi - pi_approx
# 計測終了時間
end_time = time.monotonic()
# 計算時間
elapsed_millisec = (end_time - start_time) * 1000
print("円周率近似値 : ", pi_approx)
print("計算誤差 : ", calculation_error)
print("経過時間[msec]: ", elapsed_millisec)
公式DockerImageにpythonがあります。
docker run -it --rm --name python-leibniz -v "$PWD":/usr/src/leibniz -w /usr/src/leibniz python python leibniz.py
JavaScript
![](http://plainprogram.com/wp-content/uploads/2023/05/javascript-e1698462739990.png)
Webフロントエンドで必須のJavaScript。Node.jsの登場でバックエンドにも使えるようになっています。
JavaScriptソースコードと実行コマンド
// ライプニッツ公式計算
const calculateLeibnizFormula = (n) => {
let piApprox = 0;
let sign = 1;
for (let i = 0; i < n; i++) {
piApprox += sign / (2 * i + 1);
sign = -sign;
}
piApprox *= 4;
return piApprox;
}
// 計算する項数(1億)
const n = 100000000;
// 計測開始時間
startTime = Date.now();
// ライプニッツ公式計算
piApprox = calculateLeibnizFormula(n);
// 誤差
calculationError = Math.abs(Math.PI - piApprox);
// 計測終了時間
endTime = Date.now();
// 計算時間
elapsedMillisec = (endTime - startTime);
console.log(`円周率近似値 : ${piApprox}`);
console.log(`計算誤差 : ${calculationError}`);
console.log(`経過時間[msec]: ${elapsedMillisec}`);
公式DockerImageのnodeを使います。なのでサーバーサイド側での実行となります。ただNode.jsで使っているエンジンはChromeブラウザなどと同じV8なので、ブラウザで実行しても速度は大きくは変わりません。
docker run -it --rm --name js-leibniz -v "$PWD":/usr/src/leibniz -w /usr/src/leibniz node node leibniz.js
PHP
![](http://plainprogram.com/wp-content/uploads/2023/05/php-e1698462898117.png)
Webを支えてきたスクリプト言語PHP。どれくらいの速さなのか。
PHPソースコードと実行コマンド
<?php
// ライプニッツ公式計算
function calculateLeibnizFormula($n) {
$pi_approx = 0.0;
$sign = 1.0;
for ($i = 0; $i < $n; $i++) {
$pi_approx += $sign / (2 * $i + 1);
$sign = -$sign;
}
return 4 * $pi_approx;
}
// 計算する項数(1億回)
$n = 100000000;
// 計測開始時間
$startTime = microtime(true);
// ライプニッツ公式計算
$piApprox = calculateLeibnizFormula($n);
// 誤差
$calculationError = pi()-$piApprox;
// 計測終了時間
$endTime = microtime(true);
// 計算時間
$elapsedMillsec = ($endTime - $startTime);
echo "円周率近似値 : {$piApprox}";
echo "計算誤差 : {$calculationError}";
echo "経過時間[msec]: {$elapsedMillsec}";
?>
公式DockerImageであるphpを使います。
docker run -it --rm --name php-leibniz -v "$PWD":/usr/src/leibniz -w /usr/src/leibniz php php leibniz.php
Ruby
![](http://plainprogram.com/wp-content/uploads/2023/05/ruby-e1698462886739.png)
Webを支えてきたスクリプト言語Ruby。PHPと差はいかほどか。
Rubyソースコードと実行コマンド
def calculate_leibniz_formula(n)
pi_approx = 0.0
sign = 1.0
for i in 0...n
pi_approx += sign / (2.0 * i + 1)
sign = -sign
end
4 * pi_approx
end
require 'benchmark'
# 計算する項数(1億回)
n = 100000000
# 計測開始
pi_approx = 0
elapsed_sec = Benchmark.realtime do
# ライプニッツ公式計算
pi_approx = calculate_leibniz_formula(n)
end
# 誤差
calculation_error = Math::PI - pi_approx
# 計算時間
elapsed_millisec = elapsed_sec * 1000
puts "円周率近似値 : #{pi_approx}"
puts "計算誤差 : #{calculation_error}"
puts "経過時間[msec]: #{elapsed_millisec}"
公式DockerImageであるrubyを使います。
docker run -it --rm --name ruby-leibniz -v "$PWD":/usr/src/leibniz -w /usr/src/leibniz ruby ruby leibniz.rb
Swift
![](http://plainprogram.com/wp-content/uploads/2023/05/swift-e1698462776799.png)
Swiftは、iPhoneやiPadなどのiOSアプリケーション開発で主に使われる言語。
Swiftソースコードと実行コマンド
import Foundation
func calculateLeibnizFormula(n: Int) -> Double {
var piApprox = 0.0
var sign = 1.0
for i in 0..<n {
piApprox += sign / (Double(i) * 2.0 + 1.0)
sign = -sign
}
return piApprox * 4.0
}
// 計算する項数(1億回)
let n = 100000000
// 計測開始時間
let startTime = Date()
// ライプニッツ公式計算
let piApprox = calculateLeibnizFormula(n: n)
// 誤差
let calculationError = Double.pi - piApprox
// 計算時間
let elapsedMillisec = Date().timeIntervalSince(startTime) * 1000
print("円周率近似値 : \(piApprox)");
print("計算誤差 : \(calculationError)");
print("経過時間[msec]: \(elapsedMillisec)");
公式DockerImageであるswiftを使います。最適化のため-Oオプションを入れます。
docker run --rm -v "$PWD":/usr/src/leibniz -w /usr/src/leibniz swift swift -O leibniz.swift
Kotlin
![](http://plainprogram.com/wp-content/uploads/2023/05/kotlin-e1698462791706.png)
Javaを源流とするKotlin。Androidアプリの推奨開発言語です。
Kotlinソースコードと実行コマンド
// ライプニッツ公式計算
fun calculateLeibnizFormula(n: Int): Double {
var piApprox = 0.0
var sign = 1.0
for (i in 0 until n) {
piApprox += sign / (i * 2 + 1)
sign = -sign
}
return piApprox * 4
}
fun main(args: Array<String>) {
// 計算する項数(1億回)
val n = 100000000;
// 計測開始時間
val startTime = System.currentTimeMillis()
// ライプニッツ公式計算
val piApprox = calculateLeibnizFormula(n)
// 誤差
val calculationError = Math.abs(Math.PI - piApprox)
// 計測終了時間
val endTime = System.currentTimeMillis()
// 計算時間
val elapsedMillisec = endTime - startTime
println("円周率近似値 : $piApprox");
println("計算誤差 : $calculationError");
println("経過時間[msec]: $elapsedMillisec");
}
公式DockerImageであるkotlinを使います。
docker run --rm -v "$PWD":/usr/src/leibniz -w /usr/src/leibniz zenika/kotlin kotlinc leibniz.kt -include-runtime -d leibniz.jar
docker run --rm -v "$PWD":/usr/src/leibniz -w /usr/src/leibniz zenika/kotlin kotlin leibniz.jar
Go
![](http://plainprogram.com/wp-content/uploads/2023/05/go-e1698462805947.png)
Googleが開発した言語です。大規模スケールのアプリに対応することを目的としています。
Goソースコードと実行コマンド
package main
import (
"fmt"
"math"
"time"
)
// ライプニッツ公式計算
func calculateLeibnizFormula(n int) float64 {
piApprox := 0.0
sign := 1.0
for i:= 0; i < n; i++ {
piApprox += sign / (2.0*float64(i) + 1.0)
sign = -sign;
}
piApprox *= 4.0
return piApprox
}
func main() {
// 計算する項数(1億)
n := 100000000
// 計測開始時間
startTime := time.Now()
// ライプニッツ公式計算
piApprox := calculateLeibnizFormula(n)
// 誤差
calculationError := math.Pi - piApprox
// 計測終了時間
endTime := time.Now()
// 計算時間
elapsed := endTime.Sub(startTime)
fmt.Printf("円周率近似値 : %v\n", piApprox)
fmt.Printf("計算誤差 : %v\n", calculationError)
fmt.Printf("経過時間[msec]: %v\n", elapsed.Milliseconds())
}
公式DockerImageであるgolangを使います。Go言語のコンパイル時はデフォルトで最適化されます。
docker run --rm -v "$PWD":/usr/src/leibniz -w /usr/src/leibniz golang go run leibniz.go
Rust
![](http://plainprogram.com/wp-content/uploads/2023/05/rust-e1698462820582.png)
C++の性能を持ちかつ安全に使えることを目標とするRust。Linuxカーネル開発にも対応しています。
Rustソースコードと実行コマンド
use std::time::Instant;
// ライプニッツ公式計算
fn calculate_leibniz_formula(n : i32) -> f64 {
let mut pi_approx: f64 = 0.0;
let mut sign: f64 = 1.0;
for i in 0..n {
pi_approx += sign / (2.0 * i as f64 + 1.0);
sign = -sign;
}
pi_approx * 4.0
}
fn main() {
// 計算する項数
let n = 100000000;
// 計測開始時間
let start_time = Instant::now();
// ライプニッツ公式計算
let pi_approx = calculate_leibniz_formula(n);
// 誤差
let calculation_error = std::f64::consts::PI - pi_approx;
// 計算時間を計測
let end_time = Instant::now();
let elapsed = end_time.duration_since(start_time);
// 結果を表示
println!("円周率近似値 : {}", pi_approx);
println!("計算誤差 : {}", calculation_error);
println!("経過時間[msec]: {}", elapsed.as_millis());
}
公式DockerImageであるrustを使います。なおここではcargoでプロジェクト作成していることを前提としています。–releaseオプションで最適化しています。
docker run --rm -v "$PWD":/usr/src/leibniz -w /usr/src/leibniz rust cargo run --release
処理速度計測結果
全11言語の、円周率計算処理の処理時間を見てみましょう。
言語 | 方式 | 処理時間[ミリ秒] |
---|---|---|
C++ | コンパイラ | 102 |
Java | コンパイラ | 104 |
C# | コンパイラ | 102 |
Python | インタープリタ | 11193 |
JavaScript | インタープリタ | 117 |
PHP | インタープリタ | 2548 |
Ruby | インタープリタ | 10168 |
Swift | コンパイラ | 102 |
Kotlin | コンパイラ | 106 |
Go | コンパイラ | 107 |
Rust | コンパイラ | 103 |
![](https://plainprogram.com/wp-content/uploads/2023/12/speed-1024x476.png)
コンパイラ型言語は速い。インタープリタ型言語は遅い。一目瞭然ですね。
処理速度を見直すときのポイント3つ
プログラミング言語によって、処理速度が異なるのは事実。
でもだからといって、プログラミング言語の選定において、処理速度が決定的な理由になることは、まずないです。
処理速度を考えるなら、プログラミング言語の選択よりも大事なことがあります。
- 処理速度は、I/Oバウンドタスクのほうが影響が大きい
- ライブラリが性能改善になるときもある
- プログラミング言語を組み合わせて使うこともできる
I/Oバウンドタスクに注意しよう
コンピューター処理で処理時間がかかるものは、次の2つに区分されます。
- CPUバウンドタスク CPUの能力に影響する処理。計算処理など。
- I/Oバウンドタスク 入出力の能力に影響する処理。ファイル読み書き、ネットワーク通信など
このうち、プログラミング言語によって差が出るのは、CPUバウンドタスクだけです。
I/Oバウンドタスクは、プログラミング言語の違いには殆ど影響しません。どちらかといえばこのような要素に影響します。
- OS(Windows,Mac,Linuxなど)
- ハードウェア(ディスクread/write速度、ネットワーク通信速度など)
そして、処理速度の問題は、I/Oバウンドタスクによって引き起こさるケースが多いのです。
性能問題は、I/Oバウンドタスクの性能向上(入出力データサイズを減らすとか、バックグラウンドで処理するとか)を考えるとか、並行/並列処理を考えたほうがよい場合が多いです。
![](https://plainprogram.com/wp-content/uploads/2023/10/concurrency-and-parallelism-1-300x169.jpg)
ライブラリを活用しよう
どのプログラミング言語でも、様々な用途に応じたライブラリが使えます。
速度向上を重視したライブラリも多いので、ライブラリを使うだけで処理速度が向上することも多々あります。
Pythonで考えましょう。有名なライブラリにNumPyがあります。NumPyを使った処理でも計測してみましょう、
import numpy as np
# ライプニッツ公式計算(NumPy利用)
def calculate_leibniz_formula3(n):
seq = np.array(range(n))
pi_approx = np.sum( np.where(seq % 2 == 0, 1, -1) / (seq * 2 + 1)) * 4
return pi_approx
言語 | 処理時間[ミリ秒] |
---|---|
Python | 11193 |
Python(NumPy使用) | 5704 |
ライブラリを使っただけで、処理時間が1/2になりました。
特にPythonは、ライブラリを使ってこそ真価を発揮する言語です。積極的に活用しましょう。
プログラミング言語の組み合わせも検討しよう
処理速度が重要であるなら、特に速度が重要なところだけ別のプログラミング言語で組む、という手法もよく採られます。
PythonのライブラリをC++で組む
さきほど出てきたNumPy。Pythonのライブラリですが、実はC言語で実装されています。
Pythonのライブラリを、C++などで組む、というやり方はよく使われます。
JavaScriptの一部処理をWebAssemblyで組む
永らくJavaScriptしか使えなかったWebフロントエンド。近年は、WebAssemblyという技術が登場しています。コンパイラ型に近い処理速度が実現可能で、C、C++、Rustといった言語で開発が可能です。
メインはJavaScript、時間のかかる処理だけRustでWebAssenmby実装、といった組み合わせも可能です。
リアルタイム処理の場合は要注意
処理速度が言語選択の決定的理由ではない、と言いましたが、リアルタイム処理の場合は話が違ってきます。
リアルタイム処理とは、遅延が許されない処理、のこと。例えば以下のようなものがあります。
- ロボット制御
- オンラインゲーム
- OSカーネル
これらは、常に一定間隔で動作する必要があり、遅れてはならない処理です。遅れとは1秒や2秒といったレベルでなく、ミリ秒(千分の一秒)、マイクロ秒(百万分の一秒)レベルの話になります。
このレベルなら当然コンパイル型言語を使うのですが、更に注意すべきなのが、プログラミング言語が持つガベージコレクト機構です。
ガベージコレクトとは、使わなくなったメモリを裏で自動開放する仕掛け。Java,C#,Swift,Kotlin,Goなどが持っています。メモリ開放を気にしなくていいメリットがあるのですが、いつ動くかは分かりません。これが裏で動いたときに、リアルタイム性に影響を及ぼす場合があるのです。
なのでリアルタイム処理には、ガベージコレクトを持たない C、C++、Rustといった言語が、主に使われます。
C++には「ゼロオーバーヘッド原則」、Rustには「ゼロコスト抽象化」の考えがあります。どちらも、実行するときにガベージコレクトなどの余計なCPUコストをゼロにする、という方針で設計されている言語です。
まとめ
全11種のプログラミング言語で、処理速度の比較を行ってきました。
前述のように、プログラミング言語選択において、処理速度が決定的な理由になることは少ないでしょう。
しかし、プログラミング言語ごとの処理速度の特性を知っておくことは、性能向上に有効です。その背後にある本質を掴んでおきましょう。
![](https://plainprogram.com/wp-content/uploads/2023/09/pragmatic-programmer-300x169.jpg)
![](https://plainprogram.com/wp-content/uploads/2023/05/comparison-of-programming-languages-300x169.jpg)
![](https://plainprogram.com/wp-content/uploads/2024/02/little-experience-with-container-300x169.jpg)
![](https://plainprogram.com/wp-content/uploads/2024/02/nullpo-300x169.jpg)
![](https://plainprogram.com/wp-content/uploads/2024/05/program-platform-300x169.jpg)
コメント
コメント一覧 (8件)
AMD Ryzen 7 PRO 4750G (8C16T 3.60 GHz)、Python3.12の環境でやってみました
バックグランドの処理があり結果は安定しないので5回計測です
円周率近似値 : 3.141592643589326
計算誤差 : 1.0000467121074053e-08
経過時間[msec]: 9328.00000000043 9110.000000000582 9514.999999999418 9016.000000000531 9421.000000000276
関数の前に2行足してJITでも試しました
from numba import jit
@jit
経過時間[msec]: 342.9999999998472 344.00000000005093 327.9999999995198 342.9999999998472 344.00000000005093
Powershellでもやってみました(Powershellがどのくらい遅いのか知りたくて比較でPythonをやったのが事実ですが)
# ライプニッツ公式計算
function calculate_leibniz_formula($n) {
$pi_approx = 0
$sign = 1
foreach ($i in 0..$n) {
$pi_approx += $sign / (2 * $i + 1)
$sign = -$sign
}
$pi_approx *= 4
return $pi_approx
}
# 計算する項数(1億回)
$n = 100000000
# 計測開始時間
$stopwatch = [System.Diagnostics.Stopwatch]::new()
$Stopwatch.Restart()
# ライプニッツ公式計算
$pi_approx = calculate_leibniz_formula $n
# 誤差
$calculation_error = [math]::pi – $pi_approx
# 計算時間
$elapsed_millisec = $Stopwatch.elapsed.TotalMilliseconds
“円周率近似値 : $pi_approx”
“計算誤差 : $calculation_error”
“経過時間[msec]: $elapsed_millisec”
円周率近似値 : 3.14159266358933
計算誤差 : -9.99953275737653E-09
経過時間[msec]: 219900.8995
思った以上で絶望的に遅いのでループを1/2で変数へのアクセスを減らして計測
for ($i = 0; $i -lt $n; $i += 2) {
$pi_approx += 1 / (2 * $i + 1) + -1 / (2 * ($i + 1) + 1)
}
円周率近似値 : 3.14159264457622
計算誤差 : 9.01357743998688E-09
経過時間[msec]: 121251.7091
言語の速度比較にはなってないです、すみません
Powershellで速度が必要な部分は率直にC#で書くべきですね
Add-Type -T @”
using System;
public class cs {
public static double CalculateLeibnizFormula(int n) {
double pi_approx = 0.0;
double sign = 1;
for (int i = 0; i < n; i++) {
pi_approx += sign / (2.0 * i + 1.0);
sign = -sign;
}
pi_approx *= 4.0;
return pi_approx;
}
"@
$pi_approx = [cs]::calculateleibnizformula($n)
円周率近似値 : 3.14159264358933
計算誤差 : 1.00004671210741E-08
経過時間[msec]: 116.1705 114.6853 115.2893 114.1421 114.9261
コメントに間違いがありました、正しくは
Add-Type -T @”
using System;
public class cs {
public static double CalculateLeibnizFormula(int n) {
double pi_approx = 0.0;
double sign = 1;
for (int i = 0; i < n; i++) {
pi_approx += sign / (2.0 * i + 1.0);
sign = -sign;
}
pi_approx *= 4.0;
return pi_approx;
}
}
"@
でした
tyさん、コメントありがとうございます!
numbaを入れると、かなり速くなるのですね。
最近のPythonは、高速化技術もいろいろあるので、
「Pythonは遅い」というのも、すぐに過去の話になるかもしれませんね。
Powershellは試したことありませんでしたが、確かに遅いのですね…
貴重な情報ありがとうございました!
前回のコメントで根本的なミスをしていました
vscode上で編集しそのまま実行したのですがPowershell拡張機能が入っており
デバッガーの介在によると思われる大幅な性能低下があり130秒程度かかっていたのですが
ターミナルで実行すると11秒程度となり10倍以上高速となりました
プログラムは掲載されたものをPowershellで動作するように改変したものです
人騒がせなコメントで申し訳ありませんでした
tyさんありがとうございます。
拡張機能次第で、大きな差が出ることもあるのですね。
たいへん貴重な情報を頂きました。
ぜひこれからも、お気づきのことありましたら、コメントお寄せください!
よろしくお願いいたします。
gcc の最適化オプションは -o じゃなくて -O とか -O2 な気がして、かなりパフォーマンスが変わると思われます。
なべるさんご指摘ありがとうございます。
ご指摘にあります通り、-O や -O2 が正しい最適化オプションになります。
誤記がありましたので修正いたしました。
なお計測結果自体は、-O オプションありで計測したものを記載しています。
他にもお気づきのことありましたら、ぜひコメントお寄せください。
よろしくお願いいたします。