Обновить

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

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

Если что, у нас в C# кодогенераторы работают точно так же, единственное отлчие - имя файла со сгенерированным кодом вручную писать не надо.

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

В Dart part 'file.g.dart' прописывается прямо в исходном файле, а это уже жесткая связка. Исходник знает о своём сгенерированном двойнике. Если бы генерация была полностью внешней - гоняй на пайплайне сколько угодно, исходники чистые, проверяешь результат. Но когда исходник содержит ссылку на то что сделал кодген - это уже другая история.

Всё ещё не вижу проблем. Ссылка на сгенерированный код в программе будет в любом случае (зачем-то же его генерировали!), чем же ссылка в форме имени файла фундаментально отличается от ссылки в форме имени функции?

В Dart part 'file.g.dart' прописывается в исходнике до генерации. Без запуска генератора - файл невалидный, IDE ругается, компилятор не запустится. Да, генератор может сам добавить part 'file.g.dart' - но тогда либо коммитишь сгенерированный код в репозиторий (PR выглядит как простыня автогенерации), либо гоняешь генератор на каждом пайплайне как первый шаг перед компиляцией. Именно я и пытался обойти через  --enable-experiment=enhanced-parts и как в итоге оказалось что в целом с этим можно жить. но тот факт что это флаг экспериментальный тоже показатель

В Dart part ‘file.g.dart’ прописывается в исходнике до генерации. Без запуска генератора - файл невалидный, IDE ругается, компилятор не запустится.

Значит, надо запустить генератор, чтобы файл появился.

Да, генератор может сам добавить part ‘file.g.dart’ - но тогда либо коммитишь сгенерированный код в репозиторий (PR выглядит как простыня автогенерации), либо гоняешь генератор на каждом пайплайне как первый шаг перед компиляцией.

Очевидно, part ‘file.g.dart’ не является сгенерированным кодом, даже если он был добавлен генератором. Относиться к этой сторочке как к сгенерированному коду - всё равно что отказываться добавлять в репозиторий package.json на том основании, что он был создан командой npm init.

Кроме того, повторюсь: “нет ничего плохого в том, чтобы гонять генератор на каждый пайплайн”.

Согласен, одна строчка не проблема. Проблема когда она в каждом файле где есть аннотация на классе. Стандартный build_runner именно так и работает - part 'user_service.g.dart' в user_service.dart, part 'user_controller.g.dart' в user_controller.dart и так далее. И это можно обойти только через экспериментальный флаг. И ещё один момент который меня удивил: аннотация не знает как генерировать код - она просто маркер. Логику генерации пишешь отдельно в Builder. То есть чтобы написать инструмент с кодогенерацией - нужно написать отдельный инструмент для генерации. Для меня как JS-разработчика это странно - аннотация должна сама знать что она генерирует.

аннотация должна сама знать что она генерирует

Аннотации именно тем и отличаются от декораторов, что сами ничего не знают. Dart - не единственный язык, который использует аннотации, точно так же они работают как минимум в Java, C# и Go.

Верно, аннотации в Java и C# тоже не знают что генерируют - но там есть рефлексия в рантайме, поэтому фреймворк сам читает аннотации и действует. В Dart рефлексии в AOT нет — поэтому и нужен отдельный Builder который парсит AST и генерирует код. Это и есть разница. В Go аннотаций вообще нет - там всё явно через код, другая парадигма.

В Go есть теги на полях структур, это те же аннотации по сути

И в C# кроме рефлексии есть кодогенерация, управляемая теми же атрибутами. Кстати, активно работать над этой кодогенерацией начали как раз когда стали развивать AoT-компиляцию.

Понимаю что немного оффтоп, но…

И вот ирония - я искал серебряную пулю, которая уже есть.

А помимо Go ещё есть C#, Java и PHP где последние два со своими Spring и Symfony уделывают вхлам всю экосистему и ноды и гошки вместе взятых под бекенд. Ну ладно, .NET тоже не такой уж и примитивный, там даже OAuth “из коробки”, но послабее будет.

Ну и накрайняк Rust, он тоже под бекенд вполне себе ничего, но повелосипедить придётся.

Всякие Elixir и Crystal в расчёт не беру (извините, языки-то реально хорошие), слишком маргинальные, почти как Dart. Найти на них разработчика будет тяжко. Ну и экосистема с первой тройкой (Java/C#/PHP) не сравнится.

честно - мои знания php заканчиваются 5ой версии:). и я старался сравнить именно рантаймы, без фреймворков. Если у кого-то есть желание написать аналогичный сервис на PHP (там сейчас вроде и встроенный веб-сервер есть) или довести C#/.NET до ума, прогоню на том же стенде и добавлю в таблицу. .NET у меня заработал со второго раза, скорее всего что-то упустил — так что результаты там под вопросом. Rust — это была бы отдельная статья: "Как я потратил 2 недели чтобы написать Hello World"

Понимаю что немного оффтоп, но…

я когда начал перепроверять числа в бечмарках. и стало все не так однозначно(с)
Bun native - догонял и даже обгонял тот же Go. Но как добавляешь тротлинг или зависимости из npm становиться хуже голой nodejs или рестарты на хелсчеке. серебряная пуля тут скорей - "компромис"

Рефлексия тоже отсутствует, а как строить DI-контейнер без рефлексии?

Вот этого совсем не понял. В моём мире использование рефлексии – антипаттерн. И DI прекрасно реализуется без неё.

Хочешь сделать for...in по объекту? Забудь.

И этого тоже. Если объект не имплементирует Iterable, то так и должно быть. А если имплементирует, то отлично работает.

____

За тесты RPS – на бэкенде Дарт не пробовал, но звучит странно. Это же не Дарт как таковой работает, он компилируется в бинарный код платформы – что вроде бы должно давать максимальную производительность. Стоило бы выяснить, что там Клод наваял, а не тащить в прод не глядя. Или вы его в JS компилили?

По рефлексии - я пришёл из JS-мира, где это норма: NestJS, Angular, TypeScript декораторы, и можно пробежаться по обьекту через for...in. Поэтому когда обнаружил что в Dart её нет - пошёл делать сам. В целом это не проблема, просто свои "нюансы" - как err != nil в Go. Каждый язык со своими особенностями.

По RPS - Клод наваял порт ioredis, механический один-в-один перенос, и он показал на 5% больше RPS чем самый поплурный Redis-клиент. Так что за качество кода вопросов нет. За RPS - вопросы к Google, это их рантайм и их "ready for cloud". Ну и сервисы в репозитории - Node.js и Dart. Как говориться найдите 10 отличий. Код хуже тк его я писал сам)

я пришёл из JS-мира 

Клод наваял порт ioredis

Мне кажется, в этом и проблема ‒ Вы пытаетесь напрямую перенести привычки из JS в Дарт. А там таки сильно иначе всё. И GC тоже можно управлять в какой-то степени, прямо из кода. В частности, убедиться, что все объекты имеют реализацию dispose(), и что ссылки на них обнуляются после использования. Но это надо экспериментировать, конечно ‒ навскидку волшебную таблетку не предложу.

Если пройдет по ссылки которую я указал в тексте

Generally old space is organized in pages, so if there's at least one object on a page, the whole heap page is retained. So a partial reason may be fragmentation. Though if the analyzer is ever idle, it may trigger compacting GC, which can get rid of this fragmentation.

вы хоть за вызывайте dispose() и забнуйляти переменные. если хоть что то осталось что вся страница останется

Если пройдет по ссылки которую я указал в тексте

Вы уверены, что эта ссылка хоть сколько-то релевантна? Там речь о потреблении памяти сервером анализа кода в JIT среде. Как это связано с работой реального кода в AOT, я не знаю.

Если почитать всю ветку внимательно - там речь о поведении VM, а не специфике JIT. VM одна и в JIT и в AOT. GC один, heap pages одни. JIT/AOT влияет на компиляцию кода, а не на то как VM управляет памятью.

Если почитать всю ветку внимательно - там речь о поведении VM, а не специфике JIT.

Там речь о том, что Analyzer uses too much memory when there is many lints enabled. При чём тут боевой код?

VM одна и в JIT и в AOT. 

Нет. Тут ниже бурное обсуждение этого вопроса, повторяться не буду. Вкратце: JIT == VM, AOT == runtime. Разные вещи.

AOT бинарник содержит embedded runtime: GC, event loop, isolates, I/O - без этого Dart код не запустится. JIT/AOT влияет только на компиляцию, runtime один. Если VM не отдаёт память и I/O тормозит под throttling и без - флаги не помогут, это задокументировано в репозитории с цифрами. Если я что-то готовлю неправильно - PR открыт, исходники там же.

Это же не Дарт как таковой работает, он компилируется в бинарный код платформы – что вроде бы должно давать максимальную производительность

Дарт просто собирает нативный образ DartVM + ваш код в виде натива, который может запустить система. А по сути это та же самая а-ля javaVM, только маленькая.

Ну очень маленькая:

AOT Compilation (Independent Binary)

​If you use the dart compile exe command, the code runs as an independent, native executable.

  • How it works: Dart uses Ahead-of-Time (AOT) compilation to convert your source code into machine code (x64 or ARM64) specific to Linux.

  • The "VM" question: It does not run inside a separate, standalone Virtual Machine like a Java .jar file does. Instead, the "Dart Runtime" is stripped down and embedded directly into the binary.

  • Runtime contents: This embedded runtime is minimal, handling only essential tasks like garbage collection (GC) and the basic memory management required for Dart's "Isolate" model.

  • Result: You get a self-contained file that you can run on a Linux machine without needing the Dart SDK installed.

И что тут опровергает "образ DartVM + ваш код"?

Именно DartVM работает с память, обслуживает ваши изоляты (ведь main запускается в изоляте). Абсолютно то же самое делает и java. Только Java по ходу исполнения переводит байт код в нативные (прогрев) и в теории может что-то оптимизировать под конкретное железо. А Dart это сделает сразу на "усредненую" машину.

Минимальный exe "Hello world" который собирает Dart под Windows - 5Mb. Если бы он собирал несколько килобайт как это делают C/C++ можно было бы рассуждать о неком настоящем нативе.

И что тут опровергает "образ DartVM + ваш код"?

Runtime != VM. Код, написанный на чистом С тоже использует свой рантайм, например.

Все деления Runtime/VM - чистый маркетинговый флуд. Ну или нужно ставить VM отдельно / она включена в ваш код.

Я собираю один единый exe с помощью GraalVM для десктоп на JavaFX.
Приложение полностью независимо, один exe не требует никаких предустановленных компонентов на машине.
Но это все та-же JavaVM только находится внутре того-же exe.
Это теперь Runtime или VM?

Все деления Runtime/VM - чистый маркетинговый флуд.

Почему? Разница существенная, виртуалка ‒ это именно что отдельная среда исполнения внутри host OS, а рантайм ‒ просто кусок кода, подключающийся по необходимости и крутящийся внутри самой ОС.

Код ни в Dart ни в Java не запускается сам по себе. Его запускает JavaVM или DartVM. В случае Dart это тот самый Loop который строит очередь тасков/микротасков и построчно выполняет каждый шаг программы. Как не обзовите НативнаяКомпиляция/Предкомпиляция - все равно каждое действие проходит через очередь DartVM. Она рулит потоком управления.

В случае Dart это тот самый Loop который строит очередь тасков/микротасков и построчно выполняет каждый шаг программы. 

Ммм, а где не так? В любой программе есть event loop, который так работает. Дарт ни разу не исключение. И это сильно отличается от запуска внутри изолированной среды VM/application server ‒ которая вносит свои накладные расходы.

Ммм, а где не так?

В реальном нативе, тот же C/C++. Ваша программа набор инструкций процессору. Которые он будет выполнять без остановки, пока они не закончатся. И нет в собранном бинарнике никаких Loop. Все инструкции идут шаг за шагом без остановок. Ну только системный планировщик их приостанавливает, рулит их приоритетом.
Ваш код Dart это не иснтрукции процессору а инструкции DartVM. Хоть они и уже в бинарном виде. Но запустит их именно DartVM в виде своих task/microtask. И он может менять их порядок выполнения для всяких await/thien.

В реальном нативе, тот же C/C++. Ваша программа набор инструкций процессору. Которые он будет выполнять без остановки, пока они не закончатся. И нет в собранном бинарнике никаких Loop.

Эмм, я дико извиняюсь, но это так работает только в fire-and-forget утилитах. Как только появляется хоть минимальная интерактивность ‒ а все сервисы/демоны работают именно так, без главного цикла никуда, иначе программа завершится сразу после запуска и не сможет обрабатывать внешние события. Ежели чего, С/С++ ‒ чуть ли не единственные (после ассемблера) языки, на которых я писал код за деньги. И главные циклы там обязательно присутствовали.

Вы говорите о том что можете сделать в нативе. В конце концов DartVM сама написана на C++ и при желании можно хоть повторить ее в своем коде.
Но код Dart сам по себе никогда не может быть так запущен. Он оборачивается в task и встает в очередь EventLoop.

А у этого EventLoop какие-нибудь наблюдаемые следствия есть, или тут важно просто поспорить?

Конечно есть - накладные расходы.
Собственно этот обмен мнениями и начался с цитаты про нативник dart:
".. он компилируется в бинарный код платформы – что вроде бы должно давать максимальную производительность.."

Но код Dart сам по себе никогда не может быть так запущен. Он оборачивается в task и встает в очередь EventLoop.

По-моему, мы пошли по кругу. Уже писал ‒ event loop есть неотъемлемая часть любой интерактивной программы, на любом языке.

Ну в итоге ответ на ".. что вроде бы должно давать максимальную производительность..." - по тестам Dart код проигрывает в производительности нативному C++ в 2-3 раза.
Это согласно тестам в которых использовались оба языка:
Dart vs C++

 по тестам Dart код проигрывает в производительности нативному C++ в 2-3 раза.Это согласно тестам в которых использовались оба языка

Мы сменили тему? Довольно трудно спорить с тем, что хорошо оптимизированный код на С/С++ будет быстрее в большинстве случаев. Но это никак не связано с тем, работает ли Дарт под VM или просто задействует свой рантайм.

Я повторюсь еще раз - единственная разница между VM языка и Runtime только в том что с VM вы берете свой файл и без изменений его переносите между linux/windows/ЧемТоЕще. Или прибиваете гвоздями в свой готовый файл и собираете уже отдельно для linux/windows/ЧтоТоЕще. .Net runtime, Node.js - это тоже runtime только вот никакой разницы с JavaVM у них нет. И Dart runtime точно такой же, просто он включен в вашу программу и не требует установки отдельно.

Если там JIT - это VM, если запускается заранее скомпилированный код - рантайм.

Но ведь JavaVM при работе постепенно перекомпелирует байткод в нативный (прогревается), спустя какое-то время вся система может быть уже в нативном виде.
Получается VM плавно мутирует в рантайм? :)

Перенести структуру, сохранить логику, сменить рантайм. Легко?

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

Dart — это Flutter, для бэкенда нет ничего, от слова совсем.

Флаттер - увы, да. И это безумие Гугла (хотя чего взять с Интернет компании - у неё Интернет мышление без особенностей вариантов). Затачивать язык под разработку интерфейса - предполагать, что кроме интерфейса в приложении не будет ничего.

Что нет ничего для бэкенда - сомневаюсь, сам не искал, но поискать могу посоветовать. Уж если в JavaScript нашлось, то в Dart точно найдётся. А для переписывания один в один - так и должно мнооогого не хватать...

Посмотрел, как вообще пишут. И тут меня накрыл ужас - кодогенерация.

Пишут... кто? Это так, к слову... Кодогенерация, как ясно далее по тексту, не зашла административным процедурам расчитанным на отсутствие кодогенерации. Кто бы сомневался?

И это - не про Dart, а про навёрнутое, уже, вокруг него.

Как пример того, к чему приходит группа "разработчиков" учащихся друг у друга - брависсимо. А теперь ещё и Клод до кучи...

С памятью отдельная история.

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

И это называется "ready for cloud".

Именно называется. И это, на самом деле и почему я пишу коммент, трагедия. Технологии впариваются продаются как на базаре, особенно Гуглом (а чё, Интернет мышление же, а Интернет - он базар и есть). И что-то выяснить без глубокого погружения в предмет - невозможно. В результате - решения случайны, результат очевиден наблюдаем.

Очень хорош на бумаге, но на деле его ниша — только Flutter.

Не вполне верно без явных временных привязок. Дойдут у кого-то с реальным знанием языка дело до ниши - она и расширится. А с Клодой и Гуглой - не станет расширяться.

Практический вопрос - какая польза будет тому, кто её расширит? Моя догадка - практически никакой...

Несколько лет пытались внедрить макросы, но не смогли,

Согласно открытым (ну, не вполне и не всегда и не отовсюду) источникам - дело хуже. Смогли, по всем пунктам смогли, кроме одного - макросы не удалось совместить с hot reload, а это один из столпов рекламы Флаттера.

Если бы после спада нагрузки память возвращалась за разумное время, уже сейчас получился бы отличный цикл: HPA поднимает второй под за секунду, трафик распределяется, нагрузка падает, память освобождается, Kubernetes уплотняет ноды.

Что-то мне опыт подсказывает - и это слишком оптимистично. То же самый эффект что и с Дартом, только теперь с Кубернетисом - со свиным рылом в калашный ряд. И это, на самом деле, тоже трагедия - ИТ самоорганизуются в системы так, чтобы противостоять изменениям, как следует (ещё и) из статьи.

И вот ирония - я искал серебряную пулю, которая уже есть. Да, она скучная, многословная, с err != nil на каждой строчке, и этот культ явности, где context.Context первым аргументом в каждую функцию звучит как привет из 2009 года.

Ну, err != nil мешает тем меньше, чем разумней обрабатываются ошибки. Да и контекст передавать как параметр точно нужно только в те функции, которые изначально написаны соответствующе. Это третья трагедия - низкий порог входа в сочетании с уважением к мнению большинства...

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

Claude Code сделал механический перенос почти бесплатным

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

Чего хорошего я могу сказать?

Между вайбом и копипастой нет качественных различий. Клод не создаёт в ИТ новых проблем, он резко усугубляет старые. И это хорошо. Чем раньше наступает неизбежный кризис, тем лучше - на борьбу с неизбежным потрачено меньше ресурсов, значит для выхода из кризиса осталось больше.

Про Claude Code и биржу - вы лучше меня сформулировали то, что я пытался сказать в "Настоящем уроке". Еще года 2-3 назад я 10 раз подумал перед тем как вписываться в такую авантюру и столько же проверил руками.

По FFI - да пробовал, но значимого результата не дало. и это понятно почему. проблема не в сборке мусора внутри VM, а в том что VM не отдаёт освобождённую память обратно ОС.

Generally old space is organized in pages, so if there's at least one object on a page, the whole heap page is retained. So a partial reason may be fragmentation. Though if the analyzer is ever idle, it may trigger compacting GC, which can get rid of this fragmentation.

А вот как понять где какой обьект на какой страницы вы не узнаите если даже захотите.
И я все же уверен тут проблема не в самом языке а в для чего он предназначен
И если не эта фраза: Creating scalable, high performance APIs and event-driven apps are good use cases for Cloud Run
Я бы даже не пытался

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации