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

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

НЛО прилетело и опубликовало эту надпись здесь
Я пробовал низкоуровневые вещи написать на rust, все же он пока не готов, всплывают некоторые вещи, например проблемы с alloca.
rust в общем случае не должен уступать по скорости C

Это почему это? В Rust используется bound checking при обращении к массивам по индексам, тогда как в C — нет. В ряде случаев Rust-овый компилятор способен избавиться от таких проверок (например, при итерациях), но не всегда.

Плюс в Rust-е есть Drop-ы, которые аналоги плюсовых деструкторов, и Drop-ы должны вызываться при выходе из скоупа. Что так же не бесплатно.

Плюс в Rust-е практикуется возврат Result-ов, т.е. пар значений. И в Result-е запросто может оказаться динамически созданный объект на месте Err. Что так же не дешево.

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

Скорее в общем случае Rust должен хоть немного, но отставать от C.
Что-то вы написали вроде и по делу, но не в тему. Абстракции в Rust точно так же как в С++ нулевой стоимости. Сам по себе компилятор не будет ни с того ни с сего добавлять сложность. Пары значений ни при каком раскладе к динамически создаваемому объекту не приведут. Только если вы явно их положили в кучу. В обычном случае же это будет или запись в стек или возврат в регистрах (гуглить по reg struct return). Дропы тоже будут использоваться только там где они реально нужны.

Если уж сравнивать числодробительные возможности, то тогда надо смотреть на бенчмарки. Особенно забавно выглядит первая десятка в K-Nucleotide. И это притом что в Rust-е на данный момент еще только предстоит написать те оптимизации, которые будут на 100% пользоваться гарантиями его системы типов.
как в С++ нулевой стоимости

Абстракции в C++ далеко не нулевой стоимости.
Пары значений ни при каком раскладе к динамически создаваемому объекту не приведут.

Я этого и не утверждал. Только вот тот, кто вызывает метод f() не может знать, положит ли метод f() в результат простой Err или же это будет созданный динамически объект, который реализует нужный трайт.
Дропы тоже будут использоваться только там где они реально нужны.

Наличие паник подразумевает, что должен быть какой-то механизм автоматического раскручивания Drop-ов при выбросе паники по аналогии с плюсовыми деструкторами и исключениями. Это не бесплатно, даже если паники не бросаются.
Если уж сравнивать числодробительные возможности, то тогда надо смотреть на бенчмарки.

Речь шла про общий случай, а не про числодробилки.
Только вот тот, кто вызывает метод f() не может знать, положит ли метод f() в результат простой Err или же это будет созданный динамически объект, который реализует нужный трайт.

Вот этого не может быть. Нельзя положить "какой-то объект реализующий трейт" по значению: размер объекта должен быть известен. Так что мы всегда увидим или конкретный тип или Box<Error>.

> Так что мы всегда увидим или конкретный тип или Box<Error>

И какой тип получается вот в этом примере из стандартной документации? Неужели Box<Error>?

Нет.


Лучше смотреть вот сюда: parse возвращает конкретный (ассоциированный) тип из трейта FromStr. Какой именно — зависит от того, что (вернее, во что) парсим. Для bool это будет ParseBoolError и т.д.

Странно, у меня почему-то было ощущение (явно с чьих-то слов) о том, что в Result Err может быть именно что трейтом, а не конкретной структурой.

Возможно, ошибаюсь.
Result<T, E> это практически тот же шаблон что в C++. Так что в принципе можно объявить любой тип в качестве E.

Возможно, вы обратили внимание на какой-то из вариантов, где использовались trait объекты.

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


Положить трейт в Err можно, но это будет уже Result<T, Box<Error>>, в общем, из сигнатуры можно делать однозначные выводы.

Вы похоже не понимаете, что такое zero cost abstraction.
Абстракции в C++ далеко не нулевой стоимости.
Если вам нужно вызвать функцию напрямую, то это будет ровно тот же самый call, что в C что в C++ что в Rust. Если вы заранее не знаете адресата (косвенный вызов), то опять же все три приведут одинаковой задержке. При всем желании C тут не сможет быть быстрее. Методы объектов Glib как пример.

