C++, Java, Python, Go, Rust… There are many programming languages. One of the differences between them is processing speed.
People say various things, such as:
- C++ is fast
- Python is slow
- Rust is on par with C++
But first of all, why do different programming languages have different processing speeds? Are the things they do really that different?
Why do programming languages differ in speed?
Even code that produces the exact same results can run at different speeds depending on the programming language used.
Let’s look at a real example. Let’s compare 11 languages.
- C++
- Java
- C#
- Python
- JavaScript
- PHP
- Ruby
- Swift
- Kotlin
- Go
- Rust
Which languages are fast and which are slow? What are the differences?
Computers can’t read source code as is
First, an important thing to remember: computers cannot directly understand code in any programming language.
The only thing computers can understand is machine language, which is code like this:
7001 0000 7002 0000 1212 800d 1220 800C …
Well, it’s incomprehensible. It’s something that no human being can read.
When a program runs, it goes through these steps:
- Write code in a language people can understand
- Translate it into machine language
This ” language that humans can understand ” is a programming language.
Differences in translation speed depending on the method
The process of translating into machine language can be imagined as a situation where an American and a Japanese person are having a conversation.
As you can imagine, there are two ways of translating.
- Simultaneous translation: When you speak in English, it is translated into Japanese almost instantly
- Bulk translation: Translate content written in English into Japanese at a later date
Programming languages are broadly divided into two types based on how they are translated into machine code.
The simultaneous translation method is called the interpreter method , and the batch translation method is called
the compiler method.
Interpreted: Quick to execute but slow
The interpreted languages and their features are as follows:
Interpreter Method |
---|
Applicable languages: Python, JavaScript, PHP, Ruby |
Translation method: Simultaneous translation |
Processing speed: Slow |
When to run: Can be run immediately |
Languages like Python allow you to write code and then execute it immediately, because the programming language engine translates each line of code into machine code and executes it.
It can be executed quickly, but the processing speed is slow because translation is performed at every step.
Compiler type: Fast but requires ahead-of-time compilation
The compiler-based languages and their features are as follows:
Compiler method |
---|
Applicable languages: C++, Java, C#, Swift, Kotlin, Go, Rust |
Translation method: Bulk translation method |
Processing speed: Fast |
When executing: Compilation (= translation) is required first |
When you write code in a language like C++, you first need to compile it , during which it is translated into machine code.
Once the translation is complete, an executable file is created. Since this is in machine language, it runs very quickly.
To be precise, when compiled, it may be fully compiled into machine code, or it may be compiled into an intermediate language. Java and C# are intermediate codes.
It’s not a question of which is better.
So which is better, interpreted or compiled?
You can probably tell that’s not what I’m talking about. The point is to use them differently depending on how you’re using them.
Testing processing speed in 11 languages
Now let’s actually measure how much the processing speed differs among 11 different programming languages.
Calculate the Leibniz formula
The subject we will use is the Leibniz formula. It is a program to calculate pi.
Leibniz’s formula is as follows:
$$ {\displaystyle 1-{\frac {1}{3}}+{\frac {1}{5}}-{\frac {1}{7}}+{\frac {1}{9}}-\cdots ={\frac {\pi }{4}}} $$
The calculation is repeated: 1, -1/3, +1/5, … The more times it is repeated, the closer it gets to the true value of pi.
Let’s try to repeat the calculation 100 million times . A huge number! But a computer can do it easily.
We use the Linux Docker Image for the development and execution environment. Other specifications are as follows.
OS | Ubuntu22.04 (WSL2 on Windows11) |
---|---|
CPU | Ryzen5 |
Development environment | Get each image from DockerHub (latest version as of November 2023) |
The source code for each language and the Docker execution command are as follows.
The content is somewhat technical, so please look at the language that interests you.
C++
C++ is a classic language that has been around for a long time. If you want speed, C++ is the way to go…
C++ source code and execution command
#include <iostream>
#include <chrono>
#include <cmath>
#include <iomanip>
// Calculate using the Leibniz formula
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() {
// Number of terms to compute (100 million iterations)
const int n = 100000000;
const auto start_time = std::chrono::high_resolution_clock::now();
// Compute using the Leibniz formula
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 << "Approximation of Pi : " << pi_approx << std::endl;
std::cout << "Calculation Error : " << calculation_error << std::endl;
std::cout << "Elapsed Time [msec] : " << elapsed_millisec << std::endl;
return 0;
}
When it comes to C++ compilers on Linux, I use gcc. I add the -O option for optimization.
docker run --rm -v "$PWD":/usr/src/leibniz -w /usr/src/leibniz gcc g++ -O leibniz.cpp -o leibniz_cpp
./leibniz_cpp
Java
Java is the main language used on the web server side. It used to be quite slow, but it has become faster through repeated improvements.
Java source code and execution command
import java.time.Duration;
import java.time.Instant;
public class Leibniz {
// Calculate using the Leibniz formula
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) {
// Number of terms to compute (100 million iterations)
final int n = 100000000;
Instant startTime = Instant.now();
// Compute using the Leibniz formula
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("Approximation of Pi : " + piApprox);
System.out.println("Calculation Error : " + calculationError);
System.out.println("Elapsed Time [msec] : " + elapsed.toMillis());
}
}
To build Java, you need a JDK (Java Development Kit). We will use OpenJDK here. There are various JDKs such as AdoptOpenJDK, Eclipse Temurin, Amazon Corretto, etc., but they are basically the same as OpenJDK.
docker run --rm -v "$PWD":/usr/src/leibniz -w /usr/src/leibniz openjdk javac Leibniz.java
C#
C# is a language developed by Microsoft as a rival to Java. With the introduction of .NET Core, it can now run on Linux.
C# source code and execution command
using System;
class Program
{
// Calculate using the Leibniz formula
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()
{
// Number of terms to compute (100 million iterations)
const int n = 100000000;
var sw = new System.Diagnostics.Stopwatch();
sw.Start();
// Compute using the Leibniz formula
double piApprox = CalculateLeibnizFormula(n);
double calculationError = Math.PI - piApprox;
sw.Stop();
Console.WriteLine("Approximation of Pi : {0}", piApprox);
Console.WriteLine("Calculation Error : {0}", calculationError);
Console.WriteLine("Elapsed Time [msec] : {0}", sw.ElapsedMilliseconds);
}
}
Here we use Mono, an open Linux implementation of the .NET Framework.
docker run --rm -v "$PWD":/usr/src/leibniz -w /usr/src/leibniz mono mcs leibniz.cs
./leibniz.exe
Python
Python has become popular with the advent of AI. It is an all-purpose language.
Python source code and execution command
import math
import time
# Calculate using the Leibniz formula
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
# Number of terms to compute (100 million iterations)
n = 100000000
start_time = time.monotonic()
# Compute using the Leibniz formula
pi_approx = calculate_leibniz_formula(n)
calculation_error = math.pi - pi_approx
end_time = time.monotonic()
elapsed_millisec = (end_time - start_time) * 1000
print("Approximation of Pi : ", pi_approx)
print("Calculation Error : ", calculation_error)
print("Elapsed Time [msec] : ", elapsed_millisec)
The official Docker Image has python.
docker run -it --rm --name python-leibniz -v "$PWD":/usr/src/leibniz -w /usr/src/leibniz python python leibniz.py
JavaScript
JavaScript is essential for the web front-end, but with the advent of Node.js it can now also be used for the back-end.
JavaScript source code and execution command
// Calculate using the Leibniz formula
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;
}
// Number of terms to compute (100 million iterations)
const n = 100000000;
startTime = Date.now();
// Compute using the Leibniz formula
piApprox = calculateLeibnizFormula(n);
calculationError = Math.abs(Math.PI - piApprox);
endTime = Date.now();
elapsedMillisec = (endTime - startTime);
console.log(`Approximation of Pi : ${piApprox}`);
console.log(`Calculation Error : ${calculationError}`);
console.log(`Elapsed Time [msec] : ${elapsedMillisec}`);
We will use the official DockerImage node. Therefore, it will be executed on the server side. However, the engine used in Node.js is V8, the same as the Chrome browser, so the speed will not change significantly even if you run it in a browser.
docker run -it --rm --name js-leibniz -v "$PWD":/usr/src/leibniz -w /usr/src/leibniz node node leibniz.js
PHP
PHP is the scripting language that powers the web. How fast is it?
PHP source code and execution command
<?php
// Calculate using the Leibniz formula
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;
}
// Number of terms to compute (100 million iterations)
$n = 100000000;
$startTime = microtime(true);
// Compute using the Leibniz formula
$piApprox = calculateLeibnizFormula($n);
$calculationError = pi()-$piApprox;
$endTime = microtime(true);
$elapsedMillsec = ($endTime - $startTime);
echo "Approximation of Pi : {$piApprox}";
echo "Calculation Error : {$calculationError}";
echo "Elapsed Time [msec] : {$elapsedMillsec}";
?>
We will use the official Docker Image of php.
docker run -it --rm --name php-leibniz -v "$PWD":/usr/src/leibniz -w /usr/src/leibniz php php leibniz.php
Ruby
Ruby is a scripting language that has supported the web. How does it differ from PHP?
Ruby source code and execution command
# Calculate using the Leibniz formula
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'
# Number of terms to compute (100 million iterations)
n = 100000000
pi_approx = 0
elapsed_sec = Benchmark.realtime do
# Compute using the Leibniz formula
pi_approx = calculate_leibniz_formula(n)
end
calculation_error = Math::PI - pi_approx
elapsed_millisec = elapsed_sec * 1000
puts "Approximation of Pi : #{pi_approx}"
puts "Calculation Error : #{calculation_error}"
puts "Elapsed Time [msec] : #{elapsed_millisec}"
We will use the official Docker Image, ruby.
docker run -it --rm --name ruby-leibniz -v "$PWD":/usr/src/leibniz -w /usr/src/leibniz ruby ruby leibniz.rb
Swift
Swift is a language primarily used in developing iOS applications for iPhones and iPads.
Swift source code and execution command
import Foundation
// Calculate using the Leibniz formula
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
}
// Number of terms to compute (100 million iterations)
let n = 100000000
let startTime = Date()
// Compute using the Leibniz formula
let piApprox = calculateLeibnizFormula(n: n)
let calculationError = Double.pi - piApprox
let elapsedMillisec = Date().timeIntervalSince(startTime) * 1000
print("Approximation of Pi : \(piApprox)");
print("Calculation Error : \(calculationError)");
print("Elapsed Time [msec] : \(elapsedMillisec)");
Use the official Docker Image, swift. Add the -O option for optimization .
docker run --rm -v "$PWD":/usr/src/leibniz -w /usr/src/leibniz swift swift -O leibniz.swift
Kotlin
Kotlin, which has its origins in Java, is the recommended development language for Android apps.
Kotlin source code and execution command
// Calculate using the Leibniz formula
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>) {
// Number of terms to compute (100 million iterations)
val n = 100000000;
val startTime = System.currentTimeMillis()
// Compute using the Leibniz formula
val piApprox = calculateLeibnizFormula(n)
val calculationError = Math.abs(Math.PI - piApprox)
val endTime = System.currentTimeMillis()
val elapsedMillisec = endTime - startTime
println("Approximation of Pi : $piApprox");
println("Calculation Error : $calculationError");
println("Elapsed Time [msec] : $elapsedMillisec");
}
We will use the official Docker Image, 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
Go is a language developed by Google, aimed at large-scale applications.
Go source code and execution command
package main
import (
"fmt"
"math"
"time"
)
// Calculate using the Leibniz formula
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() {
// Number of terms to compute (100 million iterations)
n := 100000000
startTime := time.Now()
// Compute using the Leibniz formula
piApprox := calculateLeibnizFormula(n)
calculationError := math.Pi - piApprox
endTime := time.Now()
elapsed := endTime.Sub(startTime)
fmt.Printf("Approximation of Pi : %v\n", piApprox)
fmt.Printf("Calculation Error : %v\n", calculationError)
fmt.Printf("Elapsed Time [msec] : %v\n", elapsed.Milliseconds())
}
We will use the official Docker Image, golang. Go is optimized by default when compiling.
docker run --rm -v "$PWD":/usr/src/leibniz -w /usr/src/leibniz golang go run leibniz.go
Rust
Rust aims to have the performance of C++ while being safe to use. It also supports Linux kernel development.
Rust source code and execution command
use std::time::Instant;
// Calculate using the Leibniz formula
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() {
// Number of terms to compute (100 million iterations)
let n = 100000000;
let start_time = Instant::now();
// Compute using the Leibniz formula
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!("Approximation of Pi : {}", pi_approx);
println!("Calculation Error : {}", calculation_error);
println!("Elapsed Time [msec] : {}", elapsed.as_millis());
}
We will use the official Docker Image, rust. Note that this assumes that the project has been created with cargo.
It is optimized with the –release option.
docker run --rm -v "$PWD":/usr/src/leibniz -w /usr/src/leibniz rust cargo run --release
Processing speed measurement results
The processing time for calculating pi in all 11 languages is as follows:
Language | Method | Processing time [ms] |
---|---|---|
C++ | Compiler | 102 |
Java | Compiler | 104 |
C# | Compiler | 102 |
Python | Interpreter | 11193 |
JavaScript | Interpreter | 117 |
PHP | Interpreter | 2548 |
Ruby | Interpreter | 10168 |
Swift | Compiler | 102 |
Kotlin | Compiler | 106 |
Go | Compiler | 107 |
Rust | Compiler | 103 |
Compiled languages are fast. Interpreted languages are slow. It’s pretty self-explanatory.
Three points to consider when reviewing processing speed
It is true that different programming languages have different processing speeds.
However, when it comes to processing speed, there are more important things to consider than the choice of programming language.
- Processing speed is affected more by I/O bound tasks
- Libraries can sometimes improve performance
- You can also combine programming languages
Beware of I/O-bound tasks
Computer processing that takes a long time can be divided into two categories:
- CPU-bound tasks : Processing that affect the CPU’s capabilities, such as calculation processes.
- I/O bound tasks : Processing that affects the ability to input and output, such as reading and writing files, network communications, etc.
Of these, only CPU-bound tasks make a difference depending on the programming language.
.
I/O bound tasks are hardly affected by programming language differences. The factors that affect them are:
- OS (Windows, Mac, Linux, etc.)
- Hardware (disk read/write speed, etc.)
- Infrastructure (network speed, etc.)
And in many cases, speed issues are caused by I/O bound tasks.
To address performance issues, it is often best to consider improving the performance of I/O-bound tasks (reducing the size of input and output data, processing in the background) or parallel/concurrent processing.
Use the library
Every programming language has libraries available for different purposes.
In many cases, processing speed can be improved simply by using a library.
For example, Python. NumPy is a famous library. Let’s measure the processing using NumPy.
import numpy as np
# Leibniz formula calculation (using 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
Language | Processing time [ms] |
---|---|
Python | 11193 |
Python (using NumPy) | 5704 |
By using the library, the processing time was cut in half.
Python in particular is a language that really shines when you use libraries , so be sure to make use of them.
Consider programming language combinations
If processing speed is important, you could consider using a different programming language only for the parts where speed is particularly important.
We mentioned NumPy earlier. It is a Python library, but it is actually implemented in C.
- The main processing is done by Python
- Create a Python library in C++ for only time-consuming processes
This is a commonly used method.
For a long time, the web front end was only capable of using JavaScript.
In recent years, a technology called WebAssembly has emerged.
It is possible to achieve processing speeds close to those of compiler-based systems, and development can be done in languages such as C, C++, and Rust .
- Mainly JavaScript
- Implemented WebAssembly in Rust for only time-consuming processes
Combinations such as the following are also possible.
Be careful when using real-time processing
I said that processing speed is not the deciding factor in language choice.
However, real-time processing is a different story.
Real-time processing is processing in which delays cannot be tolerated. Examples include the following:
- Robot Control
- Online games
- OS kernel
These processes must always operate at regular intervals and must not be delayed. Delays are not on the level of seconds, but on the level of milliseconds (one thousandth of a second) or microseconds (one millionth of a second).
At this level, you would obviously use a compiled language, but what you need to be even more careful about is
the garbage collection mechanism that the programming language has.
Garbage collection is a mechanism that automatically frees unused memory in the background. Java, C#, Swift, Kotlin, Go, etc. have it. The advantage is that you don’t have to worry about memory freeing, but you don’t know when it will run.
When this happens in the background, it can affect real-time performance.
Therefore, languages such as C, C++, and Rust, which do not have garbage collection, are mainly used
for real-time processing .
C++ has the “Zero Overhead Principle” and Rust has the “Zero Cost Abstraction” concept. Both languages are designed with the goal of eliminating unnecessary CPU costs , such as garbage collection, when they are executed .
Summary
We have compared the processing speed of a total of 11 programming languages.
As mentioned above, processing speed is rarely the deciding factor when choosing a programming language.
However, knowing the processing speed characteristics of each programming language is effective in improving performance. Let’s understand the essence behind it.
Comments