Hacker News

Поврзавме C++ со една нишка со Rust со повеќе нишки

Поврзавме C++ со една нишка со Rust со повеќе нишки Оваа сеопфатна анализа на интерфејс нуди детално испитување на нејзините основни компоненти и пошироки импликации. Клучни области на фокус Дискусијата се фокусира на: Основна механизација...

1 min read Via antithesis.com

Mewayz Team

Editorial Team

Hacker News
Еве го целосниот пост на блогот за оптимизација:

Поврзавме C++ со една нишка со 'рѓа со повеќе нишки

Интерфејсот на C++ код со една нишка со Rust со повеќе нишки не е само возможен - тоа е еден од најпрактичните начини за модернизирање на старите системи без целосно препишување. Во Mewayz, се справивме со овој предизвик кога го зголемивме нашиот деловен оперативен систем од 207 модули за да опслужи 138.000 корисници, а резултатите фундаментално го променија начинот на кој размислуваме за интероперабилноста на системите.

Зошто би го поврзале C++ со една нишка со рѓа со повеќе нишки?

Повеќето производствени системи носат години C++ код тестиран во битка. Препишувањето на сè во Rust звучи привлечно на хартија, но воведува огромен ризик и месеци инженерско време. Прагматичниот пристап е постепено усвојување - завиткување на постоечката логика на C++ додека се претоваруваат оптоварувањата кои се тешки за истовремена работа во моделот на сопственост на Rust.

Во нашиот случај, основните деловни логички модули работеа сигурно во C++ со една нишка со години. Тие се занимаваа со секвенцијална обработка на задачи, генерирање документи и финансиски пресметки. Но, бидејќи нашата корисничка база се зголеми над 100 илјади, ни требаше паралелна обработка на податоци, истовремено ракување со API и безбедно управување со споделени состојби. Карактеристиките на Rust Send и Sync ни дадоа гаранции за истовременост на компајлирање кои C++ едноставно не може да ги понуди без обемна рачна ревизија.

Клучната мотивација е намалувањето на ризикот. Го задржувате она што функционира и додавате какви размери - без да ја коцкате целата ваша база на кодови со миграција што можеби никогаш нема да заврши.

Како всушност функционира границата на FFI?

Странскиот функционален интерфејс (FFI) помеѓу C++ и Rust работи преку C-компатибилни функционални потписи. Блоковите надворешни „C“ на Rust ги изложуваат функциите што C++ може директно да ги повика и обратно. Критичниот предизвик се појавува кога траењето со повеќе нишки на Rust треба безбедно да го повика кодот C++ со една нишка.

Ова го решивме со помош на посветена архитектура:

  • Извршител на C++ ограничен со низа: Сите повици на C++ се пренесуваат преку една посветена нишка со помош на канал за пренесување пораки, со што се осигурува дека непроменливата со една нишка никогаш не е прекршена.
  • Слој на асинхрон мост на 'рѓа: Задачите на Токио ја доставуваат работата до извршителот на C++ и чекаат резултатите преку oneshot каналите, одржувајќи ја страната на Rust целосно асинхрона.
  • Управување со непроѕирно покажувач: објектите во C++ се обвиткани во структури на Rust кои имплементираат Drop за детерминистичко чистење, спречувајќи протекување на меморијата преку јазичната граница.
  • Серијализација на границата: Сложените структури на податоци се сериозни на FlatBuffers на слојот FFI, избегнувајќи совпаѓање на кревки распоред на структурата и овозможувајќи независна еволуција на секоја страна.
  • Изолација на паника: Рустот catch_unwind ја обвиткува секоја влезна точка на FFI така што паниката никогаш не ја преминува јазичната граница, што би било недефинирано однесување.

Овој модел ни ја даде пропусната моќ на Rust со повеќе нишки со доверливост на докажаната логика на C++ - без препишување ниту една линија од оригиналните деловни правила.

Кои се најголемите стапици што треба да се избегнуваат?

Најопасната грешка е да се претпостави дека C++ кодот е безбеден за нишки кога не е. Глобалната состојба, статичните променливи и библиотечните повици што не се враќаат ќе предизвикаат трка со податоци што компајлерот на Rust не може да ги открие преку границата на FFI. Безбедносните гаранции на Rust застануваат на блокот небезбеден - сè што е внатре е ваша одговорност.

Клучен увид: Rust гарантира безбедност на меморијата во рамките на сопствениот код, но во моментот кога ќе ја преминете границата на FFI во C++, ќе го наследите секој проблем со безбедноста на нишките што го има C++. Архитектурата околу таа граница е поважна од кодот на двете страни од неа.

💡 DID YOU KNOW?

Mewayz replaces 8+ business tools in one platform

CRM · Invoicing · HR · Projects · Booking · eCommerce · POS · Analytics. Free forever plan available.

Start Free →

Друга вообичаена замка е доживотно управување. Објектите на C++ не учествуваат во проверката на заемот на Rust. Ако Rust испушти референца додека C++ сè уште држи покажувач, добивате грешки кои се брутално тешки за дијагностицирање. Ова го решивме со примена на строга семантика на сопственост: објектите во C++ секогаш се во сопственост на точно една обвивка на Rust, а споделениот пристап оди преку пребројувањето на референцата базирана на Arc на страната на Rust.

Во однос на перформансите, прекумерните повици на FFI создаваат трошоци од префрлување контекст и серијализација. Ние ги групираме операциите секогаш кога е можно, испраќајќи редица работни ставки до извршителот на C++ наместо да правиме поединечни меѓујазични повици.

Како се покажа овој пристап во производството?

По распоредувањето на хибридната архитектура низ нашата платформа, измеривме конкретни подобрувања. Пропусната моќ на барањето се зголеми за 3,4 пати за модули кои претходно се наоѓаа на тесно грло при секвенцијална обработка на C++. Латентноста на опашката (p99) се намали за 61% бидејќи асинхроното траење на Rust може истовремено да обработува независни барања додека C++ се справува со задачи тешки за пресметување на својата посветена нишка.

Што е уште поважно, имавме нула грешки поврзани со истовременост во првите шест месеци од производството. Шаблонот за ограничување на нишките структурно го оневозможи C++ кодот да се повикува од повеќе нишки, додека системот на типот на Rust спречи трка со податоци на неговата страна од границата. Ова беше значително подобрување во однос на нашиот претходен пристап во обидот да додадеме нишки во C++ со mutexes, кои предизвикаа три инциденти во услови на трка во една четвртина.

Инженерскиот тим објави и побрзи циклуси на повторување. Може да се вградат нови функции во Rust со целосна поддршка за истовремено, додека постоечките C++ модули продолжија да работат без модификација. Оваа инкрементална стратегија значеше дека никогаш не сме имале високоризична миграција „голема експлозија“ — само стабилно, мерливо подобрување.

Често поставувани прашања

Дали Rust може да повикува библиотеки со C++ со една нишка без измена?

Да, но мора да се осигурате дека сите повици до таа библиотека се случуваат од една нишка. Стандардниот шаблон е да се создаде посветена нишка на извршител што ги серијализира сите повици во C++ преку канал. Асинхронизираните задачи на Rust поднесуваат барања и чекаат одговори без да го блокираат времето на траење со повеќе нишки. Самиот код C++ не бара промени - безбедносното ограничување се спроведува целосно на страната на Rust.

Дали горните трошоци на FFI се доволно значајни за да влијаат на перформансите на апликацијата?

Индивидуалните повици FFI имаат минимални трошоци - обично под 10 наносекунди за едноставен функционален повик. Сепак, сериизацијата на сложените структури на податоци и синхронизацијата на нишките на границата се собираат ако остварите илјадници фино-гранулирани повици. Операциите за серии и користењето формати за серијализација со нула копии како FlatBuffers или Cap'n Proto ги задржуваат трошоците занемарливи дури и во обем.

Дали треба да ја преработиме нашата база на кодови C++ во Rust наместо интерфејс?

За повеќето тимови, инкременталното поврзување е побезбедна и побрза патека. Целосното препишување воведува месеци на инженерски ризик без вредност за корисникот до завршување. Интерфејсот ви овозможува веднаш да испраќате подобрувања, да го потврдите пристапот на Rust во производството и да мигрирате модули еден по еден врз основа на тоа каде истовременоста дава најголемо влијание. Препишете ги само модулите каде што трошоците за одржување на границата на FFI ги надминуваат трошоците за препишување.


Во Mewayz, градиме инфраструктура која се зголемува - и технички и оперативно. Нашиот деловен оперативен систем со 207 модули им помага на 138.000 тимови да водат попаметни работни текови почнувајќи од 19 $/месечно. Без разлика дали управувате со проекти, автоматизирате операции или го зголемувате вашиот бизнис, Mewayz се прилагодува на начинот на кој работите. Започнете го вашиот бесплатен пробен период на app.mewayz.com и видете што модерен деловен оперативен систем може да направи за вашиот тим.