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

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

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

Общая задача

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

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

Как же так! Чуть не забыли про виновника торжества! Код весьма сомнителен, ведь логики всего две строки (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)

34

Python

from time import time

def fb(x: int) -> str:
    return "Fizz" * (not x % 3) + "Buzz" * (not x % 5) or f'Other({x})'

start = time()

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

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

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

Спасибо LeeeeT за идиоматичную fb.

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

py -m fbpy

Результаты

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

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

Среднее, с.

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

Длина, линии

6,891

24,966

15,979

287616 (CPython311)

11

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

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

Место

Язык

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

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

Python

11

2

Swift

16

3

Dart

19

4

Kotlin

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.

Комментарии (71)


  1. Starl1ght
    00.00.0000 00:00
    +32

    Господи, это настолько плохо, что даже хорошо.

    В случае С++ вы побенчмаркали скорость флашинга в стандартный вывод. Не говоря, про std::format, который вполне может аллоцировать, чтобы сразу деаллоцировать.


    1. Miiao Автор
      00.00.0000 00:00
      -7

      Что же, кажется, это первый комментарий без агрессии! Брависсимо! Искренне поражаясь такой милости, объясню, что бенчмарк и должен был быть глупым, измерение вывода и форматирования было специальным, и было во всех языках.

      Что же касается плюсов, часть с ними была скорее шуткой, предназначенной для ответа на вопрос «Is modern C++ faster than C?».

      В любом случае, спасибо.


    1. HabraUser666
      00.00.0000 00:00
      +3

      Объясните, пожалуйста, для тупых, что автор вообще пытается делать? Что это за хрень такая упоротая? Причем тут вообще скорость работы языка?


  1. Siemargl
    00.00.0000 00:00
    +24

    Мда, не без осечки, конечно

    1. Автор не различает [по сложности?] возврат объекта, указателя и константы

    2. Классическое тестирование скорости выполнения print

    Но все можно исправить, да и многоязычие надо поощрять.


    1. Miiao Автор
      00.00.0000 00:00
      -4

      Спасибо за доброе слово. Тестирование скорости вывода и было мной задумано, что же касается первого пункта, право, не понимаю о чём вы. Я ведь логику с языка на язык переносил, а не состояния возвратных регистров.

      В любом случае, всегда готов выслушать всякое предложение :)


      1. Siemargl
        00.00.0000 00:00
        +1

        Для понимания стоит заходить на gotbolt. Конструирование объекта на хипе (с последующим вызовом GC), копирование массива или возврат указателя и константы это кардинально разные по затратам операции.


    1. adeshere
      00.00.0000 00:00

      1. Классическое тестирование скорости выполнения print

      Не знаю, как в других языках, но конкретно в фортране львиную долю времени исполнения будет выполняться именно write. В фортране этот оператор содержит немеряное количество опций на все случаи жизни, и он реально тормозной. Я когда вычислениями занимаюсь (где на один write сотни и тысячи арифметических операций с плавающей точкой), то вместо write для записи результатов вручную эмулирую выходной проток с буферизацией по 2-4Кб, чтобы вызывать write пореже и без форматирования. Даже на фоне довольно интенсивных расчетов такая оптимизация очень заметно сказывается на скорости. А в предложенном коде на один write буквально пара тривиальных машинных кодов.

      Возникает такое впечатление, что компилятор фортрана что-то там с оператором write нахимичил. Иначе невозможно объяснить, почему фортрановский write выполняется даже быстрее, чем всяческие cout-ы и их аналоги, которые в разы проще по логике (и на порядки компактнее по машинному коду).


      1. Siemargl
        00.00.0000 00:00
        +1

        да ничего там не проще. с каждой новой итерацией [стандартов] форматированный вывод все сложнее и медленнее


  1. fk0
    00.00.0000 00:00
    +25

    На голом C специально пишут тормозную версию с копированием строк, далее заявляют, что мол Rust быстрей всех. Дальше не читал.


    1. gchebanov
      00.00.0000 00:00
      +16

      Код должен миллисекунды работать, человек померил скорее всего как антивирус в облако заливает бинари, или терминал рендрит юнткод, странно что автора не удивляет разброс с 6 до 20 секунд на трай, и питон обгоняющий плюсы по скорости.


    1. Miiao Автор
      00.00.0000 00:00
      -12

      Modern C++, что ещё сказать…


      1. fk0
        00.00.0000 00:00
        +5

        Даже на Modern C++ строки по-значению копировать не обязательно, можно возвращать указатели на предварительно сформированные строки, что в примерах на других языках делается. Более того, выбор fizbuzz -- вообще достаточно идиотский. Там на любом языке оптимальная программа пишется одинаково и имеет одинаковую производительность. И хуже того, Modern C++ имеет, в отличие от, нормальные constexpr функции и всё такое прочее, чтоб весь fizbuzz расчитать в compile time и вывести путём одного единственного системного вызова write.


        1. JustForFun88
          00.00.0000 00:00
          +1

          Ну в Rust это тоже есть, так что здесь паритет :-)


          1. Kelbon
            00.00.0000 00:00

            Нет, там все очень сложно с constexpr вычислениями


    1. Miiao Автор
      00.00.0000 00:00
      -11

      Глупец ты, fk0, Rust ведь не быстрее C получился ????


  1. AnthonyMikh
    00.00.0000 00:00
    +11

    unsafe {core::mem::transmute((x % 3 == 0, x % 5 == 0, x))}

    У вас UB, раскладка кортежей в памяти не гарантированна.


    1. Miiao Автор
      00.00.0000 00:00
      -9

      Спасибо, знаю, уж очень хотелось извращений


      1. Siemargl
        00.00.0000 00:00
        +3

        Для правильных тонких извращений нужно сначала пресытиться классикой....


  1. DreamingKitten
    00.00.0000 00:00
    +1

    Исключительно ради придраться.

    go build по умолчанию выдаёт бинарник с отладочной информацией. Сделайте ему strip --strip-all и он похудеет до 1200 Kb.


    1. mrobespierre
      00.00.0000 00:00
      +2

      Стрипать Go бинари нельзя, это не поддерживается, работа с heap может сломаться. Вместо этого надо делать no debug build go build -ldflags '-s -w'


      1. BadHandycap
        00.00.0000 00:00
        -1

        Флаг "-s" для компоновщика и есть "стрипание".

        Так можно или нельзя?


        1. mrobespierre
          00.00.0000 00:00

          Это не стрипание, стрипание - отрезание debug инфы утилитой strip, если сделать go build -ldflags '-s -w' она вообще не добавляется.

          Попробуйте сделать обычный билд, стрипнуть его и сравнить с nodebug build который сделает go build, они скорее всего будут отличаться.


          1. BadHandycap
            00.00.0000 00:00
            -3

            Какой же ты душный...


      1. DreamingKitten
        00.00.0000 00:00
        +2

        Стрипать Go бинари нельзя, это не поддерживается, работа с heap может сломаться

        Говорят, что этот баг пофиксили ещё в 2011 году.


        1. mrobespierre
          00.00.0000 00:00
          +2

          Круто, хотелось бы верить. А какой-то пруф есть? Ну там issue resolved или что-то подобное. Не хотелось бы получить неожиданный segfault.


          1. DreamingKitten
            00.00.0000 00:00
            +1

            1. mrobespierre
              00.00.0000 00:00
              +1

              Спасибо. По ссылке есть внятные пруфы только про ldflags и 2016, но благодаря ей я нашёл обсуждение в google groups про strip. Хотя Дэйв Чейни и утверждает, что strip ломает бинари, остальные участники его опровергают. Будем пробовать.


    1. Miiao Автор
      00.00.0000 00:00

      Буду знать, спасибо ;)


  1. lea
    00.00.0000 00:00
    +6

    Размер бинаря
    Язык Размер, КБайт
    C 90
    Kotlin 540
    C++ 16310
    Java (JRE11) 305767

    У меня у одного вызывают сомнения эти цифры?


    1. Miiao Автор
      00.00.0000 00:00
      -1

      У джавы указан размер JRE. Насчёт остальных не врал, можете сами попробовать.


    1. DoHelloWorld
      00.00.0000 00:00
      +2

      Для плюсов можно -fno-exceptions -fno-rtti иначе прилинкует подефолту. В тесте они не нужны. вроде


  1. Ktator
    00.00.0000 00:00
    +5

    Я просто оставлю это здесь: https://habr.com/ru/post/540136/

    PS: PascalABC попробовали, а самый популярный .NET язык (C#) не попробовали...


    1. Miiao Автор
      00.00.0000 00:00
      -6

      Занятная статейка ????

      Не видел смысла во включении шарпа, так как есть джава. Несмотря на все его отличия, синтаксис у них одинаковый во многих аспектах. Думал над включением F#, но меня уже не хватало.


      1. ftc
        00.00.0000 00:00
        +3

        Ну мы ж тут вроде скорость дефолтного рантайма замеряем :-) Так что шарп стоило включить.


  1. Inobelar
    00.00.0000 00:00
    +8

    rust-фанатик детектед. что меряем-то? :D printf? гоу мерять доступы к массивам, там кажись, у раста накладные на bound checking :D


    1. Miiao Автор
      00.00.0000 00:00
      -10

      Измерял специально максимально глупо и неэффективно)

      Что же касается массивов, их, простите, в си нет, там арифметика указателей…


      1. BadHandycap
        00.00.0000 00:00
        +6

        В языке Си нет массивов? Деннис Ритчи уже в курсе?


        1. igor6130
          00.00.0000 00:00
          +1

          Деннис Ритчи умер. :(


          1. BadHandycap
            00.00.0000 00:00
            +1

            Тогда это двойное неуважение со стороны вышестоящего оратора.


  1. mostodont32
    00.00.0000 00:00
    +4

    Ох, сердечко прихватило, когда std::endl увидел в замере производительности...


    1. Miiao Автор
      00.00.0000 00:00

      В Rust тоже такая хрень в println встроена, и ничего, живёт.


      1. Kelbon
        00.00.0000 00:00
        -2

        Нет не встроена, прочитайте что такое flush буфера.

        Хотя вам нужно почитать сначала как делаются бенчмарки


        1. Miiao Автор
          00.00.0000 00:00

          А я вам говорю, что встроена. Вам нужно почитать код стандартной библиотеки.


          1. Kelbon
            00.00.0000 00:00
            -3

            да что вы несёте. Принт в расте вообще реализован в компиляторе, а не стандартной бибилотеке


            1. mayorovp
              00.00.0000 00:00
              +3

              В компиляторе реализован не println!, а format_args!.. После построения структуры Arguments её дальнейшая обработка происходит нормальным библиотечным кодом.


              Полностью перечислить все этапы по которым "гуляют" вызовы функций довольно сложно, но вот основные. Для начала, вызывается метод write_fmt у структуры std::io::Stdout:


              #[stable(feature = "rust1", since = "1.0.0")]
              pub struct Stdout {
                  inner: &'static ReentrantMutex<RefCell<LineWriter<StdoutRaw>>>,
              }

              Само форматирование делается довольно хитро, но в итоге оно всё равно попадает в реализацию трейта Write у Stdout, откуда оно попадает в реализацию трейта Write у StdoutLock, откуда оно попадает в реализацию трейта Write у std::io::buffered::linewriter::LineWriter, откуда оно наконец попадает в реализацию трейта Write у std::io::buffered::linewritershim::LineWriterShim.


              Ну а дальше читаем реализацию этого трейта и видим, что при записи перевода строки делается flush.




              Ну или же можно просто запустить вот этот код и всё увидеть:


              fn main() {
                  print!("aaa\nbbb\nccc");
                  std::process::abort();
              }
              


              1. Kelbon
                00.00.0000 00:00

                вот прямо ваш код и выдает только aaa bbb и теряет ccc
                https://godbolt.org/z/aKndTczb3


                1. mayorovp
                  00.00.0000 00:00
                  +2

                  Ну да, я специально написал его таким образом. Чтобы было очевиднее превращение перевода строки в аналог endl


  1. leonidv
    00.00.0000 00:00
    +17

    На основе комментариев к статье можно сделать статью "Как не надо писать бенчмарки"


  1. Lord_Ahriman
    00.00.0000 00:00
    +9

    У вас случайно или намеренно написан неэффективный код на Си, плюс измерена скорость непонятно чего (работа со стандартным выводом и print показателем эффективности языка не является). Плюс по мелочи - вы путаете указатели с объектами и т.п. Делать какие-либо выводы из этого нельзя, статья, фактически, представляет собой классический пример confirmation bias.


  1. LivEEvil
    00.00.0000 00:00
    +7

    Что бы показать какая Java "медленная" будем тестировать ее без C2 компиляций и наворотим специально всякого мусора.

    зачем на каждый вызов создавать новые объекты?

    FB fizzbuzz = new FB();

    зачем использовать стримы, которые создают еще кучу объектов? Хотя в том же котлине простой for. Что бы потом удивляться почему же так медленно?

    Как же это плохо....


    1. Miiao Автор
      00.00.0000 00:00
      -16

      А чего вы меня спрашиваете о том, зачем использовать объекты? Не я, дорогой, ЁЁПе придумал…


      1. ashalaenko
        00.00.0000 00:00
        +1

        Тут вопрос зачем вы на каждый вызов метода создаете инстанс класса? Зачем вообще там отдельный класс и что мешало в главный класс Main поместить метод fb и вызывать его в обычном цыкле, без всяких стримов, в функции main, как вы до этого сделали с Котлином и другими языками. ЁЁПе , как вы выразились , есть в боьшинстве приведенных вами яхыков, но вы почему то там не городили инстанцирования класса на каждую итерацию цикла.


  1. ftc
    00.00.0000 00:00

    Очень странное сравнение, как по мне. Если уж сравнивать производительность - то не решения "в лоб" или решения "максимально задействующего синтаксический сахар", а "решения, в котором сколько-нибудь заморочились с производительностью".

    В плюсах - если уж хотим использовать iostream, то стоит выключить синхронизацию с stdio, чтобы буферизация работала std::ios::sync_with_stdio(false); . Там же, в плюсах, похоже компилятор решил весь C++-ный рантайм статически слинковать. Оно конечно удобно, но вообще говоря не то чтобы принято (ну т.е. под винды стоит компилировать MSVC и использовать его системные dll-ки, под пингвинов - системный же libc).

    В джаве я не очень понял, зачем делать объект FB внутри main, а потом дёргать у него статический метод, в котором снова создаётся объект того же FB.

    Вот к go, python и php придраться особо негде, по крайней мере на первый взгляд. А в остальных я не силён.


    1. Miiao Автор
      00.00.0000 00:00
      -2

      rustc тоже статически стандартную библиотеку линкует ????

      Уже устал говорить, но для вас, человека вежливого, повторю: измерял не скорость языков, а скорость языковых средств. Именно потому и вывод, и форматирование включены.

      И всё же люди принимают результаты близко к сердцу.

      Насчёт джавы, я ни разу не ООПшник (бросил этой дурью маяться как пару лет), а особенно не джавист. Рад буду поглядеть на то, как дёрнуть этот метод без создания объекта, думаю, способы быть должны.

      Спасибо за спокойный feedback ;)


      1. ftc
        00.00.0000 00:00
        +1

        Статический метод - дак по имени класса же FB.fb(x). А изнутри объекта (из не-статического метода) - просто по имени.


        Дело не в том, что близко к сердцу или нет, а в том, что "что мы доказываем". Если доказываем "на языке X можно быстро написать что-то годное за счёт удобных языковых средств" - в таком случае непонятно, зачем мерять производительность (ну т.е. это прям совсем не основная характеристика при таком сравнении), а надо скорее сравнивать лёгкость написания / понимания / расширения.

        А если доказываем "на языке X можно написать, чтобы быстро работало" - то тогда надо сравнивать более-менее идиоматичные способы написания "чтобы быстро работало". Ну либо бескомпромиссно оптимизировать, но эт уже нужны специалисты в каждом конкретном ЯП и лучше тогда в виде соревнования какого-то.

        В общем, я не очень понял, что хотели сравнить, отсюда и вопросы.

        Что же касается размера влинкованного рантайма (или размера интерпретатора) - дак он сильно зависит и от компилятора в том числе. Хотя конечно даёт некоторую информацию на тему "сколько надо потратить места, чтобы на сферическом компьютере в вакууме запустить эту программу". С другой стороны - когда он статически слинкован, нам на каждую программу своя копия рантайма нужна, а если мы про php там или джаву, то он более-менее один на всех. Впрочем, эт уже отдельная история.


  1. igor6130
    00.00.0000 00:00
    +2

    И все же, что измерялось в таблице «Удобство форматирования (учитываются не только символы)»? Что значит «учитываются не только символы»? И что за загадочный столбец «Доп. символы»?


  1. mobi
    00.00.0000 00:00
    +3

    А теперь забудем про примеры не самого оптимального кода и посмотрим на результаты "наименьшего" времени выполнения (логично, что внешние процессы могут замедлить время выполнения, но не увеличить его):


    Fortran 4,531
    PHP 5,497
    Go 5,912
    C 6,687
    Rust 6,815
    Python 6,891
    D 8,034
    Swift 8,073
    PascalABC.NET 8,282
    Java 8,579
    Dart 9,013
    Kotlin 13,666
    C++ 18,179


    После чего расходимся изучать Fortran (или, на крайний случай, PHP).


    1. Miiao Автор
      00.00.0000 00:00
      -3

      Вас чем-то обидели физики?


      1. BadHandycap
        00.00.0000 00:00
        +2

        Вас, видимо, чем-то обидели химики?


  1. trueZurg
    00.00.0000 00:00
    +1

    А где ассемблер? Низачет :)


  1. Kelbon
    00.00.0000 00:00
    +6

    Просто чудовищно, абсолютно на каждом этапе, начиная с
    1. меряем какую-то дичь

    Даже фанат раста скажет, что пост плохой, потому что на расте написан какой-то ужас.

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

    И главное автора даже не смутило, что php быстрее плюсов и имеет меньший бинарник


  1. web3_Venture
    00.00.0000 00:00
    -3

    очень интересно складывает пару чисел... Но когда тесты доходят до реального применения, только вдумайтесь CLR работает быстрее ваших наивных языков


  1. Izaron
    00.00.0000 00:00
    +2

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

    1. Запусков очень мало.

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


    1. Ktator
      00.00.0000 00:00

      Так как у нас тут ОС, которая в любой момент может запустить фоновый процесс, который может замедлить выполнение бенчмарка, то я бы брал минимальное время среди всех запусков. Как время, в которое сторонние процессы меньше всего мешали измеряемому.


  1. X-Ray_3D
    00.00.0000 00:00
    +2

    Странные результаты у автора, у меня дебажная сборка С++ на порядки быстрее:

    Win 10 x64
    Intel(R) Core(TM) i3-10105 CPU @ 3.70GHz
    
    Базовая скорость:	3.70 ГГц
    Сокетов:	1
    Ядра:	4
    Логических процессоров:	8
    Виртуализация:	Включено
    Кэш L1:	256 КБ
    Кэш L2:	1.0 МБ
    Кэш L3:	6.0 МБ
    
    в среднем 
    100000 iterations in 0.343 seconds
    +/- 0.05 seconds

    А релизная:

    100000 iterations in 0.16 seconds
    +/- 0.02 seconds


  1. Cheater
    00.00.0000 00:00

    Безотносительно проблем с адекватностью бенчмарка - вы всё это на калькуляторе что ли запускаете? oO
    Ваша версия на C у меня выполняется за 0.3-0.4 сек а не за 10 секунд, на довольно слабой машине (Chromebook).

    Дайте в глаз тому кто вам помогал оптимизировать C. Выигрыш от обращения по индексу вряд ли большой, зато о том чтобы over 9000 раз не делать puts в stdout никто не озаботился. Вот немного дополненный вариант с буферизацией, на моей машине он быстрее вашего примерно в 4 раза. Можно и ещё быстрее

    #include <stdio.h>
    #include <time.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdbool.h>
    
    char* FB[] = {"", "Buzz\n", "Fizz\n", "FizzBuzz\n"};
    
    char buffer[BUFSIZ];
    
    char *buffer_tip = buffer;
    
    void fb(int x) {
        bool fizz = x % 3 == 0;
        bool buzz = x % 5 == 0;
        unsigned int len;
    
        if (!fizz && !buzz) {
            len = sprintf(buffer_tip, "Other(%d)\n", x);
            buffer_tip += len;
        } else {
            len = (fizz && buzz) ? 9 : 5;
            memcpy(buffer_tip, FB[fizz << 1 | buzz], len);
            buffer_tip += len;
        }
    }
    
    int main() {
        clock_t start = clock();
        memset(buffer, 0, BUFSIZ);
        setvbuf(stdout, 0, _IOFBF, BUFSIZ);
    
        for (int i = 1; i <= 100000; i++) {
            fb(i);
            if ((buffer_tip - buffer)>= BUFSIZ - 20) {
                *buffer_tip = 0;
                puts(buffer);
                buffer_tip = buffer;
            }
        }
        printf("100000 iterations in %f seconds\n", (float) (clock() - start) / CLOCKS_PER_SEC);
    }
    


  1. MaKeSter
    00.00.0000 00:00
    +2

    В C++ тестировали медленность cout?


    1. Miiao Автор
      00.00.0000 00:00

      Да


  1. Nidrop
    00.00.0000 00:00

    Dart — замечательный скриптовой язык...

    Dart, хоть и скриптовой...

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

    dart compile exe fbdart.dart

    что-то тут не сходится


  1. playermet
    00.00.0000 00:00
    +2

    Чтобы статья была с пользой, приведем очередную итерацию шпаргалки по написанию бенчмарков:

    Использовать как можно больше итераций.
    Прогревать код перед началом замеров.
    
    Минимизировать влияние операций замера времени.
    Делать замер до и после цикла тела бенчмарка.
    
    Использовать самый точный из доступных таймеров.
    
    Не допускать посторонний код в теле бенчмарка.
    Особенно это касается операций ввода-вывода.
    
    Использовать случайные входные данные на каждой итерации.
    Заранее генерировать массив значений входных данных.
    
    Проверить данные:
     - входные данные корректны
     - входные данные репрезентативны
     - код не завершается слишком рано
     - результаты кода корректны
    
    Убедиться что результаты не являются мертвым кодом.
    Например:
     - вычислять и выводить сумму результатов
     - сохранять результаты в массив
    
    При сравнении языков писать эквивалентный код.
    Примеры проблем:
     - используя разные алгоритмы
     - передача по ссылке и значению
     - строки с интернированием и без
     - используя ООП и без


    1. Miiao Автор
      00.00.0000 00:00
      -2

      В данном случае вывод и форматирование - не посторонние вещи, а, по сути, то, скорость чего тестируется. ООП - определённо медленно :)

      А так, спасибо за шпаргалку