Rust is the fastest, Miiao took measurements

Spoiler: Rust is faster and generally makes sense. (reference to article series by @humbug)

Joking aside, but we did not come here for this (I speak for myself). Not long ago, a friend of mine asked me to write FizzBuzz in Rust. It would seem, what is the problem? So the problem is not in what, but in whom – in me. I got a version that is both terribly concise and terribly verbose, and at the same time I decided to write approximately identical (in terms of logic, not appearance) implementations in several other languages, and then measure their performance on average hardware (Windows, 3.5 GHz, 4 GB of RAM) thought of it in order to understand the possibility of practical application of such solutions. In general, more to the point.

NOTE: There were five runs, but only the shortest and longest times, and the average proportional of all five runs, are presented.

General task

The task is extremely simple – to implement the function fb, which will return “Fizz”, “Buzz”, “FizzBuzz”, or “Other(x)”, where x is a thirty-two-bit signed argument. Display execution results fb for numbers in the sequence from 1 to 100000. In some pseudocode, it can be described as:

func fb(x: int): String {
    var fizz = x % 3 == 0
    var buzz = x % 5 == 0
    if fizz and not buzz
        return "Fizz\n"
    else if not fizz and buzz
        return "Buzz\n"
    else if fizz and buzz
        return "FizzBuzz\n"
    else
        return "Other(${x})\n"
}

entry {
    var start = Time.now()
    for i = 1 to 100000
        print(fb(i))
    var elapsed = Time.since(start).as_milliseconds() / 1000.0
    print("100000 iterations in ${elapsed} seconds\n")
}

C

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

char* FB[] = {"", "Buzz", "Fizz", "FizzBuzz"};

char* fb(char *s, int x) {
    bool fizz = x % 3 == 0;
    bool buzz = x % 5 == 0;
    if (!fizz && !buzz) {
        sprintf(s, "Other(%d)", x);
    } else {
        strcpy(s, FB[fizz << 1 | buzz]);
    }
    return s;
}

main() {
    clock_t start = clock();
    char s[15] = {0};
    for (int i = 1; i <= 100000; i++) {
        puts(fb(s, i));
    }
    printf("100000 iterations in %f seconds\n", (float) (clock() - start) / CLOCKS_PER_SEC);
}

The code is transparent as the tears of the baby that wrote it. It differs little from the original one, but is interesting in that instead of a trivial approach associated with a simple enumeration of options, we use bit shifts and or to select the desired string from an already prepared buffer. Many thanks to @rgimad for helping with optimizations.

Compilation Command

gcc fbc.c -Ofast

results

Least, s.

The largest, p.

Average, p.

Size, KB

Length, lines

6.687

17.928

9.627

90

27

C++

#include <iostream>
#include <chrono>
#include <string>
#include <format>
#include <ranges>

auto fb(int x) -> std::string {
    bool fizz = x % 3 == 0;
    bool buzz = x % 5 == 0;
    if (fizz && !buzz) {
        return "Fizz";
    } else if (!fizz && buzz) {
        return "Buzz";
    } else if (fizz && buzz) {
        return "FizzBuzz";
    } else {
        return std::format("Other({})", x);
    }
}

auto main() -> int {
    std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
    for (int i : std::ranges::views::iota(1, 100001)) {
        std::cout << fb(i) << std::endl;
    }
    std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
    std::cout << std::format(
        "100000 iterations in {} seconds",
        (double) std::chrono::duration_cast<std::chrono::milliseconds>(end - start)
            .count() / 1000.0) << std::endl;
}

Many plus people blame me for the fact that my plus code is actually C code, so this time I decided to present the most modern solution using C++20 features.

Compilation command

g++ fbcpp.cpp -Ofast --std=c++20

results

Least, s.

The largest, p.

Average, p.

Size, KB

Length, lines

18.179

27.418

21.606

16310

31

D

import std.format: format;
import std.stdio;
import std.range : iota;
import std.algorithm;
import std.datetime : MonoTime;

