LINUX.ORG.RU

Сравнение Rust и C++ на примере трассировщика путей

 ,


6

2

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

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

Разработка трассироващика путей на Rust`е, часть 7: Заключение

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

Картинка

Для начала, пример вывода трассировщика путей! Захардкоженная сцена выглядит вот так:

http://ruudvanasseldonk.com/images/robigo-luculenta.png

Если вам интересно что-то в ней изменить, то смотрите set_up_scene в app.rs.

Начало работы с Rust

В настоящий момент, вы можете установить компилятор Rust`а и Cargo за пару минут, даже в Windows. Да и заставить их работать было намного проще, чем, например, Scala и sbt.

Сообщество Rust`а показалось мне очень дружелюбным. Когда я не знал что делать, мне очень помогали IRC канал и /r/rust. Члены основной команды разработки языка есть и там и там, так что часто советы были весьма профессиональными.

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

Владение

Если бы мне надо было описать Rust одним словом, это было бы «владение». Для меня, это то, что отличает Rust от остальных языков. В большинстве языков владение неявно и это приводит к нескольким типам ошибок. Когда в Си функция возвращает указатель, кто ответственен за освобождение памяти? Вы можете ответить на этот вопрос без подглядывания в документацию? И даже если вы знаете ответ, то все равно легко забыть освободить память или освободить ее дважды.

Проблема относится не только к указателям, она касается все ресурсов. Может показаться, что сборка мусора это отличное решение, но она работает только для памяти. Тогда вам нужен другой способ для освобождения ресурсов (вроде файловых дескрипторов) и все проблемы возвращаются. Например, сборщик мусора в C# спасает от ошибок «использования после освобождения» (use after free), но ничего не спасает вас от ошибок «использования после удаления» (use after dispose). Разве ObjectDisposedException намного лучше сегфолта? Из-за явного времени жизни и системы владения в Rust нет этих типов ошибок.

прим. ozkriff: поскольку я с C# знаком мало, то пришлось загуглить про этот ObjectDisposedException. Вот пример кода:

using System;
using System.IO;

public class ObjectDisposedExceptionTest 
{
   public static void Main()
   {     
      MemoryStream ms = new MemoryStream(16);
      ms.Close();
      try 
      {
         ms.ReadByte();
      }
      catch (ObjectDisposedException e) 
      {
         Console.WriteLine("Caught: {0}", e.Message);
      }
   }
}

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

Обновление Luculentus

Сами по себе достоинства явного владения не уникальны для Rust`а. Такой же код можно написать на современном C++, который очень сильно отличается от С++ до-2011. Когда я писал Luculentus, C++11 поддерживался только частично. Я использовал много простых указателей, которые сегодня уже не нужны. Я заменил большинство простых указателей в Luculentus на shared_ptr или unique_ptr, а массивы на векторы. Как следствие, все ручные деструкторы пропали. (Раньше их было шесть). Раньше было 11 операторов удаления, теперь их нет. Все управление памятью стало автоматическим. Это сделало код не только короче, но и снизило вероятность появления ошибок.

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

Но все же, обновление демонстрирует, что возможно писать относительно безопасный код и на С++. Вы получаете безопасность и автоматическое управление памятью практически без накладных расходов. Единственной проблемой является то, что вы должны очень настойчиво стремиться к этому. Вы можете использовать unique_ptr, но так же можете и простой указатель. Все опасные инструменты «старого» С++ все еще доступны и вы можете смешивать их с новым С++. Конечно, есть определенная ценность в возможности собирать старый код (Бьерн называет это достоинством), но я бы предпочел не смешивать неявно эти две парадигмы и не поддерживать старые ошибочные решения. Требуется некоторое время, что бы разучиться использовать new и delete, но даже тогда старые API останутся с нами на очень долгое время.

Новое начало

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

Другим моментов, в котором Rust сделал правильный выбор, является мутабельность. В Rust`е все по-умолчанию неизменяемо, когда как в C++ наоборот. В коде Luculentus 535 раз появляется «const» (на момент написания). В Robigo Luculenta всего 97 «mut». Конечно, в C++ больше дублирования, но это все равно показывает, что неизменяемость по-умолчанию - лучше. Так же, компилятор Rust выдает предупреждение о переменных, которым не нужно быть изменяемыми, это тоже хорошо.

Несмотря на то, что синтаксис является делом вкуса, есть и измеряемые величины. Если я сравню количество непробельных символов в коде, то у С++ будет примерно 109 тысяч символов (не считая файлы, которые я не портировал на Rust), а у Rust - 74 тысячи. Почти на треть меньше.

C++ славится своими информативными и понятными сообщениями об ошибках, когда что-то идет не так в шаблонном коде. Ошибки в Rust`е, в основном, намного более понятны, но некоторые тоже могут напугать:

error: binary operation `/` cannot be applied to type `core::iter::Map<'_,f32,f32,core::iter::Map<'_,&[f32],f32,core::slice::Chunks<'_,f32>>>`

Производительность

Я добавил базовые счетчики производительности в Luculentus и Robigo Luculenta. Они считают количество завершившихся задач трассировки (trace tasks) в секунду. Вот результаты:

Компилятор              платформа           производительность

GCC 4.9.1*              Arch Linux x64      0.35 ± 0.04
GCC 4.9.1               Arch Linux x64      0.33 ± 0.06
rustc 0.12 2014-09-25   Arch Linux x64      0.32 ± 0.01
Clang 3.5.0             Arch Linux x64      0.30 ± 0.05
msvc 110                Windows 7 x64       0.23 ± 0.03
msvc 110*               Windows 7 x64       0.23 ± 0.02
rustc 0.12 2014-09-23   Windows 7 x64       0.23 ± 0.01

Везде выставлены самые высокие уровни оптимизации. Компиляторы со звездочкой использовали PGO (Profile-guided optimization - оптимизация, управляемая профилированием https://ru.wikipedia.org/wiki/Profile-guided_optimization). Единственный вывод, который я могу сделать из этого, что вам, наверное, не стоит использовать Windows для сильно нагружающих процессор приложений.

Во второй статье из этой серии я отметил, что код на Rust`е собирается очень быстро, но тогда было не много кода. Сейчас время сборки вот такое (в секундах):

Компилятор             Время

rustc 0.12 2014-09-26  7.31 ± 0.05
Clang 3.5.0            13.39 ± 0.03
GCC 4.9.1              17.3 ± 0.5
msvc 110               20.4 ± 0.3

Сборка теперь не так быстра, но все равно быстрее С++.

Заключение

Изучать Rust было интересно. Мне понравился язык и портирование привело к нескольким озарениям, которые могут улучшить и оригинальный код. Владение часто неявно в других языках, что увеличивает чувствительность кода к человеческим ошибкам. Rust делает владение явным, убирая возможность допущения подобных ошибок. Все безопасно по умолчанию. Все это сдвигает Rust намного ближе к краю «стабильность» на спектре, чем к краю «быстрая разработка». Я не написал на Rust`е достаточно кода, что бы быть на 100% уверенным, но пока что достоинства Rust`а перевешивали его недостатки. Если бы я выбирал между C++ и Rust`ом для своего следующего проекта, то выбрал бы Rust.

Ну как, это тянет на «нормальное» сравнение? По-моему, в любом случае интересно почитать.

а чем трассировщик путей отличается от трассировщика лучей?

Bad_ptr ★★★ ()

Ок. Как я заметил Шланг пока быстро собирает медленную фигню.
Ну хоть собирает и то хорошо...

Stahl ★★☆ ()

А на других задачах как эта ржавчина?

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

Я не разбираюсь в этом, но русская википедия говорит, что «Трассировка пути является обобщением традиционной трассировки лучей».

ozkriff ()

За статьи спасибо, посмотрю. Но замеры производительности наводят на мысль, что исходный код был кривоватенек... или собирался без оптимизаций.

AIv ★★★★★ ()

Фу. Тексты о ЯП не на английском выглядят как говно.

unt1tled ★★★★ ()

Спасибо за ссылки и за перевод. Остальные посты переводить не будешь?

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

Насчет производительности, недавно обновили код программ на ржавчине в benchmarksgame, вроде совсем не так и плохо:

http://benchmarksgame.alioth.debian.org/u64q/benchmark.php?test=all&lang=...

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

Вот картинки, на всякий, а то на этом benchmarksgame сегодня одно, а завтра уже или другое или вообще ничего :( .

ozkriff ()

Годно. С нетерпением жду следующий безопасный ЯП.

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

Ок. Как я заметил Шланг пока быстро собирает медленную фигню.
Ну хоть собирает и то хорошо...

Собственно, почему «медленную фигню»? Можно более развернуто?

ozkriff ()
Ответ на: комментарий от ozkriff
GCC 4.9.1*              Arch Linux x64      0.35 ± 0.04
GCC 4.9.1               Arch Linux x64      0.33 ± 0.06
Clang 3.5.0             Arch Linux x64      0.30 ± 0.05

Больше — лучше.

Stahl ★★☆ ()

Язык без DNA inference не нужен

buddhist ★★★★★ ()

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

Dark_SavanT ★★★★★ ()

не стоит использовать Windows для сильно нагружающих процессор приложений

не стоит использовать Windows

I-Love-Microsoft ★★★★★ ()

C++ славится своими информативными и понятными сообщениями об ошибках, когда что-то идет не так в шаблонном коде.

здесь, похоже, не хватает кавычек

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

Если писать «Sarcasm» напротив каждого сарказма, он перестанет быть сарказмом. Так что все правильно написано.

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

Я тебя не так понял, подумал, что ты утверждаешь, что llvm вытягивает даже такую медленную гадость как rust и все такое.

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

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

Остальные посты переводить не будешь?

Нет, из этой серии посты точно не буду переводить. Я и эту-то случайно перевел)

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

Нет, из этой серии посты точно не буду переводить. Я и эту-то случайно перевел)

Я их уже прочитал, они более «технические», думаю, их и нет смысла переводить.

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

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

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

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

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

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

Если убрать часть про «медленную гадость» и негативный оттенок, то я соглашусь) В смысле, глупо самому чего-то микроптимизировать, если потом ты все равно скормишь это llvm, который все и сам сделает. Надо просто ему не мешать и проводить высокоуровневые языкоспецефические оптимизации, до которых llvm не додумается сам. Вроде же, все компиляторы, основанные на llvm, так-то так и делают.

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

даже сам Страуструп стебётся по этому поводу:

«Я написал программу из 4-х строки и получил столько ошибок, что они не влезли в буффер терминала».

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

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

Почему? Вот код на плюсах, если что: https://github.com/ruud-v-a/luculentus.

или собирался без оптимизаций.

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

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

В оригинале было «cryptic error messages», это я так на автомате перевел. Черт знает, выделение кавычками тут выглядит как-то совсем уныло. Лучше уж тогда не выпендриваться и перевести близко к оригиналу.

Хм, и в следующем предложении есть толстый намек: «Ошибки в Rust`е, в основном, намного более понятны, но некоторые тоже могут напугать». Информативные и понятные сообщения никого не напугают.

... тем более не все знают С++ и для них этот пассаж будет непонятен.

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

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

В статье говорится, что «Везде выставлены самые высокие уровни оптимизации»,

Добавил -flto в makefile - получил около 25% прироста к скорости, добавил -Ofast - еще 3-4%, так что автор не прав. Ну и да - код далеко не везде оптимальный, ну или выглядит не оптимальным.

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

Ой, жалкие 15%, да и то в пределах погрешности измерений. Я бы и половиной не думая пожертвовал только за теоретические бонусы от экосистемы llvm.

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

Я просто программист. Меня мало волнует «экосистема» компилятора. С моей точки зрения тот компилятор лучше, которые генерирует более шустрый код.
Скорость компиляции? Пофиг.

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

Хм, и в следующем предложении есть толстый намек

Да, и все же это может запутать несведущего человека.

Мне кажется слегка странным читать подобную статью, если ты ничего не знаешь о C++.

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

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

Интель говно и некомильфо. За поциентом уже давно замечено, что «тот компилятор лучше, который GCC».

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

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

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

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

Ну не всем же бебианы на ппц собирить.

Оно хоть на каком-то VIA вообще заведётся?

Да.

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

ICC

А, ну да. А про

if (cpuVendor.contains("AMD") {
     for (double i = 0.0d; i < DBL_MAX; i*=++i);
     usleep(6000);
}

Который порождается этим проприетарным дермищем, видимо все забыли.

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

видимо все забыли.

Да, кроме фанатиков.

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

Годно. С нетерпением жду следующий безопасный ЯП.

В смысле? Есть какой-то на примете? Или раст чем-то не нравится и есть надежды на то, что кто-то сделает лучше?

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

Вангую, что если пруф и будет, то приведённого кода там не обнаружится. И хорошо, если это будет не ссылка на хабр...

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

Подобной фигни там не обнаружилось, но ICC действительно генерирует более тормозной код (без SSE, например), если в качестве целевого процессора указан не intel. Хотя точно такая же фигня происходит, если код выполняется на более новой процессоре от тех же intel. Гуглить про ICC cpu dispatcher.

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

«Просто программист» которого ничего не волнует - это быдлокодер. А быстрый код должен волновать не тебя, а заказчика, под которого бинари можно собрать любым компилятором. А вот как раз скорость компиляции, нормальные (да, даже по сравнению с gcc 5) варнинги, кросс-компиляция из коробки, статический анализ, бОльшая строгость, более ранняя и полная поддержка нового стандарта должны бы быть по меньшей мере полезны. Это из того что уже есть сейчас.

А скорость - дело наживное. clang за свои 7 лет достиг gcc которому, на минуту, 27 лет.

slovazap ★★★★★ ()

The only conclusion I can draw from this, is that you should probably not use Windows if you want performance from CPU-bound applications.

Вот ето спермоотсос.

postman_ ★☆ ()

[..] нормальное сравнение C++ и Rust.

Когда в Си функция возвращает указатель [..]

ээээ???

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

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

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

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

Система владения в Rust`е предотвращает ошибки некорректного управления ресурсами, но она требует еще большего внимания, чем статическая типизация.

O_o

shty ★★★★★ ()

все ручные деструкторы пропали

не выдержали конкуренции с дикими собратьями?

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