[11 languages] Considering the “Processing Speed” of Programming Languages

processing-speed-of-program

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?

TOC

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.

Compare 11 languages
  1. C++
  2. Java
  3. C#
  4. Python
  5. JavaScript
  6. PHP
  7. Ruby
  8. Swift
  9. Kotlin
  10. Go
  11. 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:

Machine Language
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:

When the program runs
  • 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
Interpreter Method

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
Compiler method

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.

OSUbuntu22.04
(WSL2 on Windows11)
CPURyzen5
Development environmentGet each image from DockerHub
(latest version as of November 2023)
Development and execution environment

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++

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

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:

LanguageMethodProcessing time [ms]
C++Compiler102
JavaCompiler104
C#Compiler102
PythonInterpreter11193
JavaScriptInterpreter117
PHPInterpreter2548
RubyInterpreter10168
SwiftCompiler102
KotlinCompiler106
GoCompiler107
RustCompiler103
Processing speed measurement results

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:

Computer processing division
  • 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
LanguageProcessing time [ms]
Python11193
Python (using NumPy)5704
Python measurement results

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.

Building Python libraries in C++

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.

Building part of JavaScript with WebAssembly

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:

Real-time processing example
  • 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.

Let's share this post !

Comments

To comment

CAPTCHA


TOC