В C++ может быть оверхед по объему кода при инстанциировании шаблона. То же самое и в Rust. Но это цена за возможность мономорфизации кода и оптимизаций при инлайнинге. В C шаблонов нет, но это не значит, что он будет быстрее в этом случае (инлайнинг в C++/Rust наоборот может оказаться быстрее). Наконец, и в C++ и в Rust можно написать код в plain C стиле и получить ту же производительность.

Я этого и не утверждал. Только вот тот, кто вызывает метод f() не может знать, положит ли метод f() в результат простой Err или же это будет созданный динамически объект, который реализует нужный трайт.
Неверно. Это записано в типе функции и известно статически уже на этапе компиляции.

Речь шла про общий случай, а не про числодробилки.
Вы говорите про общий случай, но упоминаете почему-то частности.

Наличие паник подразумевает, что должен быть какой-то механизм автоматического раскручивания Drop-ов при выбросе паники по аналогии с плюсовыми деструкторами и исключениями. Это не бесплатно, даже если паники не бросаются.
Опять мимо кассы. Паники построены с помощью механизма исключений LLVM на базе Itanium EABI. В отличие от setjmp/longjmp этот механизм не вносит задержек при нормальном развитии событий. Оверхед возникает только при фактической диспетчеризации исключения.
Вы похоже не понимаете, что такое zero cost abstraction.

Да куда уж мне.
Вы говорите про общий случай, но упоминаете почему-то частности.

А вы подумайте, как эти частности скажутся в сумме.
Опять мимо кассы.

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

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

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

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

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

Я не предлагаю верить. Я предлагаю смотреть на конкретные примеры. А там код с абстракциями может уже быть как медленнее так и быстрее наивной реализации. It depends.
Вот как раз верить во что-то, без каких либо оснований, это и есть настоящий фанатизм.

Ну так споры на счет скоростей C и C++ ведутся уже не одно десятилетие. И опыт показывает, что на C++ можно получить код даже быстрее, чем на C, но это не в общем случае.

Теперь те же самые споры будут на счет C и Rust. С ожидаемым результатом.
Стоит все же упомянуть, что и C и C++ с точки зрения теории типов находятся ниже Rust.

Если говорить совсем точно, то C можно описать с помощью простого типированного лямбда исчисления (с дополнениями). На уровне типов он ничего особенного не предоставляет.

C++, являясь тьюринг полным и на уровне системы типов, чуть сложнее: его можно представить простым типированным λ-исчислением на уровне термов и простым нетипированным λ-исчислением на уровне типов. Нетипированное оно потому, что в С++ нет возможности задать ограничения параметры шаблонов. То что в комитете называют концептами и уже несколько версий не могут сделать.

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

Круче только языки с продвинутыми системами типов на базе λ-исчисления высших порядков: Haskell, Coq, Agda и прочее.

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

P.S.: Кому интересна эта тема, может посмотреть мой доклад на одной из конференций C++ Siberia.
Синтетический пример:

extern fn foo(input: &T);

fn bar(input: &T) { ... }

fn baz(input: &T) {
    foo(input);
    bar(input);
    foo(input);
}

Здесь в коде есть одна внешняя функция с неопределенным контрактом и две внутренние которые пользуются ссылкой на T.

В Rust если функция принимает &T это значит, что объект на момент выполнения является замороженным и гарантировано не будет меняться где-то еще. Поэтому оптимизатор может с чистой совестью один раз прочитать значение из памяти и положить его в регистр (если это выгоднее и не создаст нежелательного register pressure).

Функция bar() сможет быть заинлайнена внутрь baz() и пользоваться тем же значением из регистра, потому что компилятор может быть уверен, что input случайно не поменяется (interior mutability — отдельная история). То есть компилятору тут даже escape анализ проводить не нужно, чтобы это понять.

Разумеется в C++ все сильно сложнее и в общем случае const T& не является достаточным основанием для подобных оптимизаций. Алиасинг указателей вообще больная тема.
потому что компилятор может быть уверен, что input >случайно не поменяется (interior mutability — отдельная >история)

А почему можно так лихо убрать из рассуждений interior mutability, вполне может быть:


struct T {
    data: RefCell<u32>,
}

fn foo(input: &T) {
    let mut data = input.data.borrow_mut();
    *data += 1;
}

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

То, что внутри immutable объекта есть указатель по существу не сильно влияет. Само значение указателя, его можно сложить в регистр и не трогать. Можно так же инлайнить.
Вроде бы…
Все проще. Вводя unsafe, вы грубо говоря, подписываетесь под тем, что обеспечите все необходимые условия для корректной работы.

Если вы объявили тип T как Send+Sync то это означает, что вы позаботитесь о синхронизации и видимости.

В однопоточном случае это значит, что вы динамически проконтролируете баланс ссылок на объект. По этой причине RefCell кидает панику если при попытке взять мутабельную ссылку счетчик shared ссылок ненулевой.

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

А может и не распорядиться и работать с трайтами, как с таблицей виртуальных функций, а так же выполнять все bound checks и возвращать через Result пользовательские enum-ы размером в сотни байт, при этом ничуть не избавляясь от цепочки if-ов, которые спрятаны за синтаксическим сахаром try! и?..
Я вас в упор не понимаю.

Я пытаюсь говорить о фундаментальных преимуществах, которые может дать система типов Rust, аналогов которых в С/C++ нет. Вы — про текущие недостатки реализации.

Какой-то бессмысленный спор выходит.
Давайте вернемся к истокам спора. Началось все с фразы:
rust в общем случае не должен уступать по скорости C

На мой взгляд, это утверждение неверно. Т.к. в общем случае (а не тогда, когда какой-то код затачивается под максимальную производительность в микробенчмарке посредством ухода в unsafe) пользователи будут пользоваться преимуществами Rust-а, как то:
— встроенные проверки выхода за пределы векторов;
— более высокие уровни абстракции, т.е. trait-ы, которые в ряде случаев будут работать так же, как и виртуальные методы в обычных ОО-языках;
— RAII через Drop, в том числе и при паниках;
— возврат значений (в том числе и не маленьких) через Result, вместо использования привычных для C-шников параметров-указателей.

Все эти вещи не бесплатны. И пусть их стоимость невелика, она все таки не нулевая. Плюс к тому, эти вещи позволяют решать более сложные задачи, что в итоге приводит к тому, что программист оперирует на более высоком уровне абстракции, смиряясь с некоторыми неоптимальностями внизу. Это нормально, поскольку потеря нескольких процентов производительности — это небольшая цена за сокращение сроков разработки, повешение качества и надежности.

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

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

Ну и да, речь не про фундаментальные преимущества, которые _могут_, а про постылую обыденность, в которой идеальный быстрый код пишут словами в камментах на профильных форумах.
RAII через Drop, в том числе и при паниках;

Но ведь в C вы всё-равно будете освобождать ресурс тогда, когда он вам не нужен. Имеено об этом идёт речь, когда говорят о zero-cost abstractions. То есть, корректная программа на C не будет ничем быстрее корректной программы на, например, C++ с деструкторами. Просто если вы используете C вам придётся те же самые деструкторы вызывать в явном виде, что намного сложнее, или же и вовсе невозможно.

Не так все просто. Во-первых, в том же C++ деструкторы не всегда инлайнятся. Т.е. если где-то в коде написано что-то вроде:
void f() {
  File a(...);
  ...
  File b(...);
  ...
} // (1)

а в каждом деструкторе делается вызов close(), то код с деструкторами будет чуть-чуть дороже, чем код с прямым вызовом close():
void f() {
  int a = open(...);
  ...
  int b = open(...);
  ...
  close(b);
  close(a);
}

Во-вторых, запись действий по очистке вручную может позволить записать действия более компактно. Т.е. если в коде на C++ между вызовами деструкторов a и b пройдет вызов еще нескольких деструкторов, то данные и код для вызова деструктора b уже могут уйти из кэша. Тогда как в C может быть записано что-то вроде:
void f() {
  int a, b;
  ...
  a = open(...);
  ...
  b = open(...);
  ...
cleanup:
  ... // Какие-то другие действия.
  close(a);
  close(b);
}

Так что на практике выплывают некоторые мелочи из-за которых незначительное преимущества в скорости у C все-таки образуются.

Другое дело — стоят ли они того…
Во-первых, в том же C++ деструкторы не всегда инлайнятся.

Если они не инлайнятся — значит, компилятор считает, что так будет быстрее и вполне возможно, что он прав.


Т.е. если в коде на C++ между вызовами деструкторов a и b пройдет вызов еще нескольких деструкторов, то данные и код для вызова деструктора b уже могут уйти из кэша.

Какая-то странная ситуация. Если там столько всего произошло между этими двумя вызовами то у вас и на С данные точно так же "остынут". Какая разница, как это записать?


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

Если они не инлайнятся — значит, компилятор считает, что так будет быстрее и вполне возможно, что он прав.
Или в принципе не может это сделать, т.к. они лежат в отдельной dll-е.
Какая разница, как это записать?
Ну если вы не увидели разницы между приведенными двумя примерами кода, значит ее нет, а я во всем неправ.
Или в принципе не может это сделать, т.к. они лежат в отдельной dll-е.

Если у вас функция деинициализации лежит в отдельно библиотеке то вы и в C будете бессильны.


Ну если вы не увидели разницы между приведенными двумя примерами кода, значит ее нет, а я во всем неправ.

Ну я признаю, что если у вас перед вызовами close() идёт, например, особождения массива динамически выделенных объектов, то данные могут действительно уйти из кэша. Просто когда я представляю себе гипотетически такую ситуацию и представляю, что я пишу этот код на C, я понимаю что не знаю как его лучше написать. Вообще не представляю, какой вариант будет быстрее. И вряд ли вам кто-то скажет точно кроме серии бенчмарков. Отсюда вывод — потенциально вы может и можете написать на C более быстрый код. На практике, не изучая конкретный случай, компилятор, процессор — нет, не можете.

Никто не против потерять 1% в скорости при гарантиях data safe. В этом и фишка.
А разве кто-то против этого спорит? Иметь гарантии безопасности от Rust-а при разнице в скорости в районе процента-двух — это просто замечательно.
Ну вот и спорить о том, какой конь сферичеснее в вакууме тоже не стоит.
Rust — это самое прекрасное, что создала компиляторная индустрия за последние лет 10.
То мы говорили о скорости исполнения кода, а теперь уже о размере.
Ну справедливости ради размер кода влияет на его скорость его исполнения из-за наличия кеша.

Landing pads же не разбавляют каждый метод, равномерно его раздувая. А будет call/ret на 12k от текущей позиции или на 13k — разницы практически никакой.


Ну и для желающих от них полностью избавиться, можно запретить panic'и (использовать panic = abort вместо panic = unwind).

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

Я потому и говорю, что не существует никакого «общего случая». Всегда надо смотреть на конкретный пример.
Я так понимаю, просмотр таблиц идет только при панике? При нормальном развитии событий никакой потери производительности, при этом допущении, нет?
Да, это просто статические данные, как и строковые литералы. Только пожалуйста не забывайте, что сам механизм диспетчеризации исключения является очень медленным.
Первый вариант реализации нелокального возврата из функции в моей JIT VM был сделан с помощью выброса исключения. Для прототипа большего и не надо было, да и на первый взгляд, инструмент был самый подходящий: бросаем исключение в дебрях, в объекте исключения кодируем некоторым образом информацию о точке, докуда надо размотать стек и выбрасываем его. Остальное сделает автоматика.

Потом на практике оказалось, что кумулятивно, одна только эта операция по скорости отбрасывала JIT код далеко назад даже по сравнению с софтовым исполнением! То есть буквально: нет инструкций blockReturn — JIT в 50 раз быстрее. Есть — настолько же медленнее.

После того, как вместо выбрасывания исключения результат вызова метода я стал кодировать в паре регистров (обычный результат и контекст нелокального выхода, если таковой есть) JIT VM стала опережать софтовую VM на всех типах инструкций.

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

The main model used today for exceptions (Itanium ABI, VC++ 64 bits) is the Zero-Cost model exceptions.… the Zero-Cost model, as the name implies, is free when no exception occurs

