Rust быстрее всех, Miiao сделал замеры

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Спойлер: Rust быстрее и вообще смысл в этом. (отсылка на серию статей от @humbug)

Шутки шутками, но пришли мы сюда не за этим (говорю за себя). Не так давно один мой знакомый попросил меня написать FizzBuzz на Rust. Казалось бы, в чём проблема? Так вот проблема не в чём, а в ком - во мне. Получил я версию одновременно жутко лаконичную и ужасно вербозную, а заодно решил написать примерно идентичные (в плане работы логики, а не внешнего вида) реализации на нескольких других языках, а потом и измерить их производительность на среднем железе (Windows, 3,5 ГГц, 4 ГБ ОЗУ) додумался, дабы разобраться в возможности практического применения подобных решений. Вообщем, ближе к делу.

NOTE: Проводилось по пять запусков, но представлены только наименьшее и наибольшее время, а также среднее пропорциональное для всех пяти запусков.

Общая задача

Задача предельно проста - реализовать функцию fb, которая будет возвращать "Fizz", "Buzz", "FizzBuzz" или "Other(x)", где x - тридцатидвухбитный знаковый аргумент. Вывести результаты выполнения fb для чисел в последовательности с 1 по 100000. На некотором псевдокоде можно описать как:

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);
}

Код прозрачен как слёзы младенца, что его писал. Мало отличается от исходного, но интересен тем, что вместо тривиального подхода, связанного с простым перебором вариантов, мы используем битовые сдвиг и или для выбора нужной строки из уже готового буффера. Большое спасибо@rgimadза помощь с оптимизациями.

Команда компиляции

gcc fbc.c -Ofast

Результаты

Наименьшее, с.

Наибольшее, с.

Среднее, с.

Размер, КБайт

Длина, линии

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;
}

Многие плюсовики винят меня в том, что мой код на плюсах - на самом деле код на си, так что на этот раз я решил представить максимально современное решение, использующее фичи С++20.

Команда компиляции

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

Результаты

Наименьшее, с.

Наибольшее, с.

Среднее, с.

Размер, КБайт

Длина, линии

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);
}

Решение на ди выглядит как идеальная версия решения на плюсах. Оно мне невероятно нравится, написание доставило огромное удовольствие.

Команда компиляции

dmd fbd.d -O -release

Результаты

Наименьшее, с.

Наибольшее, с.

Среднее, с.

Размер, КБайт

Длина, линии

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

Вот и старичок-фортран подъехал! Признаться, написание кода на этом немолодом красавце было не самым простым, но довольно приятным и интересным процессом.

Команда компиляции

gfortran fbf.f08 -fimplicit-none -Ofast

Результаты

Наименьшее, с.

Наибольшее, с.

Среднее, с.

Размер, КБайт

Длина, линии

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.);
}

Как же так! Чуть не забыли про виновника торжества! Код весьма сомнителен, ведь логики всего две строки (1, 11), а бойлерплейта-то сколько... Несмотря на то, что внешне это решение значительно отличается от решения на си, алгоритм тут такой же - смещение, только неявное, выполняемое на строке 11, на строке 1 же выполняется форматирование.

Команда компиляции

cargo build -r

Результаты

Наименьшее, с.

Наибольшее, с.

Среднее, с.

Размер, КБайт

Длина, линии

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")

Свифт решил навестить своего старшего брата, а заодно и посоревноваться с ним. Решение невероятно элегантно и красиво, браво.

Команда компиляции

swiftc fbswift.swift -O -static

Результаты

Наименьшее, с.

Наибольшее, с.

Среднее, с.

Размер, КБайт

Длина, линии

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)
}

Несмотря на всю мою неприязнь к Go, не могу сказать, что мне было неприятно писать этот код, и даже наоборот.

Команда компиляции

go build fbgo.go

Результаты

Наименьшее, с.

Наибольшее, с.

Среднее, с.

Размер, КБайт

Длина, линии

5,912

16,001

11,636

1910

30

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 - уникальный язык. Он сочетает в себе черты императивного, объектно-ориентированного и функционального программирования в тех пропорциях, которых от подобного языка не ждёшь.

Команда компиляции

pabcnetc fbpas.pas

Результаты

Наименьшее, с.

Наибольшее, с.

Среднее, с.

Размер, КБайт

Длина, линии

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 - замечательный скриптовой язык, мне он сразу понравился, не зря самый популярный GUI-фреймворк - Flutter - предназначен именно для него.

Команда компиляции

dart compile exe fbdart.dart

Результаты

Наименьшее, с.

Наибольшее, с.

Среднее, с.

Размер, КБайт

Длина, линии

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);
?>

