Pull to refresh

Comments 27

Спасибо за статью.
Может, я невнимательно читал, но не увидел обоснования именно такого решения. Можно как-то прорезюмировать коротко? Я бы сервер скорее писал на C#, который предоставит весь функционал с помощью открытого и документированного протокола. А на Java потом только клиент. Что-то на подобие баз данных SQL. Немного громоздко, но по-моему менее громоздко чем портирование миллионов строк кода на плюсы и кодогенерация.
Здравствуйте. Речь шла о том, чтобы предоставить продукт «чисто Java» или «чисто C++», который позволял бы пользоваться всей функциональностью решения без необходимости зависеть от сервера. Серверные версии с SDK под разные языки у нас, разумеется, и так продаются в виде отдельных продуктов (как self-hosted, так и cloud). Не все хотят ими пользоваться, не все готовы для решения задачи вида «записать pdfку» поднимать сервер — некоторые хотят просто подключить библиотеку к своему продукту и пользоваться ею.
А проводились ли какие-либо тесты производительности и потребления ресурсов? Это ведь очень интересно, имея два три функционально одинаковых кода, узнать как С#, C++ и Java с ними справляются…
Да, разумеется. Как правило, сначала портируется «как есть», а потом начинаются оптимизации (как библиотеки, так и генерированного кода). Поскольку код изначально оптимизирован для C#, порты, как правило, работают медленнее (очень сложно подобрать адекватные тесты, но на том, что мы видели, замедление может происходить в разы, вплоть до одного порядка). Когда это становится критичным, происходит оптимизация — так, нам пришлось отказаться от boostовских регулярок из-за того, что в .Net всё работает намного быстрее.
а замедление — портирование библиотек и рантайма подвело, или сам по себе язык при переносе страдал? ну вот вы сказали что бустовые регулярки медленнее дотнет-ных, а если какой-то счётный алгоритм? или там сортировка не библиотечная?
Несколько факторов. В случае с регулярками — да, дело было чисто в оптимизации библиотек. Это только один случай, в .Net много специфических оптимизаций, на которые нужно наступить, чтобы о них узнать.

В случае с портированным кодом — проседания могут быть из-за разницы в механизмах работы с памятью. В C++ нет сжатия кучи, зато есть оверхед при копировании указателей (конструкторы-деструкторы, подсчёт ссылок) — из-за этого код, который часто создаёт много мелких объектов, будет работать медленнее после портирования. Насколько — полностью определяется соотношением рабочей нагрузки и операций по созданию объектов и работе с указателями. Счётные алгоритмы выглядят примерно одинаково на обоих языках, и с ними особой разницы мы не замечали — в основном всё упирается или в библиотеки, или в структуру языка.
Для затравки могли бы какой-нибудь helloworld с C# на C++ перевести, дабы продемонстрировать возможности платформы. Без разбора, вы его в следующей части обещали :)

Без реальных примеров преобразования кода эта статья смотрится несколько… пиарно, чтоль. Но спасибо, интересно таки. Ждем продолжения.
Спасибо за замечание. Да, в следующей статье будут примеры, эта и так получилась довольно большая.
Задача «писать один раз — транспилировать на десяток языков» уже решена Haxe. Но «своё» решение всегда ближе и приятнее.
Это если изначально разработка на нём велась. У нас задача стояла перенести десятки миллионов уже готового кода на C#.
*десятки миллионов строк.
Да, но, вы уже сделали транспилятор в Java. Гораздо проще было бы сделать транспилятор в Haxe (т.к он более выразительный чем C#) и потом уже из Haxe во все остальное. И продолжить разработку на Haxe. И это было бы дешевле, чем продолжать портировать код на Х языков, даже имея тулинг для облегчения процесса.

Сразу контр-аргумент по поводу «у нас никто не пишет на Haxe». Он очень похож на C#, у всех кто пришел в Haxe из C# или Java не было проблем с пониманием, как оно работает или проблем с адаптацией.
Спасибо за пояснение. Похоже, данный проект прошёл мимо нас уже потому, что поддержка C# и Java была добавлена в 2012 году, т. е. через 5 лет после выхода наших первых продуктов на Java (которые, в свою очередь, отставали на несколько лет от таковых для .Net). Нужно будет познакомиться с ним поподробнее.

С ходу я вижу следующие риски при использовании Haxe, которых нет в текущей нашей парадигме:

  1. Сложности с сохранением структуры типов. Поскольку речь идёт о библиотеках, API после портирования C# > Haxe > C# должен оставаться тем же самым с точностью до всех библиотечных типов и интерфейсов. В противном случае это повлечёт необходимость изменения кода клиентов, чего, естественно, требуется избегать всеми возможными способами. Это относится не только к .Net, но и к API уже вышедших продуктов для других языков.
  2. Миграция с C# на Haxe должна быть выполнена идеально с первого раза (сейчас проблемы с портированным кодом никак не влияют на релизы для .Net), и до её завершения возможности начинать покрытие других языков нет.
  3. Утрата истории изменений кода, насчитывающего десятки миллионов строк и сотни человеколет разработки.
  4. Все подсистемы должны работать в точности одинаково на всех языках и в точности как в исходном коде .Net. Например, наши юзкейзы требуют, чтобы генерация XML или графики в портированном коде была полностью аналогична таковой в .Net.
  5. Непонятно, что будет при переезде с дотнетоспецифическими вещами вроде встраиваемых ресурсов, работы со сборками или кодом, завязанным на рефлексию.


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

1) Повторить простые контракты можно. А вот со спецификой C# типа in/out, явной/неявной реализацией интерфейсов, перегрузкой операторов итд уже будет сложнее. С другими языками страшнее, особенно чем дальше они от managed языков с GC. Возможно придётся писать «фасады» под таргет платформу.
3) Повторите структуру файлов/папок старого проекта. На худой конец заставьте git думать что произошел ренейм из .cs в .hx.
4) Часть модулей придётся «переизобрести» в любом случае. Но вы уже это делали, когда эмулировали .NET. Добавлять чисто нативный код (C++, Java итд) после транспиляции никто не запрещает.
5) Рефлексия есть. Чисто платформными фишками Haxe позволяет пользоваться. Плюс это всего лишь генерация кода из кода, сборка остается за вами, и что вы туда дольете дело ваше. Работу с эмбеддед ресурсами можно сделать через абстракцию.

В общем, только человек знакомый со всем вашим проектом может правильно оценить риски, и не за один присест, нуансов много.
Просто взять из коробки Haxe и перенести туда 100% кода не получится. Но перевести 95% кода на Haxe и 5% на каждую платформу ИМХО вполне реально.

П.с. С++ несут больше всего рисков по портированию.
Наибольший интерес вызывает следующий момент: Портер умеет работать со Span, Memory, MemoryMarshal? В системе net они занимают центральное положение при написании технических библиотек.
Нет. В наших библиотеках они не используются, а мы поддерживаем, в первую очередь, те аспекты .Net, которые нужны непосредственно для портирования кода продуктов.
А можно поинтересоваться, в общих терминах, что это за продукт, который востребован клиентами на разных языках и оправдывает такое неординарное техническое решение в своей разработке?
Набор библиотек для работы с различными форматами файлов (офисные документы, графика и т. п.). Библиотек, правильно читающих все поля и нюансы форматов MS Office, не так много, а Automation Tools стоят дорого.
Понял, спасибо за ответ. Крутой проект, вы молодцы!

Хотел высказать своё никому не нужное мнение.


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


Например, клинтская либа для Apache Kafka написана на С++ (librdkafka), а поверх этой плюсовой либы уже работает, например, C# обёртка (kafka-dotnet).
Таких примеров очень много.


Опять же, мне кажется при транспиляции вы будете всё время заперты в клетке — ни тебе yield, ни тебе span, вы так не будете успевать за развитием языка C# и будет всё сложнее нанимать C# програмистов с существенными ограничениями на использование фишек языка C#.


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

Спасибо за комментарий. Ещё можно Skia/SkiaSharp вспомнить из этой серии.

К сожалению, изначально у нас весь код разрабатывается на C#, потому что это дешевле (и в плане оплаты труда программистов, и в плане количества проблем с управляемым кодом). Кроме того, вариант с оборачиванием библиотек C++ хорошо работает только в простых случаях (передача базовых типов, синхронность операций), а вот для работы с большими количествами объектов, передачи интерфейсов и делегатов придётся писать достаточно сложные обёртки.

Согласен, если туда-сюда надо гонять много объектов, то затраты по памяти на маршаллинг могут оказаться неприемлемыми.


Зря я начал что-то советовать не разобравшись до конца в проблеме.


А вот про "сложные обёртки" не соглашусь, транспилятор так-то тоже ни разу не простая получается обёртка.

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

Кажется в вашем случае проще было взять mono и выкинуть из него лишнее. А дальше сделать обертку на C++.

Добрый день. Возможно, Вы в чём-то правы, наши «порты» для Python действительно работают по похожей схеме. С другой стороны, при наличии большого числа мелких объектов (а это как раз наш случай) маршаллинг вызовов съедает очень много ресурсов, чего, конечно же, хотелось бы избежать.

Одной из наших целей было получить возможно более простое решение для пользователя, который может просто подключить плюсовую библиотеку к плюсовому приложению и работать. Мост между CLR (не важно, mono или нативным) — это усложнение данной схемы, хотя, возможно, и оправданное.
Sign up to leave a comment.

Articles