http://stackoverflow.com/questions/13835817/are-exceptions-in-c-really-slow
К сожалению, пока не знаю Rust, но по ходу статьи читал о преобразованиях типов попутно при переложении алгоритма под Node.

Я подозреваю, что большая потеря может происходить при перехода от v8 string -> bytes -> adler -> string -> bytes -> v8 complementary type.

По крайней мере, я бы копал в направлении работать напрямую с входными байтами в представлении v8, отдавать родную строку в v8 (речь ведь о hex).
Попробовал вызвать библиотечную версию функции из раста — получил среднее время на уровне 3,5 секунд. То есть, издержки на преобразования достаточно большие. Но мне кажется. что даже если их убрать совсем, то разница между вызовом в рамках раста и по тому или иному интерфейсу всё равно будет раза в два. То есть, будет примерно как в питоне ±. Впрочем, это конечно всё равно привлекательно, поскольку получается, что даже современный весьма хорошо оптимизированный js на порядок медленнее, чем вызываемая раст-функция через все эти обёртки и преобразования.
В ряде случаев Rust-овый компилятор способен избавиться от таких проверок (например, при итерациях), но не всегда.

Там, где не может компилятор, но очень хочется, то можно сделать руками через get_unchecked.


и Drop-ы должны вызываться при выходе из скоупа. Что так же не бесплатно.

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


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


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

Там, где не может компилятор, но очень хочется, то можно сделать руками через get_unchecked.

Это будет не общий случай.
что в общем случае код на расте будет безопаснее

Это подразумевается по умолчанию. Если намеренно игнорировать безопасность Rust-а, смысла в его использовании нет.
Это будет не общий случай.

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


Если намеренно игнорировать безопасность Rust-а, смысла в его использовании нет.

Как сказать. Если выбор между С и Rust, то даже в гипотетической ситуации, когда у нас код на 90% состоит из unsafe блоков, я бы всё равно предпочёл последний. Если, конечно, нет других факторов.

Если не нужен произвольный доступ и известен size_hint, то итераторы тоже не будут проверять границы

НЛО прилетело и опубликовало эту надпись здесь
Тогда забываем «в общем случае». В общем случае в Rust-е не будут использоваться unsafe-блоки и mem::forget.
НЛО прилетело и опубликовало эту надпись здесь
Интересное определение общего случая. Тогда понятно, почему у вас в общем случае Rust имеет производительность C.
НЛО прилетело и опубликовало эту надпись здесь
Ну логично при сравнении двух языков сравнивать те варианты использования языка, которые показывают лучший результат в каждом из них при одинаковой функциональности полученных программ. Если «лучший» значит «более производительный» (как в данном контексте), то в Rust есть способы писать код так, что бы он не уступал C по производительности. При этом, следующим вопросом может быть, будет ли такой производительной Rust-код проигрывать C-коду по другим критериям (удобство написания и поддержки, например). Я не знаю случаев, когда Rust-код будет уступать по этому критерию C-коду, хотя может они и есть.
Общий случай — это когда используются самые распространенные практики и приемы. Переход в unsafe в Rust-е для выжимания производительности вряд ли можно считать распространенной практикой.
Расскажите пожалуйста, какова распространенная практика сортировки массива в С?
Подозреваю, что это намек на то, что обобщенные реализации sort-а быстрее qsort с косвенными вызовами.

Повторю то, что уже говорил:
При этом в Rust-е вполне можно, если задаться целью, получить и более быстрый код, чем на C (в C++ это так же возможно и продемонстрировано), но речь будет идти не про общий случай, а про частную задачу, вероятно, не очень большого объема.

Какой будет следующий намек?
Зачем вы увиливаете от ответа?
Вы спорите с утверждением:
rust в общем случае не должен уступать по скорости C


Я вас спрашиваю, какая реализация сортировки в общем случае используется в С?
В расте в общем случае используется обобщенная. И что то мне подсказывает что в С в общем случае используется qsort который легко может оказаться на порядок медленнее. В результате раст не то что не уступает в общем случае, а оставляет С далеко позади.
Ну а если задаться целью, то да, можно и еще более быстрый код получить, но мы же не об этом сейчас?
Зачем вы увиливаете от ответа?

Ответ вам был дан. Почему вы не можете его прочитать и понять?
Я вас спрашиваю, какая реализация сортировки в общем случае используется в С?

Тогда позвольте вас спросить: мы будем под общий случай выдавать конкретную ситуацию? Тогда давайте посмотрим на такой вот «общий» случай:
void f(char * b, size_t i) { b[i] = 0; }

Значения b и i определяются в run-time, заранее они не известны.

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

— в Rust-е не так уж много особенностей, которые бы не позволили компилятору Rust-а сгенерировать такой же эффективный код, как и компилятору C. Одна особенность — это включенный по умолчанию bounds checking, вторая — это генерация таблиц для обработки panic (хотя это лишь косвенное влияние оказывает). А так, в принципе, у компилятора Rust-а достаточно информации, чтобы сгенерировать даже более оптимальный код;
— однако, производительность кода будет определяться не столько возможностями компилятора, сколько программистом. Rust является языком более высокого уровня, позволяет решать более сложные задачи, что дает возможность разработчику использовать более высокие уровни абстракции. Чем выше, тем меньше внимания уделяется тому, что внизу, откуда и проявляется некоторый проигрыш в производительности/ресурсоемкости по сравнению с более низкоуровневыми языками. Это не уникально для Rust-а, это уже проходилось, на моей памяти, как минимум с Ada, Eiffel и C++ (если говорить про то, что транслируется в нативный код);
— под общий случай лучше брать не какой-то микробенчмарк и уж тем более не часть микробенчмарка (вроде упомянутой вами сортировки), а решение какой-то большой задачи. Например, реализация MQ-шного брокера (хоть MQTT-брокера, хоть AMQP) или сервера СУБД. На таком объеме языки более высокого уровня сильно выигрывают в трудозатратах, качестве и надежности, но на какие-то проценты проигрывают в производительности тому же C (причины см. в предыдущем пункте). Если не верите, попробуйте пообщаться с разработчиками PostgreSQL или Tarantool-а.
от проверки границ можно избавиться при помощи арифметики указателей в unsafe-блоках

К счастью, от проверки границ можно избавиться более простым способом. (:

Интересный опыт, как насчет опубликовать это как npm пакет?

В принципе, это наверное можно сделать. единственное, я пока не совсем хорошо понимаю, как там правильно сделать кросс-платформенную сборку самим npm всего этого дела вместе с растовским кодом. Если для сборки с/с++ там есть node-gyp, то для сборки раста надо как-то cargo вызывать. Или затаскивать сразу под все платформы уже скомпилированные библиотеки? Но это как-то мне кажется перебор, ради такой плёвой функции. хотя, можно было бы подумать над тем, чтобы сделать какую-то обёртку, которая удобно подключала бы через аддоны библиотеки, совместимые с FFI. Но это немного другая уже история.
Если бы ещё было сравнение Python + Rust
Дополнил статью
Опять же, можно посмотреть бенчмарки и сравнить. Там и на питоне реализации есть.
Там сравнение скорости вычислений, и понятно, что у компилируемых языков или языков с JIT она будет быстрее.
Ну разумеется. А разве автор поста что-то другое делал? Хеширование — самая что ни на есть вычислительная задача.

Какое еще сравнение вы ожидаете увидеть?

Думал, что для nodejs есть https://github.com/neon-bindings/neon. Было бы любопытно взглянуть на его результат. Или это что-то другое?

neon — это несколько иная штука. Они предоставляют абстракции только для node.js. То есть, код который будет написан на расте с использованием neon — теряет универсальность. Библиотечка, которую я там написал, подключается и к node.js и к python и другим — по универсальному интерфейсу. Впрочем, есть смысл попробовать, возможно он был бы быстрее за счёт лучшей адаптированности под задачу

имхо не стоит использовать неон для подключения либы. Он скорее нужен когда надо ускорить уже существующий js код, то есть какой то кусок тупит, его надо вынести в раст, но при этом не потерять обвязку из формата объектов/классов. Чисто диванно-теоретическое имхо.
НЛО прилетело и опубликовало эту надпись здесь
Да, прошу прощение. Должно быть:

-> *mut c_char
НЛО прилетело и опубликовало эту надпись здесь
Откровенно говоря, я ещё не до конца проникся всеми тонкостями работы с типами раста, но конкретно в данном случае, я опирался вот на этот пример: http://jakegoulding.com/rust-ffi-omnibus/string_return/
И именно в этом виде, с *mut оно компилируемая и работает. Если правильнее сделать как-то иначе, то будет любопытно узнать.
НЛО прилетело и опубликовало эту надпись здесь
Правильно ли я понимаю, что это касается вот этой части кода на с++
char * result = adler(url);
  info.GetReturnValue().Set(Nan::New<String>(result).ToLocalChecked());
  free(result);

То есть, вот этот вот free — вызывать опасно?
Но вроде как тут как раз не должно быть разных аллокаторов именно по причине того, что код вызывается сначала в C++, а уже потом значение передаётся в ноду. А они используют, насколько я понимаю, один и тот же аллокатор. И в документации на этот счёт как раз сказано, что при компиляции в библиотеки всё должно работать корректно.
Dynamic and static libraries, however, will use alloc_system by default. Here Rust is typically a 'guest' in another application or another world where it cannot authoritatively decide what allocator is in use. As a result it resorts back to the standard APIs (e.g. malloc and free) for acquiring and releasing memory.

Короче, говоря, освобождать отданное значение на вызывающей стороне — это должно быть вполне корректным решением.
НЛО прилетело и опубликовало эту надпись здесь

Если я правильно понял, там говорится про разные кучи, а не про разные аллокаторы. Впрочем, есть и другой момент — там идёт речь о том, как взаимодействуют библиотеки написанные на С/С++. И в этом смысле, у меня есть подозрение, что логика работы может отличаться. В данном случае, было бы разумно поставить под сомнение и то, что написано в документации и то, что написано на стэке. Существует ли воспроизводимый сценарий получить проблему? Если да, то можно было бы поставить какой-то эксперимент и посмотреть, что будет. Если откровенно, то мне вообще не вполне понятно, что именно должно в данном случае пойти не так.

Выброси ты эту ноду, нагородил тут. Пошел бы лучше PR в Rocket сделал.
Пусть они сначала своё API стабилизируют и переведут его на асинхронный режим работы. Они обещали это сделать в будущем. Кроме того, выкинуть-то я её может быть и выкинул бы, но кто мне старые проекты перепишет с ноды и питона на раст?

Тут я как раз описал некоторый такой вариант диффузной миграции. С его помощью можно постепенно наработать некоторый объём нужного мне функционала и как-то попривыкнуть к местным обычаям. А в какой-то момент просто окажется, что всё, что мне нужно — есть в расте и я понимаю как это эффективно использовать. Вот тогда-то можно будет и выкинуть что-то. И то, что-то всё равно останется, просто потому как есть проекты переменчивые, а есть в стиле «Работает — не трогай», которые я годами не обновляю, поскольку просто нет нужды. Зачем их переписывать? Что я с этого получу?

А главное, как я буду объяснять людям, которые работают со мной совместно, что я взял и выкинул ноду? Они-то раста не знают. Подключаемую библиотеку они ещё могут воспринять, а полный отказ от ноды будет означать отказ ещё и от их участия в проекте. К чему такой тоталитаризм?
Есть такой проект Neon https://github.com/neon-bindings/neon для написания модулей для ноды на rust. Можете тоже попробовать для интереса. Я пробовал (https://github.com/OrKoN/base-x-native) и получилось быстрее, чем js реализация для моего случая. Правда не сравнивал с другими нативными реализациями.
А вы не пробовали сравнить скорость такого подключения модуля к nodejs и конвертации его в WebAssemply код?

Должно получиться интересно

Getting Started With WebAssembly in Node.js
Да, это любопытно. Вчера как раз в «This Week in Rust» пиарили такую вот штуку: https://davidmcneil.github.io/the-rusty-web/

Там есть бенчмарки, которые выглядят… любопытно. Это пожалуй стоит попробовать.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории