Pull to refresh

Comments 50

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

А точно причина не фармацефтического свойства? :)

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

UFO just landed and posted this here

Каким там синтаксисом он больно сильно отталкивает? У Rust довольно типичный, современный синтаксис аля Kotlin, Swift, TypeScript и т.д. Местами аля Python и С#. Там почти вся экзотика упирается в :: и синтаксис замыканий |n| n+1. К этим вещам привыкаешь за неделю, и понимаешь что это даже удобно и создатели языка не дураки были когда так делали.

Могу только посочувствовать тем разработчикам, если они будут изучать Python, Ruby, Haskell, Lisp и т.д.

Там почти вся экзотика упирается в ::

И то только в "турборыбе" так как для неймспейсов :: и в С++ используется.

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

Да ладно?! Я ещё могу понять когда лисп или хаскель считают непривычными. И то вызывают удивление разработчики, которые 10 лет пишут на одном языке без какого-либо желания посмотреть на "экзотику". Помню как будучи ещё совсем зелёным джуном начитался баек про лисп ("побеждая посредственность" и т.д.). Примерно так же с хаскелем. В итоге ни лиспером ни хаскелистом не стал, но пощупать эти языки было определённо интересно. Учитывая, что некоторые фичи потом в мейнстрим приходят, то даже практическая польза в этом есть.

Возвращаясь к расту: что в его синтаксисе такого уж странного? fn foo(a: u16) -> u8 вместо byte foo(short a) сейчас уже никого не удивишь и никто свифт, го или котлин с китайским не сравнивает. Из необычного разве что лайфтаймы, но в среднем коде они встречаются относительно редко. Ну и в конце-концов, это ключевая фича.

Потому что у вас традиционная ориентация, которая помешала вам выбрать swift?

Счётчик ссылок на каждый нетривиальный тип - это не быстро

Как-будто в расте при асинхроном коде у вас не будет Arc на каждый чих

Если у вас

Во-первых, важна надежность.

Во-вторых, важна производительность.

то

Кто-то боится, что не сможет никого нанять

вообще не аргумент.

Честно скажу - с биржевой темой не знаком сильно, специфику не знаю. Могу сказать за банк.

В целом, требования похожие - надежность + производительность (терабайты данных, сотни миллионов операций в сутки).

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

Т.е. работа идет не с объектами, но с потоком данных. Немного иная модель.

По нашему опыту залогом производительности тут является два фактора:

  1. Минимизация динамической работы с памятью, максимальное использование статики, максимум всевозможных инициализаций выносится в compile time, в рантайме остается минимум.

  2. Выбор такого языка, который нативно поддерживает все типы данных, присутствующих в БД - char, varchar, decimal, numeric, date, time, timestamp с тем, чтобы можно было описать структуру данных, прочитать в нее записи из БД и потом просто работать с ее полями напрямую, не оборачивая участки байтового буфера в какие-то "объекты" с которыми потом можно осмысленно работать. Т.е. тут тоже максимум выносится в compile time (описание статических структур).

В нашем случае все это решалось изначально выбором очень специфической специализированной платформы на которой существует специфический язык, не являющийся "языком общего назначения", но ориентированный на быструю и эффективную работу с БД (как прямым доступом к записям, так и возможностью встраивать SQL запросы непосредственно в код, в том числе и "статический SQL" где план запроса формируется не в рантайме, но на этапе компиляции) и реализацию коммерческой бизнес-логики. Естественно, для работы с датой, временем, типами с фиксированной точкой тут есть свои типы данных (никаких дополнительных "объектов" не надо) и реализована вся арифметика для них (типа арифметики с датами/временем, различные операции с округлением для типов с фиксированной точкой и т.п.).

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

И да. "Готовых разработчиков" нет практически. Берем толковых людей и учим сами.

Это вы о каком языке говорите ? Накинуть указатели на готовую структуру, вроде, только в Си возможно.

Я говорю о специфической платформе IBM i (AS/400) - middleware коммерческие серверы от IBM и основном (более 80% кода тут на нем пишется) специализированном для коммерческих расчетов языке RPG.

Ничего там "накидывать" не надо. Писал об этом тут (прямая работа с БД) и тут (работа с БД через встроенный в код SQL). Просто объявляем структуру с нужными полями и передаем ее в качестве буфера-приемника в функцию чтения записи из таблицы (или в качестве хост-переменной в SQL запрос).

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

Суть в том, что в языке есть типы данных с фиксированной точкой packed и zoned (100% аналоги decimal и numeric в SQL), есть varchar, date, time, timestamp (100% аналоги соотв. SQL типов). Т.е. не надо никаких дополнительных "объектов", никаких преобразований. Просто прочитали в буфер и работаем.

Впрочем, "накинуть указатели на готовую структуру" (или переменную) тут тоже можно. Для этого при объявлении структуры (dcl-ds) или переменной (dcl-s) используется модификатор based:

dcl-ds dsMyDS likeds(t_dsMyDS) based(pMyDS);
dcl-s  zVar   zoned(7: 0)      based(pVar);

Теперь достаточно присвоить указателям pMyDS или pVar адрес какого-то байтового буфера и с ним можно будет работать как с переменной соотв. типа dsMyDS или zVar.

Вообще, структуры тут идеологически несколько отличаются от сишных. Тут это просто байтовый буфер с размеченными внутри него полями. Вы можете явно задать размер структуры (больший, нежели сумма размеров его полей), можете задавать положение поля внутри буфера (причем, поля могут перекрываться), можете объявить шаблон (template) структуры с указанием дефолтных значений каждого поля, а при объявлении переменной по этому шаблону сказать чтобы оно сразу приинициализировалось "как в шаблоне" - это будет сделано на этапе компиляции:

// Шаблон структуры
dcl-ds t_dsISODate qualified template;
  year    char(4)  inz('1900');
  *n      char(1)  inz('-');       // неименованое поле - разделитель
  month   char(2)  inz('01');
  *n      char(1)  inz('-');       // неименованое поле - разделитель
  day     char(2)  inz('01');
  ISODate char(10) pos(1);         // перекрывает все поля
end-ds;

dcl-ds t_dsEURDate qualified template;
  day     char(2)  inz('01');
  *n      char(1)  inz('.');       // неименованое поле - разделитель
  month   char(2)  inz('01');
  *n      char(1)  inz('.');       // неименованое поле - разделитель
  year    char(4)  inz('1900');
  EURDate char(10) pos(1);         // перекрывает все поля
end-ds;

// Переменная по шаблону с инициализацией "как в шаблоне"
dcl-ds dsISODate likeds(t_dsISODate) inz(*likeds);
dcl-ds dsEURDate likeds(t_dsEURDate) inz(*likeds);
dsl-s  strDate   char(10)            based(pStr);

// dsISODate.ISODate сразу содержит строку '1900-01-01'
// dsEURDate.EURDate сразу содержит строку '01.01.1900'

dsISODate.year  = '2023';
dsISODate.month = '10';
dsISODate.day   = '20';

// dsISODate.ISODate теперь содержит строку '2023-10-20'

// Присвоение значений полей одной структуры полям с такими же именами другой структуры
eval-corr dsEURDate = dsISODate;

// dsEURDate.EURDate теперь содержит строку '20.10.2023'

pStr = %addr(dsISODate);

// strDate теперь '2023-10-20'

pStr = %addr(dsEURDate);

// strDate теперь '20.10.2023'

Ну вот как-то так...

что в нем гарантированно нет undefined behaviour

Это не совсем верно - количество известных UB сокращено, однако существуют конструкции, которые пока всё ещё не определены - там проблемы из теории типов всплывают. Ну и честной спеки раста пока не существует, так что-то пока всё поведение - implementation defined.

А можно пример UB в Rust без использования unsafe?

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

Можно посмотреть TWIR на предмет статей про сложные вещи уровня GAT (пример). Помню там было несколько статей про проблемы с асинхронностью и вроде ещё чем-то и какие UB они на текущий момент позволяют делать и какие варианты решения имеются. То есть сейчас оно в состоянии Pre-RFC, фактически и явным образом не определено (undefined). Конкретные выпуски не назову, но вроде в последние недель 8 (не считая текущую) были обсуждения по теме.

Целочисленное переполнение в release сборке - объявлено документацией как UB.

Или вы в проде debug сборку гоняете?

Компилятор про это знает и как минимум предлагает варианты поведения на выбор. Так что это скорее unspecified behavior, т.к. пользователь сам выбирает что с этим делать.

Простите, но мне подход Swift и Crystal нравится больше: привычные операторы ВСЕГДА возвращают ошибки/кидают исключения на переполнение, и в релизной сборке тоже. А если алгоритму нужно переполнение, или хочется скорости, то пожалуйста, вот тебе специальные операторы.

А «мы ассертим в debug, но в release вы сами по себе» - так себе подход.

Кстати, а в чём разница между Undefined Behavior и Unspecified Behavior? Что-то моя голова не может уловить семантической разницы.

Undefined - когда нет явного объяснения что делать если сделали какую-то грязь, например разыменование nullptr или доступ к невыравненной памяти. Unspecified - когда есть варианты как некоторая грязь может выполнена, но явным образом не указано какое поведение правильное/дефолтное и каждый из имплементаторов волен выбирать своё поведение по-умолчанию. В частности как раз переполнение целых, штуки типа `i++ + ++i` (сравните python, c++ и c#) и т.п.

Добавьте в Cargo.toml, и будет поведение как в debug. Аналогично в debug можно выключить эти проверки.

[profile.release]

overflow-checks = true

Спасибо. Вы единственный, кто дал конструктивный ответ.

Переполнение не является UB в rust.

В debug паникует, в release переполняет, все конкретно определено.

Ну и есть методы, с еще более конкретным поведением: checked_*, overflowing_*, wrapping_*

Как хотите. Я не согласен. Я столько наелся assert, которые в проде ни чего не делали, что теперь от такого зубы сводит.

assert одинаково работает и в релизе и в дебаге, а если использовать debug_assert - на то он и дебаг

Я имел в виду проверку переполнения. По факту это - debug_assert. А таких ассертов я в C наелся.

Любой вызов функции, которая где-то там внутри имеет unsafe

Очень легко: write значение указателя на данные в программе на Раст в файл устройства (/dev), драйвер может что ему угодно по этому указателю сделать. Вот и уб без unsafe. С таким лично столкнулся, когда драйвер по указателю менял структуру в программе на расте объявленой как немутабельная (без mut), что в расте считается уб

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

А сколько людей одинаково хорошо знают Rust и принципы построения масштабируемых асинхронных приложений? И могут на Rust, это запилить?

А сколько таких людей, но с Python, Java, Node?

Если ответить на эти вопросы, то станет понятно, почему "АйТи бизнеса боятся". От не боятся, они просто хорошо считают свои деньги и время. ;)

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

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

Как пример - нужно сделать некий сервис-модуль, который по заданным на вход параметрам перелопачивает достаточно большой объем данных и выдает на выход некую выборку в виде резалтсета. Вы задачу успешно (как вам кажется) решаете и отдаете на нагрузочное тестирование. Вам ее возвращают не доработку - не копии промсреды ваш сервисмодуль отрабатывает за 3 секунды, что категорически не вписывается в требования - предельное время работы не должно превышать 300мсек.

Где тут про "масштабирование"?

От не боятся, они просто хорошо считают свои деньги и время. ;)

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

Так может задачу можно на десяток частей разбить и запустить распределенно. И будет вам ваши 300мс...

А если нет? Тогда что? "Извини, не смогла?"

Еще бы неплохо линеаризуемость соблюсти между этими подзадачами. Ну и в SLA бывает указан 99-процентиль по ответу в 60 микросекунд, например. 

Вы собираетесь перелопачивать достаточной большой объем данных в памяти своего самописного на раст приложения? Не в какой нибудь модной БД с красивым название типо Кассандра, Хадуп ?) Нет?

Ну тогда да. Только Rust.

Хадуп - это вообще не про скорость.

Почему вы решили что я что-то там в памяти буду перелопачивать? И почему решили что на Rust?

Я писал уже что у нас мощная специфическая платформа, мощный специфический язык, быстрая БД. Но. Не все можно распараллелить. Есть вещи, которые в принципе не параллелятся. Есть вещи, которые параллелить себе дороже. А есть которые можно легко распараллелить (когда много элементов и каждый обрабатывается независимо от остальных).

Но в ряде случаев есть жесткие требования на максимальное время выполнения задачи (таймауты). И в них приходится укладываться. Любой ценой.

Вот недавно делал оптимизацию. Есть некоторый запрос. Сложный, несколько таблиц, много условий, несколько подзапросов. Плюс distinct. Плюс агрегирование (group by) в одном из подзапросов. Еще и динамический (строка запроса формируется в рантайме, потом подготовка плана и выполнение). Задача выполняется почти 15 минут. При том данные начинают идти только через 13 минут после старта (т.е. 13 минут оно там что-то внутри себя крутит-вертит).

Переделал запрос. Убрал distinct, убрал агрегирование. Сделал его статическим (строка задается сразу, план запроса готовится на этапе компиляции). Получил избыточный (по данным), но линейный запрос. Контроль дублей (вместо distinct) и процесс агрегирования вынес в процедуру обработки потока данных ("просеивание"). В результате время выполнения задачи сократилось в три раза - стало менее 5-ти минут.

С памятью, кстати, проблем нет - 12Тб оперативки, 400Тб SSD массивы. Обычно используется модель памяти SINGLE LEVEL - там вы не знаете где выделена память - в оперативке или на диске, просто получаете указатель и работаете с ней. Ограничения - 16Мб статической памяти на задание (job) и не более 16Мб на один кусок динамической памяти (но кусков может быть сколько угодно). Если нужны большие куски - используем модель TERASPACE - там до 2Гб на кусок.

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

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

Через пол года будет статья: Пук пук инвестор ушел, мы не уложились в сроки из-за скорости разработки на rust и написание асинхронного кода вызвало нервный срыв.

Что же они по вашему должны были выбрать? Rust - язык быстроразвивающийся, проблем всё меньше, а новых фич больше. Да и что там с асинхронщиной? Уже анонсировали стабилизацию асинхронных функций в трейтах, так что единственный существенный аргумент против Rust скоро станет неактуальным.

Это очень субъективный показатель, имхо. Я на достаточно хорошем уровне знаю как Rust так и Node.js + TypeScript и одну и ту же задачу сделаю на обоих за примерно одинаковое время, на Rust даже быстрее будет, хотя и больше текста набрать придется (ИИ в помощь) но за отладкой просижу в разы меньше.

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

А насчет асинхронного кода, не понял в чем проблемы? Все с ним прекрасно в Rust.

Поддержу насчет асинхронщины. После FreeRTOS или Zephyr на микроконтроллерах async/await выглядит как магия

Стартап-биржа использует стартап-язык для рекламы. Впрочем, какая разница для пузыря.

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

 Rust постепенно набирает популярность ... в среде менеджмента

Да, им легче всего на уши присесть

Отучаемся говорить за всех. Если бы это была педовикия, то вам бы уже накидали в тачанку, за отсутствие референсов - кто сказал, какой менеджемент?

Я за менеджмент отвечу. Например, есть у вас легаси код, и библиотек сишных (плюсовых) разныз, и что вы будите делать в ресте, писать unsafe? В чём тогда безопасный код? А где вы будите нанимать людей? Прям на рынке такие люди есть? Так то на Golang можно за две недели переделать любого, Гуглом проверено, а ты попробуй на Раст переделать? ;-)

Именно. Там надо конкретно мировоззрение под линейное программирование менять. И кому это надо ?

Много слов, мало фактов, замеров скорости, сравнения производительности. Сколько транзакций может выполнить сервер на расте в секунду?

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

Мы делали однажды похожее. Тоже на rust-lang. Наш pipeline пропускал 100k+ транзакций типовых как поставить заявку на исполнение (fix/fast шлюз, риск-менеджмент простой, matching, market data). С запасом и гарантиями.

Ну вот ссылка на Википедию: Транзакция. Что значит "пропускал"? Он их обрабатывает на расте, сохраняет изменения в базе, и отвечает, или просто насквозь пропускает?

Сохранял в базе, обрабатывал, отвечал, да

Sign up to leave a comment.

Articles