Hacker News

Sujungėme vienos gijos C++ su kelių sriegių Rust

Sujungėme vienos gijos C++ su kelių sriegių Rust Ši išsami sąsajos analizė siūlo išsamų pagrindinių komponentų ir platesnių pasekmių tyrimą. Pagrindinės dėmesio sritys Diskusijos centre: Pagrindinis mechanizmas...

7 min read Via antithesis.com

Mewayz Team

Editorial Team

Hacker News
Štai visas SEO tinklaraščio įrašas:

Susiejome vienos gijos C++ su daugiasriegiu rūdžiu

Sąsaja vienos gijos C++ kodą su kelių gijų Rust yra ne tik įmanoma – tai vienas praktiškiausių būdų modernizuoti senas sistemas be visiško perrašymo. „Mewayz“ sprendėme būtent šį iššūkį, kai padidinome savo 207 modulių verslo OS, kad ji aptarnautų 138 000 vartotojų, o rezultatai iš esmės pakeitė mūsų požiūrį į sistemų sąveikumą.

Kodėl turėtumėte susieti vienos gijos C++ su daugiasriegiu rūdžiu?

Dauguma gamybos sistemų turi ilgus metus išbandytą C++ kodą. Viską perrašyti į Rust atrodo patraukliai popieriuje, tačiau tai kelia didžiulę riziką ir kelis mėnesius trunkantį inžinerinį laiką. Pragmatiškas požiūris yra laipsniškas pritaikymas – esamos C++ logikos apvyniojimas, kartu perkeliant didelius lygiagretumo darbo krūvius į Rust nuosavybės modelį.

Mūsų atveju pagrindiniai verslo logikos moduliai patikimai veikė vienos gijos C++ versijoje daugelį metų. Jie tvarkė nuoseklų užduočių apdorojimą, dokumentų generavimą ir finansinius skaičiavimus. Tačiau kai mūsų vartotojų skaičius išaugo daugiau nei 100 000, mums reikėjo lygiagretaus duomenų apdorojimo, vienu metu atliekamo API tvarkymo ir saugaus bendros būsenos valdymo. „Rust“ funkcijos Siųsti ir Sinchronizavimas suteikė mums kompiliavimo metu vienodumo garantijas, kurių C++ tiesiog negalėtų pasiūlyti be išsamaus rankinio audito.

Pagrindinė motyvacija yra rizikos mažinimas. Išliekate tai, kas veikia, ir pridedate svarstykles – nežaisdami visos kodų bazės perkėlimo metu, kuris gali niekada nesibaigti.

Kaip iš tikrųjų veikia FFI riba?

Svetimos funkcijos sąsaja (FFI) tarp C++ ir Rust veikia naudojant su C suderinamus funkcijų parašus. Rust išoriniai „C“ blokai atskleidžia funkcijas, kurias C++ gali iškviesti tiesiogiai ir atvirkščiai. Esminis iššūkis iškyla, kai „Rust“ kelių gijų vykdymo programai reikia saugiai iškviesti vienos gijos C++ kodą.

Tai išsprendėme naudodami specialią architektūrą:

  • Apribotas gijos C++ vykdytojas: visi C++ iškvietimai nukreipiami per vieną tam skirtą giją, naudojant pranešimų perdavimo kanalą, užtikrinant, kad niekada nebūtų pažeistas vienos gijos invariantas.
  • Rūsto asinchroninio tilto sluoksnis: Tokio užduotys pateikia darbą C++ vykdytojui ir laukia rezultatų per oneshot kanalus, išlaikant visiškai asinchronišką Rust pusę.
  • Nepermatomas žymeklio valdymas: C++ objektai yra apvynioti Rust struktūromis, kurios įgyvendina Drop, skirtą deterministiniam valymui, užkertant kelią atminties nutekėjimui per kalbos ribas.
  • Serializavimas ties riba: sudėtingos duomenų struktūros nuosekliai sujungiamos į FlatBuffers FFI sluoksnyje, išvengiant trapios struktūrų išdėstymo atitikties ir įgalinant nepriklausomą kiekvienos pusės evoliuciją.
  • Panikos izoliacija: Rust catch_unwind apgaubia kiekvieną FFI įėjimo tašką, kad panika niekada neperžengtų kalbos ribos, o tai būtų neapibrėžtas elgesys.

Šis modelis suteikė mums kelių gijų Rust pralaidumą ir patikimą C++ logiką – neperrašant nė vienos pradinių verslo taisyklių eilutės.

Kokių didžiausių spąstų reikia vengti?

Pavojingiausia klaida yra daryti prielaidą, kad C++ kodas yra saugus gijai, o ne. Visuotinė būsena, statiniai kintamieji ir nepasikartojantys bibliotekos iškvietimai sukels duomenų lenktynes, kurių Rust kompiliatorius negali aptikti už FFI ribos. Rust saugumo garantijos sustoja ties nesaugiu bloku – už viską viduje atsakote jūs.