string fb (int x) {
    bool fizz = x % 3 == 0;
    bool buzz = x % 5 == 0;
    if (fizz && !buzz) {
        return "Fizz";
    } else if (!fizz && buzz) {
        return "Buzz";
    } else if (fizz && buzz) {
        return "FizzBuzz";
    } else {
        return format("Other(%d)", x);
    }
}

void main() {
    auto start = MonoTime.currTime();
    iota(1, 100001)
      .map!(fb)
      .each!(writeln);
    writefln("100000 iterations in %f seconds", (MonoTime.currTime() - start).total!"usecs"() / 1000000.0);
}

The di solution looks like a perfect version of the plus solution. I really like it, writing gave me great pleasure.

Compilation command

dmd fbd.d -O -release

results

Least, s.

The largest, p.

Average, p.

Size, KB

Length, lines

8.034

18.79

10.043

730

27

Fortran

subroutine fb(i)
    integer, intent(in) :: i
    logical :: fizz, buzz
    fizz = mod(i, 3) == 0
    buzz = mod(i, 5) == 0
    if (fizz .and. .not. buzz) then
        write(*, '(a)') "Fizz"
    else if (.not. fizz .and. buzz) then
        write(*, '(a)') "Buzz"
    else if (fizz .and. buzz) then
        write(*, '(a)') "FizzBuzz"
    else
        write(*, '(a, i5, a)') "Other(", i, ")"
    end if
end subroutine fb

program main
    integer(kind = 4) :: i, start, end
    real(kind = 4) :: elapsed
    call SYSTEM_CLOCK(start)
    do i = 1, 100000
        call fb(i)
    end do
    call SYSTEM_CLOCK(end)
    elapsed = end - start
    write(*, '(a, f7.4, a)') "100000 iterations in ", elapsed / 1000.0, " seconds"
end program main

Here comes the old Fortran man! Frankly, writing code on this middle-aged handsome man was not the easiest, but rather pleasant and interesting process.

Compilation command

gfortran fbf.f08 -fimplicit-none -Ofast

results

Least, s.

The largest, p.

Average, p.

Size, KB

Length, lines

4.531

13.438

6.256

1781

27

Rust

#[derive(Debug)]
#[repr(u16)]
enum FB {
    Fizz = 1,
    Buzz = 256,
    FizzBuzz = 257,
    Other(i32) = 0,
}

fn fb(x: i32) -> FB {
    unsafe {core::mem::transmute((x % 3 == 0, x % 5 == 0, x))}
}

fn main() {
    let start = std::time::Instant::now();
    (1..=100000)
        .map(fb)
        .for_each(|x| println!("{x:?}"));
    println!("100000 iterations in {} seconds", start.elapsed().as_millis() as f64 / 1000.);
}

How so! Almost forgot about the hero of the occasion! The code is very doubtful, because there are only two lines of logic (1, 11), and how many boilerplates… line 11, line 1 does the formatting.

Compilation command

cargo build -r

results

Least, s.

The largest, p.

Average, p.

Size, KB

Length, lines

6.815

19.751

9.878

186

20

Swift

func fb(x: Int) -> String {
    switch (x % 3 == 0, x % 5 == 0) {
        case (true, false): return "Fizz"
        case (false, true): return "Buzz"
        case (true, true): return "FizzBuzz"
        default: return "Other(\(x))"
    }
}

let elapsed = ContinuousClock().measure {
    (1...100000)
        .map(fb)
        .forEach({(i) -> Void in print(i)})
}

print("100000 iterations in \(elapsed) seconds")

Swift decided to visit his older brother, and at the same time compete with him. The solution is incredibly elegant and beautiful, bravo.

Compilation command

swiftc fbswift.swift -O -static

results

Least, s.

The largest, p.

Average, p.

Size, KB

Length, lines

8.073

26.898

10,898

22

16

go

package main

import (
	"fmt"
	"time"
)

func fb(x int) string {
	fizz := x % 3 == 0
	buzz := x % 5 == 0
	if fizz && !buzz {
		return "Fizz"
	} else if !fizz && buzz {
		return "Buzz"
	} else if fizz && buzz {
		return "FizzBuzz"
	} else {
		return fmt.Sprint("Other(", x, ")")
	}
}