Пых-пых-пых...

Команда запуска (компиляция не поддерживается)

php -f fbphp.php

Результаты

Наименьшее, с.

Наибольшее, с.

Среднее, с.

Размер, КБайт

Длина, линии

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")
}

Эх, Котлин... любовь с первого взгляда... мы были так молоды, а вся эта страсть...

Команда компиляции

kotlinc-native -opt fbkt.kt

Результаты

Наименьшее, с.

Наибольшее, с.

Среднее, с.

Размер, КБайт

Длина, линии

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);
        }
    }
}

Куда уж без блудной матери Котлина? Что же, поглядим на неё в деле...

Команда запуска (компиляция не поддерживается)

javac fbjava.java
java Main

Результаты

Наименьшее, с.

Наибольшее, с.

Среднее, с.

Размер, КБайт

Длина, линии

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')

Эх, питончик, питончик, до чего меня довела жизнь? Использую тебя в бенчмарках...

Команда запуска (компиляция не поддерживается)

py -m fbpy

Результаты

Наименьшее, с.

Наибольшее, с.

Среднее, с.

Размер, КБайт

Длина, линии

6,891

24,966

15,979

287616 (CPython311)

20

Заключение и рейтинг

Производительность

Место

Язык

Среднее время, с.

1

Fortran

6,256

2

C и Rust

9,627 и 9,878

3

PascalABC.NET, D и Swift

10,011, 10,043 и 10,898

4

PHP и Go

11,459 и 11,636

5

Dart

13,132

6

Kotlin и Python

15,302 и 15,979

7

C++

21,606

8

Java

23,131

Размер бинаря

Место

Язык

Размер, КБайт

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

11

PHP (PHP8)

85052

12

Python (CPython311)

287616

13

Java (JRE11)

305767

Лаконичность (учитываются не только строки)

Место

Язык

Строки

1

Swift

16

2

Dart

19

3

Kotlin

20

4

Python

20

5

Rust

20

6

PascalABC.NET

20

7

D

27

8

Go

30

9

C

27

10

Fortran и C++

27 и 31

11

Java

34

Удобство форматирования (учитываются не только символы)

Место

Язык

Доп. символы

1

Rust

0

2

Kotlin и Dart

1

3

Python и Swift

3

4

D и PHP

12

5

Go

16

6

C++

17

7

Java

19

8

PascalABC.NET

20

9

C

24

10

Fortran

27

Некоторые результаты меня удивили, другие же я предугадал. Более всего меня удивила столь низкая позиция плюсов. Стёб стёбом, а мне теперь даже как-то стыдно.

Подведём итоги:

  • Rust лучше всех;

  • PascalABC.NET умеет удивлять;

  • Python лаконичный, но медленный;

  • C даёт много простора воображению;

  • Dart, хоть и скриптовой, очень быстр и красив;

  • Kotlin немного подводит, но от того не перестаёт быть прекрасным;

  • D на высоте, как и всегда;

  • C++ подкачал... ну ничего, оклемается, Трупостраус же сказал);

  • Go - простенький, но шустренький;

  • Fortran - дед, хоть не разбирается в молодёжной эстетике, всё же может задать жару даже самым быстрым из современных языков;

  • Java уже не та;

  • PHP - соник скриптового мира;

  • Swift не зря так любим разработчиками, Крис Латтнер и Грейдон Хор отлично над ним поработали.

    Прошу помнить, что бенчмарки субъективны, не стоит принимать всё близко к сердцу, первоочередная цель создания этого контента - развлечение.

    Miiao.

Источник: https://habr.com/ru/post/718186/


Интересные статьи

Интересные статьи

Вангеры в 2022 году продолжают удивлять, прежде всего людьми которые когда-то полюбили эту игру, но сегодня не забывают и вкладывают много сил, делая её лучше, современнее. Оригинальная игра получила ...
Привет, Хабр!Меня зовут Дмитрий Матлах. Я тимлид в AGIMA. Мы с коллегами обратили внимание, что в сообществе часто возникает вопрос о том, как совместить на одном проекте Bitrix-компоненты и реактивны...
Однажды мне потребовалось забирать регулярно относительно большие объемы данных в MS SQL из PostgreSQL. Неожиданно выяснилось, что самый очевидный способ, через Linked Se...
Появившиеся в 2006 году сервисы Google по работе с текстовыми документами (Google Docs) и таблицами (Google Sheets), дополненные 6 лет спустя возможностями работы с вирту...
Данная статья в большей степени автобиографическая, душевный пересказ интересного периода жизни. Выражаясь фигурально, можно назвать это одой Qt. Надеюсь, что статья будет интересна в...