LINUX.ORG.RU
ФорумTalks

Небольшой бенчмарк Rust vs C++

 , , ,


0

7

Я тут вам покушать принёс.

Rust: https://github.com/KivApple/rust_benchmark

C++: https://github.com/KivApple/cxx_benchmark

Реализован алгоритм построения icosphere. Этот алгоритм запускается 10000 раз и замеряется среднее время исполнения. Алгоритм реализован практически дословно на обоих языках. Для небольшой математики используется glm для C++ и glam для Rust. Также алгоритм использует стандартные контейнеры HashMap и Vec для Rust и std::unordered_map и std::vector для C++.

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

Rust: 211 us
MSVC: 606 us
GCC: 497 us
Clang: 575 us
Rust: 211 us
MSVC: 598 us
GCC: 496 us
Clang: 587 us

Тестовое железо: Core i7-1165G7, 64 GB RAM, Windows 11 (мне быстрее всего было это запустить на Windows машине, плюс так получилась возможность забенчить ещё и MSVC для полноты картины, так же надо учесть, что в программе нет ни I/O, ни многопоточности, так что едва ли ОС будет влиять на производительность, а если и будет, то опять же оба языка в равных условиях).

Все проекты собраны в Release режиме (cargo build --release, cmake -DCMAKE_BUILD_TYPE=Release) под 64 бита.

Критика бенчмарка принимается (я понимаю, что это один частный use-case, но в рамках него можете указать на какие-то неточности замеров или несоответствия алгоритмов для разных языков). Также можете попробовать запустить на своих машинах со своими компиляторами (код достаточно тривиальный для понимания).

UPD: Версии компиляторов:

> rustc --version
rustc 1.64.0 (a55dd71d5 2022-09-19)
> clang --version
clang version 15.0.2
Target: x86_64-pc-windows-msvc
Thread model: posix
> cl
Microsoft (R) C/C++ Optimizing Compiler Version 19.32.31329 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.
> gcc --version
gcc (GCC) 11.3.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
★★★★★

Последнее исправление: KivApple (всего исправлений: 2)

И что ты хочешь сказать что C++ в 2.5-3 раза проигрывает Rust? Это уж как-то слишком фантастично. Смотрел в каких местах программы получается наибольшая разница?

rumgot ★★★★★
()
Ответ на: комментарий от rumgot

Я могу предположить, что отличается алгоритм HashMap и unordered_set, соответственно. И это бенчмарк не только языка, но и стандартной библиотеки.

KivApple ★★★★★
() автор топика
Ответ на: комментарий от KivApple

Да даже при не самой эффективной реализации хэш таблицы не должен результат в два раза отличаться. А ты резервируешь достаточно места в хэш таблицах, не происходит ли в процессе перехэширование?

rumgot ★★★★★
()
Ответ на: комментарий от rumgot

Не резервирую, происходит. Но это проблема для обоих языков.

KivApple ★★★★★
() автор топика
Ответ на: комментарий от rumgot

У хештаблиц есть как минимум один конструкционный паттерн, который можно реализовать по-разному - хранить ли данные в самой таблице или хранить ли в таблице указатели на данные. Я не уверен, но кажется Rust использует первый подход, а C++ второй. В таком случае C++ получает штраф из-за большего количества аллокаций (и не важно перехешируем мы или нет - аллокация происходит на каждый добавляемый элемент в любом случае) и введения indirection.

C++: https://en.wikipedia.org/wiki/Hash_table#Separate_chaining

Rust: https://abseil.io/blog/20180927-swisstables

KivApple ★★★★★
() автор топика
Последнее исправление: KivApple (всего исправлений: 1)
Ответ на: комментарий от KivApple

Тут несколько факторов и самое значимое что мне бросилось в глаза это вот эта секция

	std::vector<glm::vec<3, unsigned int>> triangles = {
			{0, 4, 1}, {0, 9, 4}, {9, 5, 4}, {4, 5, 8}, {4, 8, 1},
			{8, 10, 1}, {8, 3, 10}, {5, 3, 8}, {5, 2, 3}, {2, 7, 3},
			{7, 10, 3}, {7, 6, 10}, {7, 11, 6}, {11, 0, 6}, {0, 1, 6},
			{6, 1, 10}, {9, 0, 11}, {9, 11, 2}, {9, 2, 5}, {7, 2, 11}
	};

Она развернётся в цепочку из кучи std::initializer_list<> с множенственной аллокацией временных объектов

sparks ★★★★
()
Ответ на: комментарий от rumgot

Добавил резервирование - https://github.com/KivApple/rust_benchmark/commit/ab8581bf59c918d954641eee9f5..., https://github.com/KivApple/cxx_benchmark/commit/face3cdf146c1ece6af035be254b....

Результаты:

Rust: 134 us
MSVC: 464 us
GCC: 439 us
Clang: 462 us
Rust: 120 us
MSVC: 461 us
GCC: 435 us
Clang: 465 us

Теперь разница в 3 раза :-)

KivApple ★★★★★
() автор топика
Ответ на: комментарий от sparks

У glm::vec3 есть constexpr vec3(T x, T y, T z), соответствтенно, будет вызван он напрямую без создания промежуточного initializer_list, в данном случае вложенные фигурные скобки лишь синтаксический сахар для вызова конструктора. Так что данный код создаёт лишь один initializer_list для самого vector.

20 * 3 * sizeof(unsigned int) = 20 * 3 * 4 = 240 байт. Копирование 240 байт едва ли занимает сколько-нибудь значимую роль в данном алгоритме (второй vector с самими координатами весит ещё меньше, так как там меньше элементов, а sizeof(unsigned int) == sizeof(float) на моей архитектуре), даже если его повторить несколько раз.

KivApple ★★★★★
() автор топика
Последнее исправление: KivApple (всего исправлений: 3)

Прогнал тесты на своем компе (на линуксе)

GCC (12.2.0) - 141 us
rust (1.64.0) - 218 us

Как-то это странно выглядит в сравнении с оригинальным постом. Нужны еще результаты)

selim
()

Ветка с преаллокацией. Ryzen 5950X. И где твои три раза? Давай под линуксом запускай или перечёркивайся.

➜  rustc --version
rustc 1.64.0 (Arch Linux rust 1:1.64.0-1)
➜  ./rust_benchmark 
Mesh generation time: 122 us
➜  ./rust_benchmark
Mesh generation time: 136 us
➜  ./rust_benchmark
Mesh generation time: 136 us

➜  gcc --version
gcc (GCC) 12.2.0
➜  ./cxx_benchmark 
Mesh generation time: 145 us
➜  ./cxx_benchmark
Mesh generation time: 117 us
➜  ./cxx_benchmark
Mesh generation time: 144 us
ox55ff ★★★★★
()
Ответ на: комментарий от selim

Запустил под WSL на том же железе. Хоть Rust и выигрывает, разница стала всего 20% (а ещё у меня старый GCC).

GCC: 144 us, 143 us, 142 us
Rust: 121 us, 117 us, 115 us
$ gcc --version
gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ rustc --version
rustc 1.64.0 (a55dd71d5 2022-09-19)

Возможно, под Linux используется другая целевая архитектура и компилятор может использовать более современные процессорные инструкции. С другой стороны, Rust под Windows должен следовать тем же ограничениям в теории.

KivApple ★★★★★
() автор топика
Последнее исправление: KivApple (всего исправлений: 1)
Ответ на: комментарий от KivApple

Сделал резервирование массива вершин тоже:

https://github.com/KivApple/rust_benchmark/commit/c7723711e7839224d91154243e3...

https://github.com/KivApple/cxx_benchmark/commit/7be5b2917bb45e16de55ae38e0bb...

Результаты:

[Windows]
Rust: 112 us
MSVC: 422 us
GCC: 404 us
Clang: 419 us
Rust: 111 us
MSVC: 421 us
GCC: 399 us
Clang: 415 us
[WSL]
Rust: 102 us, 102 us, 102 us
GCC: 136 us, 139 us, 141 us
KivApple ★★★★★
() автор топика
Ответ на: комментарий от KivApple

Могу выдвинуть версию, что мы сравниваем скорость работы системного аллокатора и на Windows он тормознутее, чем на Linux. А Rust, возможно, на всех ОС использует свой аллокатор и его производительность выше.

KivApple ★★★★★
() автор топика
Ответ на: комментарий от KivApple

Сделал ещё меньше аллокаций за счёт переиспользования вектора треугольников и кеша рёбер между итерациями подразделения:

https://github.com/KivApple/rust_benchmark/commit/d21704a0e891a17524942d73b3e...

https://github.com/KivApple/cxx_benchmark/commit/6f1f60a2c44d8d7ad4c04c51eb52...

[Windows]
Rust: 111 us
MSVC: 400 us
GCC: 380 us
Clang: 375 us
Rust: 110 us
MSVC: 405 us
GCC: 378 us
Clang: 378 us
[WSL]
Rust: 112 us, 111 us
GCC: 131 us, 129 us

Как можно заметить, для Rust это не особо влияет на производительность (так как в отличии от первоначального резервирования, где аллокаций стало меньше во много раз, здесь речь идёт лишь о снижении количества аллокаций с 10 до 6), а для C++ по-прежнему даёт заметный (хоть и не очень большой) прирост.

В общем, судя по всему действительно преимущество Rust в своём аллокаторе.

KivApple ★★★★★
() автор топика
Ответ на: комментарий от KivApple

Хватит уже виндотроллить. Даже в WSL всё равно аллокация страниц идёт через ядро винды. Опять же ядро линукса пропатчено, чтобы неиспользуемую память возвращать обратно в винду. Не ясно как это всё влияет.

ox55ff ★★★★★
()
Ответ на: комментарий от ox55ff

Речь не о ядерном аллокаторе, а об аллокаторе из libc. Все структуры данных алгоритма занимают несколько сотен килобайт, скорее всего аллокация страниц вообще происходит лишь один раз (а даже если нет, то за первую итерацию приложение получит достаточное количество памяти от ядра, а фрагментация кучи мала, так как все структуры данных уничтожаются между итерациями, поэтому дозапрашивать не придётся), а вот уже внутри памяти запрошенной от ОС работает аллокатор libc. И вот этот аллокатор в WSL и в Windows-приложении будет отличаться, так как у WSL и обычного Linux идентичные юзерспейс библиотеки, а все Windows C/C++ приложения используют malloc-free из MSVCRT.DLL.

KivApple ★★★★★
() автор топика
Последнее исправление: KivApple (всего исправлений: 2)
Ответ на: комментарий от rumgot

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

В C++ используется обычная хешмапа на бакетах, и это пожалуй самая медленная из реализаций хешмапы, которую можно представить. У неё выигрывает даже бустовая хешмапа, связанная теми же ограничениями.

Вот бенчмарк плюсовых хешмап: https://martin.ankerl.com/2019/04/01/hashmap-benchmarks-04-02-result-RandomFind_2000/

Растовая должна работать приблизительно так же, как absl::flat_hash_map, так что удивляться разнице в скорости как раз не стоит.

f1u77y ★★★★
()
Ответ на: комментарий от firkax

А за счёт чего? Кто-то злой подложил в компиляторы Rust и С++ генераторы оверхеда? Давайте найдём и накажем этих негодяев!

UPD: и в библиотеки тоже

f1u77y ★★★★
()
Последнее исправление: f1u77y (всего исправлений: 1)

Смысл всех этих бенчмарков. Неужели, каждый, кто проработал достаточное время, не знает, что основная беда почти всех проектов - говнокод. Который невозможно читать (нужны месяцы копания для новых разработчиков, чтобы они смогли продуктивно править баги и добавлять фичи) и невозможно развивать, т.к. каждое изменение ломает что-нибудь в другом месте.

Скорость выполнения простых алгоритмов роляла больше года до 2000-го, когда софт развивался очень медленно, релизы делались годами, а хард был ещё очень медленный, и каждый такт имел значение.

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

emorozov
()
Ответ на: комментарий от emorozov

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

f1u77y ★★★★
()

Ну а на Linux C++ всё равно быстрее выходит:

$ ./target/release/rust_benchmark
Mesh generation time: 132 us

vs

$ ./cxx_benchmark
Mesh generation time: 104 us
Ja-Ja-Hey-Ho ★★★★★
()
Ответ на: комментарий от Ja-Ja-Hey-Ho
$ g++ --version
g++ (GCC) 12.2.1 20220819 (Red Hat 12.2.1-2)
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Ja-Ja-Hey-Ho ★★★★★
()

Icosphere это икосаэдр что ли?

А че там делают всякие хэш таблицы и множества? Напуркуа оно там?

AntonI ★★★★★
()
Ответ на: комментарий от emorozov

Зависит от области. Есть области где микросекунды и такты архиважны.

AntonI ★★★★★
()
Ответ на: комментарий от emorozov

Вот так делают все на js/java на которых легко читать/писать и получаем смарты, которые не могут больше двух дней прожить без подзарядки.

rumgot ★★★★★
()
Ответ на: комментарий от emorozov

Ага. А потом мы получили электрон. Где даже текстовый редактор научили тормозить, жрать проц при моргании курсора и как не в себя оперативку.

th3m3 ★★★★★
()
Ответ на: комментарий от AntonI

Я использовал вот этот алгоритм по первой ссылки из гугла по запросу «icosphere generation algorithm»: https://schneide.blog/2016/07/15/generating-an-icosphere-in-c/ (C++)

Следующие 2 ссылки из моего гугла:

https://observablehq.com/@mourner/fast-icosphere-mesh (JavaScript)

http://blog.andreaskahler.com/2009/06/creating-icosphere-mesh-in-code.html (C#)

Все 3 алгоритма используют различные варианты ассоциативного массива чтобы хранить сгенерированные вершины с привязкой из какого ребра она была сгенерирована и избегать появления дупликатов (результатом работы алгоритмов является массив вершин и массив индексов вершин, предлагается использовать indexed rendering для отображения на экране, при этом для 4 итераций подразделения получается примерно 2500 вершин и 5000 треугольников, без индексов было бы 15000 вершин - соответственно, выигрыш по памяти в 2 раза, к тому же в 3 раза меньше математических операций таких как извлечение квадратного корня и деления, но ценой использования хештаблицы, впрочем, в последней редакции моего алгоритма нет переаллокаций и перехеширования, а значит простой поиск в хештаблице должен быть дешевле математических вычислений).

KivApple ★★★★★
() автор топика
Последнее исправление: KivApple (всего исправлений: 2)
Ответ на: комментарий от ox55ff

Так я про то и пишу - без библиотек. Вместо всех этих ни то ни сё реализаций общего назначения сделать реализацию строго под задачу. Это и есть написание алгоритмов на Си.

firkax ★★★★★
()
Ответ на: комментарий от f1u77y

Да, оверхед получается из-за использования универсальных стандартных инструментов вместо специальных под только эту задачу.

firkax ★★★★★
()
Ответ на: комментарий от firkax

Это и есть написание алгоритмов на Си.

А при чём тут язык программирования? Реализации «строго под задачу» можно писать на чём угодно.

использования универсальных стандартных инструментов вместо специальных под только эту задачу

А в чём в этой задаче может дать приемущество использование «специальных под только эту задачу» инструментов?

f1u77y ★★★★
()

Тестовое железо: Core i7-1165G7, 64 GB RAM, Windows 11
Windows 11

Шикарный тест для «Rust vs C++» ! Мы подписались на ваш канал, продолжайте держать нас в курсе.

anc ★★★★★
()
Ответ на: комментарий от selim

Как-то это странно выглядит в сравнении с оригинальным постом.

Возможно ТС в этот момент котиков смотрит :)

anc ★★★★★
()
Ответ на: комментарий от KivApple

Мдя… Когда лень думать берут всякие хэши-мэши:-)

Неразбитый икосаэдр делается через операции поворота (частный случай кватернионов), а инцидентность граней и вершин там просто by design.

Дальнейшие разбиения можно делать по разному, но опять таки если чуть подумать - оно тоже искаропки без хэш таблиц.

Есть вариант когда грани исходного икосаэдра объединяются попарно, и дальше получившиеся ромбы бьются равномерной сеткой (перекошенной прямоугольной).

Есть еще пентакисдодекаэдр, он при разбиении чуть лучше икосаэдра (треугольники ближе к правильным).

AntonI ★★★★★
()
Ответ на: комментарий от f1u77y

Можно - на чём угодно, но С++ заточен под всяческие абстракции. То есть можно конечно и Си-код С++ компилятором скомпилировать, или ещё какие-то промежуточные варианты применить, но непонятно зачем так делать.

А в чём в этой задаче может дать приемущество использование «специальных под только эту задачу» инструментов?

Ну там какие-то аллокаторы с хеш-таблицами например, а мы заранее знаем всю «геометрию» использования памяти, можно статически считать индекс в статической памяти. А ещё разместить их так чтоб процовый кеш лучше работал.

firkax ★★★★★
()
Ответ на: комментарий от ox55ff

На си нет хэштаблицы в std

Строго говоря, в POSIX.1-2001 есть hcreate(3).

Softwayer ★★
()
Ответ на: комментарий от KivApple

А где ты гцц запускал, если не в WSL? Так же clang какой версии ты не указал и как установлен. Попробуй может тоже его в wsl запустить.

technic93
()

зачем велосипедить хэш для std::pair чего-то там, если он уже есть из коробки?

Заменил unordered_map на F14ValueMap из фейсбучного folly, время выполнения упало в 2 раза (было 114 мс, стало 60). Код на расте не тестировал.

Lrrr ★★★★★
()

Верный признак секты - желание всем доказывать свою правоту, в контексте ИТ - это подборки умышленно искажённых тестов с комментариями, что «у меня Раст работает в 3-5 раз быстрее C++/С».

mxfm ★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.