func main() {
	start := time.Now();
	for i := 1; i <= 100000; i++ {
		fmt.Println(fb(i))
	}
	elapsed := time.Since(start);
	fmt.Printf("100000 iterations in %f seconds",
		float64(elapsed.Nanoseconds()) / 1000000000.0)
}

Despite all my dislike for Go, I can’t say that it was unpleasant for me to write this code, and even vice versa.

Compilation Command

go build fbgo.go

results

Least, s.

The largest, p.

Average, p.

Size, KB

Length, lines

5.912

16.001

11.636

1910

thirty

PascalABC.NET

function fb(x: integer): string;
begin
  var (fizz, buzz) := (x mod 3 = 0, x mod 5 = 0);
  if fizz and not buzz then
    result := 'Fizz'
  else if not fizz and buzz then
    result := 'Buzz'
  else if fizz and buzz then
    result := 'FizzBuzz'
  else
    result := string.format('Other({0})', x);
end;

begin
  milliseconds();
  range(1, 100000)
      .select(fb)
      .println(char(10));
  writelnformat('100000 iterations in {0} seconds', millisecondsdelta() / 1000.0);
end.

PascalABC.NET is a unique language. It combines the features of imperative, object-oriented and functional programming in proportions that you would not expect from such a language.

Compilation Command

pabcnetc fbpas.pas

results

Least, s.

The largest, p.

Average, p.

Size, KB

Length, lines

8.282

19.491

10.011

38

20

Dart

String fb(int x) {
    bool fizz = x % 3 == 0;
    bool buzz = x % 5 == 0;
    if (fizz && !buzz)
        return "Fizz";
    else if (!fizz && buzz)
        return "Buzz";
    else if (fizz && buzz)
        return "FizzBuzz";
    else
        return "Other($x)";
}

void main() {
    final start = Stopwatch()..start();
    for (int i = 0; i <= 100000; i++)
        print(fb(i));
    print("100000 iterations in ${start.elapsedMilliseconds / 1000.0} seconds");
}

Dart is a wonderful scripting language, I immediately liked it, it’s not for nothing that the most popular GUI framework – Flutter – is designed specifically for it.

Compilation Command

dart compile exe fbdart.dart

results

Least, s.

The largest, p.

Average, p.

Size, KB

Length, lines

9.013

28.216

13.132

4837

19

PHP

<?php
function fb($x) {
    $fizz = $x % 3 == 0;
    $buzz = $x % 5 == 0;
    if ($fizz && !$buzz) {
        return "Fizz\n";
    } else if (!$fizz && $buzz) {
        return "Buzz\n";
    } else if ($fizz && $buzz) {
        return "FizzBuzz\n";
    } else {
        return sprintf("Other(%d)\n", $x);
    }
}

$start = hrtime(true);
foreach (range(1, 100000) as $i) {
    echo fb($i);
}
echo sprintf("100000 iterations in %f seconds\n", (hrtime(true) - $start) / 1e+9);
?>

Puff puff puff…

Run command (compilation is not supported)

php -f fbphp.php

results

Least, s.

The largest, p.

Average, p.

Size, KB

Length, lines

5.497

26.465

11.459

85052 (PHP8)

19

Kotlin

inline fun fb(x: Int): String {
    val fizz = x % 3 == 0
    val buzz = x % 5 == 0
    if (fizz && !buzz)
        return "Fizz"
    else if (!fizz && buzz)
        return "Buzz"
    else if (fizz && buzz)
        return "FizzBuzz"
    else
        return "Other($x)"
}

fun main() {
    val elapsed = kotlin.system.measureTimeMillis {
        for (i in 1..100000)
            println(fb(i))
    }.toDouble() / 1000.0
    println("100000 iterations in $elapsed seconds")
}

Eh, Kotlin… love at first sight… we were so young, and all this passion…

Compilation command

kotlinc-native -opt fbkt.kt

results

Least, s.

The largest, p.

Average, p.

Size, KB

Length, lines

13.666

16.69

15.302

540

20

Java

import java.util.stream.IntStream;

