Как стать автором
Обновить

Комментарии 74

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Да ладно, увидел возврат std::string там где просится std::string_view и дальше уже понял, что читать не нужно. Хотя unsafe Rust на ровном месте тоже улыбнуло)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

В компиляторе реализован не 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();
}

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

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

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

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

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

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

FB fizzbuzz = new FB();

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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


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. меряем какую-то дичь

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

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

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

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

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

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

  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

Безотносительно проблем с адекватностью бенчмарка - вы всё это на калькуляторе что ли запускаете? 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);
}

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

Да

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

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

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

dart compile exe fbdart.dart

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

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

Использовать как можно больше итераций.
Прогревать код перед началом замеров.

Минимизировать влияние операций замера времени.
Делать замер до и после цикла тела бенчмарка.

Использовать самый точный из доступных таймеров.

Не допускать посторонний код в теле бенчмарка.
Особенно это касается операций ввода-вывода.

Использовать случайные входные данные на каждой итерации.
Заранее генерировать массив значений входных данных.

Проверить данные:
 - входные данные корректны
 - входные данные репрезентативны
 - код не завершается слишком рано
 - результаты кода корректны

Убедиться что результаты не являются мертвым кодом.
Например:
 - вычислять и выводить сумму результатов
 - сохранять результаты в массив

При сравнении языков писать эквивалентный код.
Примеры проблем:
 - используя разные алгоритмы
 - передача по ссылке и значению
 - строки с интернированием и без
 - используя ООП и без

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

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

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации