Спойлер: 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)
Siemargl
00.00.0000 00:00+24Мда, не без осечки, конечно
Автор не различает [по сложности?] возврат объекта, указателя и константы
Классическое тестирование скорости выполнения print
Но все можно исправить, да и многоязычие надо поощрять.
Miiao Автор
00.00.0000 00:00-4Спасибо за доброе слово. Тестирование скорости вывода и было мной задумано, что же касается первого пункта, право, не понимаю о чём вы. Я ведь логику с языка на язык переносил, а не состояния возвратных регистров.
В любом случае, всегда готов выслушать всякое предложение :)
Siemargl
00.00.0000 00:00+1Для понимания стоит заходить на gotbolt. Конструирование объекта на хипе (с последующим вызовом GC), копирование массива или возврат указателя и константы это кардинально разные по затратам операции.
adeshere
00.00.0000 00:00Классическое тестирование скорости выполнения print
Не знаю, как в других языках, но конкретно в фортране львиную долю времени исполнения будет выполняться именно write. В фортране этот оператор содержит немеряное количество опций на все случаи жизни, и он реально тормозной. Я когда вычислениями занимаюсь (где на один write сотни и тысячи арифметических операций с плавающей точкой), то вместо write для записи результатов вручную эмулирую выходной проток с буферизацией по 2-4Кб, чтобы вызывать write пореже и без форматирования. Даже на фоне довольно интенсивных расчетов такая оптимизация очень заметно сказывается на скорости. А в предложенном коде на один write буквально пара тривиальных машинных кодов.
Возникает такое впечатление, что компилятор фортрана что-то там с оператором write нахимичил. Иначе невозможно объяснить, почему фортрановский write выполняется даже быстрее, чем всяческие cout-ы и их аналоги, которые в разы проще по логике (и на порядки компактнее по машинному коду).
Siemargl
00.00.0000 00:00+1да ничего там не проще. с каждой новой итерацией [стандартов] форматированный вывод все сложнее и медленнее
fk0
00.00.0000 00:00+25На голом C специально пишут тормозную версию с копированием строк, далее заявляют, что мол Rust быстрей всех. Дальше не читал.
gchebanov
00.00.0000 00:00+16Код должен миллисекунды работать, человек померил скорее всего как антивирус в облако заливает бинари, или терминал рендрит юнткод, странно что автора не удивляет разброс с 6 до 20 секунд на трай, и питон обгоняющий плюсы по скорости.
Miiao Автор
00.00.0000 00:00-12Modern C++, что ещё сказать…
fk0
00.00.0000 00:00+5Даже на Modern C++ строки по-значению копировать не обязательно, можно возвращать указатели на предварительно сформированные строки, что в примерах на других языках делается. Более того, выбор fizbuzz -- вообще достаточно идиотский. Там на любом языке оптимальная программа пишется одинаково и имеет одинаковую производительность. И хуже того, Modern C++ имеет, в отличие от, нормальные constexpr функции и всё такое прочее, чтоб весь fizbuzz расчитать в compile time и вывести путём одного единственного системного вызова write.
AnthonyMikh
00.00.0000 00:00+11unsafe {core::mem::transmute((x % 3 == 0, x % 5 == 0, x))}
У вас UB, раскладка кортежей в памяти не гарантированна.
DreamingKitten
00.00.0000 00:00+1Исключительно ради придраться.
go build по умолчанию выдаёт бинарник с отладочной информацией. Сделайте ему strip --strip-all и он похудеет до 1200 Kb.
mrobespierre
00.00.0000 00:00+2Стрипать Go бинари нельзя, это не поддерживается, работа с heap может сломаться. Вместо этого надо делать no debug build
go build -ldflags '-s -w'
BadHandycap
00.00.0000 00:00-1Флаг "-s" для компоновщика и есть "стрипание".
Так можно или нельзя?
mrobespierre
00.00.0000 00:00Это не стрипание, стрипание - отрезание debug инфы утилитой strip, если сделать go build -ldflags '-s -w' она вообще не добавляется.
Попробуйте сделать обычный билд, стрипнуть его и сравнить с nodebug build который сделает go build, они скорее всего будут отличаться.
DreamingKitten
00.00.0000 00:00+2Стрипать Go бинари нельзя, это не поддерживается, работа с heap может сломаться
Говорят, что этот баг пофиксили ещё в 2011 году.
mrobespierre
00.00.0000 00:00+2Круто, хотелось бы верить. А какой-то пруф есть? Ну там issue resolved или что-то подобное. Не хотелось бы получить неожиданный segfault.
DreamingKitten
00.00.0000 00:00+1mrobespierre
00.00.0000 00:00+1Спасибо. По ссылке есть внятные пруфы только про ldflags и 2016, но благодаря ей я нашёл обсуждение в google groups про strip. Хотя Дэйв Чейни и утверждает, что strip ломает бинари, остальные участники его опровергают. Будем пробовать.
lea
00.00.0000 00:00+6Размер бинаря
Язык Размер, КБайт
C 90
Kotlin 540
C++ 16310
Java (JRE11) 305767У меня у одного вызывают сомнения эти цифры?
Miiao Автор
00.00.0000 00:00-1У джавы указан размер JRE. Насчёт остальных не врал, можете сами попробовать.
DoHelloWorld
00.00.0000 00:00+2Для плюсов можно -fno-exceptions -fno-rtti иначе прилинкует подефолту. В тесте они не нужны. вроде
Ktator
00.00.0000 00:00+5Я просто оставлю это здесь: https://habr.com/ru/post/540136/
PS: PascalABC попробовали, а самый популярный .NET язык (C#) не попробовали...Miiao Автор
00.00.0000 00:00-6Занятная статейка ????
Не видел смысла во включении шарпа, так как есть джава. Несмотря на все его отличия, синтаксис у них одинаковый во многих аспектах. Думал над включением F#, но меня уже не хватало.
ftc
00.00.0000 00:00+3Ну мы ж тут вроде скорость дефолтного рантайма замеряем :-) Так что шарп стоило включить.
Inobelar
00.00.0000 00:00+8rust-фанатик детектед. что меряем-то? :D printf? гоу мерять доступы к массивам, там кажись, у раста накладные на bound checking :D
Miiao Автор
00.00.0000 00:00-10Измерял специально максимально глупо и неэффективно)
Что же касается массивов, их, простите, в си нет, там арифметика указателей…
BadHandycap
00.00.0000 00:00+6В языке Си нет массивов? Деннис Ритчи уже в курсе?
mostodont32
00.00.0000 00:00+4Ох, сердечко прихватило, когда std::endl увидел в замере производительности...
Miiao Автор
00.00.0000 00:00В Rust тоже такая хрень в println встроена, и ничего, живёт.
Kelbon
00.00.0000 00:00-2Нет не встроена, прочитайте что такое flush буфера.
Хотя вам нужно почитать сначала как делаются бенчмарки
Miiao Автор
00.00.0000 00:00А я вам говорю, что встроена. Вам нужно почитать код стандартной библиотеки.
Kelbon
00.00.0000 00:00-3да что вы несёте. Принт в расте вообще реализован в компиляторе, а не стандартной бибилотеке
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(); }
Kelbon
00.00.0000 00:00вот прямо ваш код и выдает только aaa bbb и теряет ccc
https://godbolt.org/z/aKndTczb3mayorovp
00.00.0000 00:00+2Ну да, я специально написал его таким образом. Чтобы было очевиднее превращение перевода строки в аналог endl
leonidv
00.00.0000 00:00+17На основе комментариев к статье можно сделать статью "Как не надо писать бенчмарки"
Lord_Ahriman
00.00.0000 00:00+9У вас случайно или намеренно написан неэффективный код на Си, плюс измерена скорость непонятно чего (работа со стандартным выводом и print показателем эффективности языка не является). Плюс по мелочи - вы путаете указатели с объектами и т.п. Делать какие-либо выводы из этого нельзя, статья, фактически, представляет собой классический пример confirmation bias.
LivEEvil
00.00.0000 00:00+7Что бы показать какая Java "медленная" будем тестировать ее без C2 компиляций и наворотим специально всякого мусора.
зачем на каждый вызов создавать новые объекты?
FB fizzbuzz = new FB();
зачем использовать стримы, которые создают еще кучу объектов? Хотя в том же котлине простой for. Что бы потом удивляться почему же так медленно?
Как же это плохо....
Miiao Автор
00.00.0000 00:00-16А чего вы меня спрашиваете о том, зачем использовать объекты? Не я, дорогой, ЁЁПе придумал…
ashalaenko
00.00.0000 00:00+1Тут вопрос зачем вы на каждый вызов метода создаете инстанс класса? Зачем вообще там отдельный класс и что мешало в главный класс Main поместить метод fb и вызывать его в обычном цыкле, без всяких стримов, в функции main, как вы до этого сделали с Котлином и другими языками. ЁЁПе , как вы выразились , есть в боьшинстве приведенных вами яхыков, но вы почему то там не городили инстанцирования класса на каждую итерацию цикла.
ftc
00.00.0000 00:00Очень странное сравнение, как по мне. Если уж сравнивать производительность - то не решения "в лоб" или решения "максимально задействующего синтаксический сахар", а "решения, в котором сколько-нибудь заморочились с производительностью".
В плюсах - если уж хотим использовать iostream, то стоит выключить синхронизацию с stdio, чтобы буферизация работалаstd::ios::sync_with_stdio(false);
. Там же, в плюсах, похоже компилятор решил весь C++-ный рантайм статически слинковать. Оно конечно удобно, но вообще говоря не то чтобы принято (ну т.е. под винды стоит компилировать MSVC и использовать его системные dll-ки, под пингвинов - системный же libc).В джаве я не очень понял, зачем делать объект FB внутри main, а потом дёргать у него статический метод, в котором снова создаётся объект того же FB.
Вот к go, python и php придраться особо негде, по крайней мере на первый взгляд. А в остальных я не силён.
Miiao Автор
00.00.0000 00:00-2rustc тоже статически стандартную библиотеку линкует ????
Уже устал говорить, но для вас, человека вежливого, повторю: измерял не скорость языков, а скорость языковых средств. Именно потому и вывод, и форматирование включены.
И всё же люди принимают результаты близко к сердцу.
Насчёт джавы, я ни разу не ООПшник (бросил этой дурью маяться как пару лет), а особенно не джавист. Рад буду поглядеть на то, как дёрнуть этот метод без создания объекта, думаю, способы быть должны.
Спасибо за спокойный feedback ;)
ftc
00.00.0000 00:00+1Статический метод - дак по имени класса же
FB.fb(x)
. А изнутри объекта (из не-статического метода) - просто по имени.
Дело не в том, что близко к сердцу или нет, а в том, что "что мы доказываем". Если доказываем "на языке X можно быстро написать что-то годное за счёт удобных языковых средств" - в таком случае непонятно, зачем мерять производительность (ну т.е. это прям совсем не основная характеристика при таком сравнении), а надо скорее сравнивать лёгкость написания / понимания / расширения.А если доказываем "на языке X можно написать, чтобы быстро работало" - то тогда надо сравнивать более-менее идиоматичные способы написания "чтобы быстро работало". Ну либо бескомпромиссно оптимизировать, но эт уже нужны специалисты в каждом конкретном ЯП и лучше тогда в виде соревнования какого-то.
В общем, я не очень понял, что хотели сравнить, отсюда и вопросы.
Что же касается размера влинкованного рантайма (или размера интерпретатора) - дак он сильно зависит и от компилятора в том числе. Хотя конечно даёт некоторую информацию на тему "сколько надо потратить места, чтобы на сферическом компьютере в вакууме запустить эту программу". С другой стороны - когда он статически слинкован, нам на каждую программу своя копия рантайма нужна, а если мы про php там или джаву, то он более-менее один на всех. Впрочем, эт уже отдельная история.
igor6130
00.00.0000 00:00+2И все же, что измерялось в таблице «Удобство форматирования (учитываются не только символы)»? Что значит «учитываются не только символы»? И что за загадочный столбец «Доп. символы»?
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).
Kelbon
00.00.0000 00:00+6Просто чудовищно, абсолютно на каждом этапе, начиная с
1. меряем какую-то дичьДаже фанат раста скажет, что пост плохой, потому что на расте написан какой-то ужас.
Плюс к тому непонятно как сделан вывод из заголовка статьи, ведь раст хуже кучи языков из списка.
И главное автора даже не смутило, что php быстрее плюсов и имеет меньший бинарник
web3_Venture
00.00.0000 00:00-3очень интересно складывает пару чисел... Но когда тесты доходят до реального применения, только вдумайтесь CLR работает быстрее ваших наивных языков
Izaron
00.00.0000 00:00+2Проводилось по пять запусков, но представлены только наименьшее и наибольшее время, а также среднее пропорциональное для всех пяти запусков.
Запусков очень мало.
В матстате никто не считает минимум и максимум. Это уровень детского сада. Считать надо дисперсию и среднеквадратичное отклонение. В идеале это должны быть маленькие значения, а распределение времен нормальным.
Ktator
00.00.0000 00:00Так как у нас тут ОС, которая в любой момент может запустить фоновый процесс, который может замедлить выполнение бенчмарка, то я бы брал минимальное время среди всех запусков. Как время, в которое сторонние процессы меньше всего мешали измеряемому.
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
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); }
Nidrop
00.00.0000 00:00Dart — замечательный скриптовой язык...
Dart, хоть и скриптовой...
однако команда компиляции:
dart compile exe fbdart.dart
что-то тут не сходится
playermet
00.00.0000 00:00+2Чтобы статья была с пользой, приведем очередную итерацию шпаргалки по написанию бенчмарков:
Использовать как можно больше итераций. Прогревать код перед началом замеров. Минимизировать влияние операций замера времени. Делать замер до и после цикла тела бенчмарка. Использовать самый точный из доступных таймеров. Не допускать посторонний код в теле бенчмарка. Особенно это касается операций ввода-вывода. Использовать случайные входные данные на каждой итерации. Заранее генерировать массив значений входных данных. Проверить данные: - входные данные корректны - входные данные репрезентативны - код не завершается слишком рано - результаты кода корректны Убедиться что результаты не являются мертвым кодом. Например: - вычислять и выводить сумму результатов - сохранять результаты в массив При сравнении языков писать эквивалентный код. Примеры проблем: - используя разные алгоритмы - передача по ссылке и значению - строки с интернированием и без - используя ООП и без
Miiao Автор
00.00.0000 00:00-2В данном случае вывод и форматирование - не посторонние вещи, а, по сути, то, скорость чего тестируется. ООП - определённо медленно :)
А так, спасибо за шпаргалку
Starl1ght
Господи, это настолько плохо, что даже хорошо.
В случае С++ вы побенчмаркали скорость флашинга в стандартный вывод. Не говоря, про std::format, который вполне может аллоцировать, чтобы сразу деаллоцировать.
Miiao Автор
Что же, кажется, это первый комментарий без агрессии! Брависсимо! Искренне поражаясь такой милости, объясню, что бенчмарк и должен был быть глупым, измерение вывода и форматирования было специальным, и было во всех языках.
Что же касается плюсов, часть с ними была скорее шуткой, предназначенной для ответа на вопрос «Is modern C++ faster than C?».
В любом случае, спасибо.
HabraUser666
Объясните, пожалуйста, для тупых, что автор вообще пытается делать? Что это за хрень такая упоротая? Причем тут вообще скорость работы языка?