Pagrindinė įžvalga: Rust garantuoja atminties saugumą pagal savo kodą, tačiau kai peržengiate FFI ribą į C++, paveldėsite visas su C++ susijusias gijų saugos problemas. Architektūra aplink tą ribą yra svarbesnė nei kodas abiejose jos pusėse.

💡 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 →

Kitas dažnas spąstas yra viso gyvenimo valdymas. C++ objektai nedalyvauja Rust skolinimosi tikrinime. Jei Rust atmeta nuorodą, o C++ vis dar turi žymeklį, gausite nenaudojamų klaidų, kurias žiauriai sunku diagnozuoti. Tai išsprendėme taikydami griežtą nuosavybės semantiką: C++ objektai visada priklauso tiksliai vienam „Rust“ įvyniotuvui, o bendra prieiga vyksta per lanku pagrįstą nuorodų skaičiavimą „Rust“ pusėje.

Dėl našumo pertekliniai FFI iškvietimai sukuria papildomų išlaidų, susijusių su konteksto perjungimu ir serializavimu. Kai tik įmanoma, atliekame paketines operacijas, siųsdami darbo elementų eilę į C++ vykdytoją, o ne atlikdami atskirus kelių kalbų skambučius.

Kaip šis metodas veikė gamyboje?

Įdiegę hibridinę architektūrą visoje platformoje, įvertinome konkrečius patobulinimus. Modulių, kurie anksčiau trukdė nuosekliam C++ apdorojimui, užklausos pralaidumas padidėjo 3,4 karto. Tail latency (p99) dropped by 61% because Rust's async runtime could process independent requests concurrently while C++ handled computation-heavy tasks on its dedicated thread.

Dar svarbiau, kad per pirmuosius šešis gamybos mėnesius neturėjome jokių su vienalaikiškumu susijusių klaidų. The thread-confinement pattern made it structurally impossible for C++ code to be called from multiple threads, while Rust's type system prevented data races on its side of the boundary. This was a significant improvement over our previous approach of trying to add threading to C++ with mutexes, which had produced three race-condition incidents in a single quarter.

Inžinierių komanda taip pat pranešė apie greitesnius iteracijos ciklus. Naujos funkcijos gali būti sukurtos „Rust“ su visišku lygiagrečio palaikymu, o esami C++ moduliai ir toliau veikia be pakeitimų. Ši laipsniška strategija reiškė, kad niekada neturėjome didelės rizikos „didžiojo sprogimo“ migracijos – tik nuolatinis, išmatuojamas pagerėjimas.

Dažniausiai užduodami klausimai

Ar Rust gali iškviesti vienos gijos C++ bibliotekas be pakeitimų?

Taip, bet turite užtikrinti, kad visi iškvietimai į tą biblioteką vyktų iš vienos gijos. Standartinis modelis yra sukurti specialią vykdytojo giją, kuri nuosekliai sujungia visus C++ skambučius per kanalą. Rust asinchronizavimo užduotys pateikia užklausas ir laukia atsakymų neblokuodamos kelių gijų vykdymo laiko. Pats C++ kodas nereikalauja jokių pakeitimų – saugos apribojimai taikomi tik rūdžių pusėje.

Ar FFI pridėtinės išlaidos yra pakankamai didelės, kad paveiktų programos našumą?

Atskiri FFI skambučiai turi minimalių papildomų išlaidų – paprastai mažiau nei 10 nanosekundžių paprastam funkcijų iškvietimui. Tačiau sudėtingų duomenų struktūrų serializavimas ir gijų sinchronizavimas ties riba padidėja, jei atliekate tūkstančius smulkių skambučių. Vykdant paketų sudarymo operacijas ir naudojant nulinės kopijos serializavimo formatus, pvz., „FlatBuffers“ arba „Cap'n Proto“, pridėtinės išlaidos yra nereikšmingos net esant dideliam mastui.

Ar turėtume perrašyti C++ kodų bazę Rust, o ne sąsają?

Daugumai komandų laipsniška sąsaja yra saugesnis ir greitesnis kelias. Visiškas perrašymas sukelia kelis mėnesius inžinerinę riziką, o vartotojas neturi jokios vertės iki pabaigos. Interfacing lets you ship improvements immediately, validate the Rust approach in production, and migrate modules one at a time based on where concurrency delivers the most impact. Perrašykite tik tuos modulius, kuriuose FFI ribos priežiūros išlaidos viršija perrašymo išlaidas.


Mewayz kuriame infrastruktūrą, kuri keičiasi tiek techniškai, tiek eksploataciniu požiūriu. Mūsų 207 modulių verslo OS padeda 138 000 komandų vykdyti išmanesnes darbo eigas nuo 19 USD per mėnesį. Nesvarbu, ar valdote projektus, automatizuojate operacijas ar plečiate savo verslą, „Mewayz“ prisitaiko prie jūsų darbo būdo. Pradėkite nemokamą bandomąją versiją adresu app.mewayz.com ir sužinokite, ką moderni verslo OS gali padaryti jūsų komandai.