class Main {
    public static void main(String[] args) {
        FB fb = new FB();
        double start = (double) System.currentTimeMillis();
        IntStream range = IntStream.range(1, 100001);
        range.forEach(x -> fb.fb(x));
        double end = (double) System.currentTimeMillis();
        double elapsed = (end - start) / 1000.0;
        System.out.println(String.format("100000 iterations in %f seconds", elapsed));
    }
}

class FB {
    public static void fb(int x) {
        FB fizzbuzz = new FB();
        System.out.println(fizzbuzz.inner(x));
    }

    public static String inner(int x) {
        boolean fizz = x % 3 == 0;
        boolean buzz = x % 5 == 0;
        if (fizz && !buzz) {
            return "Fizz";
        } else if (!fizz && buzz) {
            return "Buzz";
        } else if (fizz && buzz) {
            return "FizzBuzz";
        } else {
            return String.format("Other(%d)", x);
        }
    }
}

Where without the prodigal mother Kotlin? Well, let’s see it in action…

Run command (compilation is not supported)

javac fbjava.java
java Main

results

Least, s.

The largest, p.

Average, p.

Size, KB

Length, lines

8.579

36.771

23.131

305767 (JRE11)

20

Python

from time import time

def fb(x: int) -> str:
    fizz: bool = x % 3 == 0
    buzz: bool = x % 5 == 0
    if fizz and not buzz:
        return 'Fizz'
    elif buzz and not fizz:
        return 'Buzz'
    elif fizz and buzz:
        return 'FizzBuzz'
    else:
        return f'Other({x})'

start = time()

for i in range(1, 100001):
    print(fb(i))

print(f'100000 iterations in {time() - start} seconds')

Eh, python, python, what has life brought me to? Using you in benchmarks…

Run command (compilation is not supported)

py -m fbpy

results

Least, s.

The largest, p.

Average, p.

Size, KB

Length, lines

6.891

24.966

15.979

287616 (CPython311)

20

Conclusion and rating

Performance

Place

Language

Average time, s.

1

Fortran

6.256

2

C and Rust

9.627 and 9.878

3

PascalABC.NET, D and Swift

10.011, 10.043 and 10.898

4

PHP and Go

11.459 and 11.636

5

Dart

13.132

6

Kotlin and Python

15.302 and 15.979

7

C++

21.606

8

Java

23.131

Binary size

Place

Language

Size, KB

1

Swift

22

2

PascalABC.NET

38

3

C

90

4

Rust

186

5

Kotlin

540

6

D

730

7

Fortran

1781

8

go

1910

9

Dart

4837

10

C++

16310

eleven

PHP (PHP8)

85052

12

Python (CPython311)

287616

13

Java (JRE11)

305767

Conciseness (not only lines are taken into account)

Place

Language

Strings

1

Swift

16

2

Dart

19

3

Kotlin

20

4

Python

20

5

Rust

20

6

PascalABC.NET

20

7

D

27

8

go

thirty

9

C

27

10

Fortran and C++

27 and 31

eleven

Java

34

Convenience of formatting (not only characters are taken into account)

Place

Language

Add. symbols

1

Rust

0

2

Kotlin and Dart

1

3

Python and Swift

3

4

D and PHP

12

5

go

16

6

C++

17

7

Java

19

8

PascalABC.NET

20

9

C

24

10

Fortran

27

Some results surprised me, others I predicted. Most of all, I was surprised by such a low position of pluses. Banter banter, and now I’m even somehow ashamed.

Let’s summarize:

  • Rust is the best;

  • PascalABC.NET knows how to surprise;

  • Python is concise but slow;

  • C gives a lot of scope to the imagination;

  • Dart, although scripted, is very fast and beautiful;

  • Kotlin fails a little, but that doesn’t stop it from being great;

  • D is on top, as always;

  • C ++ let us down … well, nothing, recovers, Corpsestraus said);

  • Go – unpretentious, but smart;

  • Fortran – grandfather, although he does not understand youth aesthetics, can still set the heat on even the fastest of modern languages;

  • Java is no longer the same;

  • PHP is the sonic of the scripting world;

  • Swift is loved by developers for a reason, Chris Lattner and Graydon Hore did a great job on it.

    Please remember that benchmarks are subjective, do not take everything to heart, the primary purpose of creating this content is entertainment.

    miiao.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *