Comments 10
Спасибо за отличное сравнение! Есть один вопрос-предложение: вроде как -Oz
оптимизирует размер, а не производительность (как -O3
). Может быть, если собрать с -O3
, то Emscripten побыстрее будет?
Сарказм:
Позволю трактовать график быстродействия так:
Автор знает rust примерно в 5 раз лучше, чем assemblyscript или C++. Уж больно большой отрыв, и 2-3 места практически одинаковы.
И спасибо за сравнение, интересная тема.
По отсутствию идиоматического кода и переизобретение уже существующих API я бы сказал, что автор знает Rust также плохо как и C++. Просто так сложилось, что многие оптимизации у Rust из коробки, включая возможность SIMD. Код на плюсах более неравномерно выделяет память, в отличие от Rust - есть Vec::with_capacity
в rust, но vector::reserve
я так и не нашёл, а capacity
явно указан не везде одинаково.
А можете привести примеры этого самого идиоматического кода на C++ или на Rust, который должен был написать человек, хорошо знакомый со спецификой языков и знающий "уже существующие API"?
Спасибо.
Если разбирать местный код:
minimum, maximum и absolute не нужны. есть std::cmp::{min, max} и f32::abs, которые по мнению компилятора могут быть заинлайнены.
pub fn new(in_x: f32, in_y: f32) -> Point {
Point{ x: in_x, y: in_y}
}
Превращается в
pub fn new(x: f32, y: f32) -> Self {
Point{x,y}
}
То бишь одноимённые параметры встают без присвоений. Аналогичная ситуация с TriangleCircle и Triangle. На производительность не влияет, но влияет на читабельность.
let mut indices: Vec<usize> = Vec::with_capacity(points_count);
for i in 0..points_count {
indices.push(i);
}
Превращается в
let mut indices = (0..points_count).collect();
Такой код генерирует меньше байткода при этом все ограничения на память присутствуют. Как минимум должно быть не медленнее исходного. Как максимум уменьшит memory footprint и как результат немного ускорится.
in_points.to_vec();
Традиционно копирование делается посредством `clone`.
Циклы в триангуляции по идее можно свести к map/filter/fold/reduce и потенциально получить дополнительное ускорение. Плюс кажется местами удаление из open_list в цикле делает что-то бесполезное.
for i in 0..(triangle_indices.len() / 3)
Ещё один напрашивающийся map
let mut triangles = triangle_indices
.chunks_exact(3)
.map(|chunk| Triangles::new(chunk.iter().map(|p| points[p].clone()).collect()))
.collect::Vec<_>()
В целом стоит почаще сводить задачи как map/filter/reduce, т.к. это помогает компилятору с проверкой и оптимизацией кода и в среднем обработка получает cache friendly для процессора. Плюс такие конструкции легко распараллеливаются каким-нибудь rayon, правда не для случая wasm.
Для тех кто стремится к идиоматичности можно посоветовать использовать cargo clippy, который подскажет как писать чутка идиоматичнее. Ну и читать доки:
Rust By Example - множество типовых примеров паттернов, которые используются в Rust
Rust Cookbook - примеры решения некоторых типовых проблем, встречающихся в программирование - логирование, параллелизм, работа с БД, CLI и пр.
P.S. Пожалуй стоит сделать дисклеймер - Rust позволяет писать код совершенно по разному, однако не делает никаких предположений касательно производительности и практически не может подсказывать как и когда использовать все встроенные в него механизмы, вроде того же chunk_exact. Поэтому не считайте этот комментарий кирпичом в сторону автора.
Касательно AssemblyScript - предполагаю, что можно увеличить быстродействия обернув все обращения к массивам в unchecked
, который отключает достаточно тяжёлые проверки на выход за границы массива.
Около двух лет назад переписывал часть приложения на AssemblyScript. Для меня его киллер-фича - это возможность получить на выходе обычный JavaScript если скомпилировать его с помощью TypeScript - очень сильно помогает при отладке. Но язык был ужасно сырой, только в процессе переписывания я создал более десятка багов в их репозитории, которые, к слову, были оперативно исправлены. Однако даже после оборачивание всего в unchecked
и частичного отключения встроенного GC код работал примерно с той же скоростью, что и оригинальный JavaScript, при этом всё ещё наблюдались отличия в поведении из-за багов, которые я уже не стал исследовать. Посмотрел сейчас в их репозиторий - и как будто за два года ничего принципиально не изменилось, куча достаточно мелких релизов с незначительными исправлениями.
А насколько эффективнее это будет работать в рантайме самой Node.js? Если есть преимущества, то можно будет менее болезненно писать хорошие библиотеки.
Зависит от того, что вы планируете писать в библиотеке. Если изобретать реакт на wasm с виртуальным DOM и прочим хтмлом, то скорее всего не сильно лучше. Если делать всякие тяжелые вычисления, как в статье - то получите прирост. Собственно для этих целей и делался wasm. У хрома и у Node.js один движок - V8 и весь JS код фактически JITился в wasm всё это время. Компилируя из языков с ручным управлением памяти вы получаете ровно эти самые бенефиты управления памятью и прирост в сравнении с аналогичным кодом на голом JS. Сами wasm модули распространяются как те же npm пакеты с JS-обёрткой. В случае с wasm-pack оно даже сгенерирует весь этот JS и подготовит для публикации.
Создание модуля WebAssembly с помощью Emscripten, AssemblyScript и Rust