Pull to refresh

Comments 10

Не, Oz нужен чтобы финальный wasm бинарник не был слишком монструозным и его загрузка в памяти была минимальной. До wasm - размер js. O3 не даст сильного прироста к производительности в случае wasm. Разве что если только вручную через clang компилировать в таргет wasm-unknown-unknown.

Сарказм:

Позволю трактовать график быстродействия так:

Автор знает rust примерно в 5 раз лучше, чем assemblyscript или C++. Уж больно большой отрыв, и 2-3 места практически одинаковы.

И спасибо за сравнение, интересная тема.

По отсутствию идиоматического кода и переизобретение уже существующих API я бы сказал, что автор знает Rust также плохо как и C++. Просто так сложилось, что многие оптимизации у Rust из коробки, включая возможность SIMD. Код на плюсах более неравномерно выделяет память, в отличие от Rust - есть Vec::with_capacity в rust, но vector::reserve я так и не нашёл, а capacity явно указан не везде одинаково.

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

Если разбирать местный код:

  1. 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 и подготовит для публикации.

Sign up to leave a comment.

Articles