Niecały rok temu, pokazałem szefostwu że może warto było użyć Rusta w jednym projekcie zamiast na maxa optymalizować pythona, z którym mieliśmy od groma wydajnościowych problemów, ale przez długi czas odpowiedzią było "nie", bo to nie jest nam potrzebne(kolega optował za C++ i całe szczęście jego pomysł miał bardziej stanowcze "nie" - zbyt wiele wycierpiałem by używać go jako głównego języka w projekcie który tworzę).

Dopiero pół roku temu najbardziej krytyczne części powoli zaczęły być przepisywane na ten język i jak można było przewidzieć, problemy wydajnościowe przy naszym używaniu programu prawie nie występują.

Obecnie projekt ma ~50k linii w pythonie i ~10k linii w rust i szefostwo uznało, że najwyższy czas przepisać to na rusta, skoro tak dobrze się sprawdza i naprawi kilka pomniejszych błędów i oczywiście jako jeden z tych co zna ten język, znaczna część pracy przypada mnie.

Minusem jest to że jest od groma przy tym roboty na kilka miesięcy i być może to w 100% nie będzie to działało identycznie jak wcześniej(a powinno).
Plusem jest to że w końcu zaczynam się naprawdę uczyć tego języka - przy robieniu projektów dla zabawy nie musiałem zbytnio się przejmować stylem, a tutaj nie dość że trzeba pisać programy tak, by się samemu je rozumiało, to trzeba zrobić je tak by inni je zrozumieli - a rust czasami bywa trudnawy do zrozumienia.

#programowanie
#rustlang
mpower

@krokietowy sam chyba sobie zdefiniowałeś nowe wyzwanie Za każdą tego typu decyzją w projekcie idzie ogromna odpowiedzialność, żeby nie okazało się, że po przepisaniu wydajność wzrosła, ale utrzymywalność spadła zbyt mocno i w efekcie nie da się rozwijać projektu tak szybko, jak wcześniej. Rusta tylko liznąłem, ale wierzę, że da się i tutaj stosować jakieś dobre praktyki i wzorce projektowe, żeby nie stworzyć spaghetti.

być może to w 100% nie będzie to działało identycznie jak wcześniej(a powinno).

Postarajcie się najpierw zmigrować testy, najlepiej gdyby były to testy BDD. Wtedy zwiększycie prawdopodobieństwo zachowania procesów biznesowych w niezmienionej formie.

krokietowy

@mpower Czytałem ostatnio, że właśnie bardzo ważne jest by przy portowaniu upewnić się że testy są dobrej jakości oraz kod jest w miarę czytelny - https://vercel.com/blog/finishing-turborepos-migration-from-go-to-rust#what-we-learned


Myślę że logika bardziej zaawansowanych operacji jest w miarę dobrze otestowana i przy odpowiednim zawzięciu powinno ją się dać jako tako przeportować z testami.


W rust przynajmniej na tą chwilę, kod po przeportowaniu ma ~5/10 razy mniej testów niż oryginalnie - glównie dla tego że jest go trudniej testować, jak i przez to że po stronie pythona nie ma aż takich gwarancji typów i aż tak jawnej obsługi błędów.

mpower

@krokietowy jeśli już na tym etapie jest trudno testować, to ja naprawdę mocno bym przemyślał to portowanie. Było wiele firm, które (pod)upadły przez nieumiejętnie przeprowadzone migracje tego typu, które są (jak słusznie zauważyłeś) bardzo kosztowne i czasochłonne, a ostatecznie może się okazać że zwyczajnie nie realizują tego co powinny. Pokrycie testów to must have przy portowaniu i zmniejszanie tego pokrycia to nie jest dobry prognostyk. Mimo wszystko trzymam kciuki za powodzenie, ale z doświadczenia radzę Wam to dobrze zweryfikować, możecie uniknąć dużych problemów za chwilę.

666

@krokietowy ciekawe, ciekawe. Powiesz cos wiecej jakiego typu problemy wydajnosciowe udalo sie ogarnac? Mikroserwisy, monilit czy cos pomiedzy?

krokietowy

@666 Tworzymy oprogramowanie na bardzo starych urządzeniach (2 albo 4 rdzenie po ~800MHz) - ale np. mają dyski ssd.

Mamy 5 programów komunikujących się ze sobą przez resty, ale są one trochę zbyt duże by móc je nazwać mikroserwisami.


Na początku problemów z wydajnością nie mieliśmy prawie żadnych, ale wraz ze zwiększoną ilością danych do przetworzenia, problemy się pojawiły:


  • długie uruchamianie programu - 15s to minimum, obecny moduł w rust uruchamia się w 1s albo i mniej(i dodatkowe zależności nie zwiększają tego czasu, w przeciwieństwie do pythona, gdzie nowa zależnośc może dodać kilka sekund do czasu uruchomienia)

  • pobieranie dużej ilości danych z bazy i konwersja do modeli pydantica(wersja 1) - na początku korzystaliśmy z ormów, ale ostatecznie by przyspieszyć to, zaczęliśmy używać czystego sql i ręcznie konwertować to do obiektów pydantica. Ostatecznie pewnie i tak zrezygnowalibyśmy z pydantica by przyspieszyć działanie, ale odbiło by się to na wykrywaniu błędów w trakcie tworzenia programu.

  • Przesyłanie dużej ilości danych przez resty(fastapi) - serializacja jsona/deserializacja + wrzucanie to do modeli pydantica i walidacja trwały zbyt długo

  • Wolne działanie zewnętrznych bibliotek - np. numpy/scipy są często wolniejsze niż najzwyklejsze operacje po stronie Rust, bez użycia osobnych bibliotek. Jeden z wykresów przy użyciu matplotlib generował się ~3s, a jego odpowiednik rustowy przy użyciu plotters-rs, robił to w ~100ms i to z dużo wyższą rozdzielczością


Reszta rzeczy jakoś nie sprawiała problemów, bo oprócz powyższych, bardzo wymagających tasków, cały kod jest asynchroniczny i nie miał żadnych zadyszek przy wykonywaniu swoich zwykłych zadań.

666

@krokietowy dzieki za odpowiedz. lubie takie opowiesci z pierwszej, maja najwieksza wartosc.

Jesli to nie tajemnica to jaki to ARM? nie ma problemu z kompilacja rusta? Hobbistycznie przysiadlem do bare metal C dla prockow stm32 i jak patrzylem Rusta pod embeded to nie ma podjazdu do C

krokietowy

@666 Któryś z imx, chyba w wersji 6 - akurat teraz nie mam możliwości sprawdzenia aktualnego modelu.

Używamy yocto, więc dodanie paczki z rustem, jest potwornie proste, bo wystarczy(zwykle) jedna paczka dla programu.

W pythonie dla każdej zależności, potrzebowaliśmy osobny plik bitbake co było nieprzyjemne.

Ramu jest chyba 2GB i korzystamy z std niemal wszędzie, więc to raczej jest główna różnica z stm32, na którym raczej trzeba oszczędzać każdą komórkę pamięci.


cross build --target armv7-unknown-linux-gnueabihf


w całości wystarcza by skompilować projekt, bez żadnych dodatkowych paczek i zmian unikalnych dla arm i ręcznie przesłać go na urządzenie, by tam go uruchomić.

krokietowy

@Giblet5280 Na początku myśleliśmy żeby zrobić jedną aplikację z dwoma językami, jednak to by komplikowało proces budowania i oprócz błędów z rusta i pythona, dodawało błędy z warstwy pośredniej, więc obecnie mamy dwa osobne serwisy o bardzo podobnej odpowiedzialności.


Najprawdopodobniej byśmy użyli PyO3 - https://github.com/PyO3/pyo3, gdybyśmy szli w tę stronę.

Astro

@krokietowy wybacz za bezpośrednie pytanie ale czy dostałeś znaczącą podwyżkę? Bo to chyba najlepszy moment na negocjacje.

Pokazałeś dużo zapału, warto by ktoś go docenił.

krokietowy

@Astro Podwyżka była, ale przed ostateczną decyzją o przepisaniu i raczej była związana z rosnącym moim stażem pracy i odejściem jednego ze współpracowników.


W sumie, nawet gdyby nie było podwyżki, to i tak bym został tam dłuższą chwilę, bo pracuje mi się tam po prostu dobrze, w fajnej atmosferze, robię w miarę ciekawe projekty i się dużo uczę(przyszedłem jako junior i od początku cisnęli mnie, bym się uczył robić dobry software, a nie że dali mi klawiaturę i kazali po prostu robić taski, by po prostu działały).

Astro

@krokietowy no to powinieneś poprosić o podwyżkę w związku z projektem.

Po pierwsze żeby doceniali wagę pracownika.

Po drugie żebyś ty nie czuł sie wydymany gdy pracy i komplikacji będzie przybywało wraz ze zmianami.

A po trzecie żeby nie psuć rynku :)

rm-rf

@krokietowy no wszystko spoko tylko jest jedno ale - uczenie się języka na produkcyjnej aplikacji to koniec końców i tak jej pisanie raz jeszcze po skończeniu nauki. Niestety znam to

krokietowy

@rm-rf Pythonową wersję robiłem w dużej części sam i to była moja pierwsza styczność z pythonem, ale niemal każdy MR był czujnie sprawdzany przez bardziej obeznanych programistów i ostatecznie, mimo że nie jest to ósmy cud świata, to kod jest w miarę czytelny i wystarczająco łatwy do modernizacji i gdyby nie problemy wydajnościowe, to by został na miejscu.


Z rustową wersją powinno być dużo łatwiej, bo mam więcej doświadczenia, zarówno w rust jak i pisaniu aplikacji, a oczy które sprawdzają MR się nie zmieniły, więc nie sądzę by został stworzony program który trzeba by było później przepisywać

rm-rf

@krokietowy a kompilowaliscie to do jakiejś implementacji c Pythona tak z ciekawości?

krokietowy

@rm-rf Myśleliśmy by użyć pypy albo nuitika, ale docelowy sprzęt ma 32 bitowy procesor arm, więc pewnie byłoby z tym trochę problemów i w końcu nawet nie spróbowaliśmy zrobić żadnych testów(nie jestem pewien czy wszystkie paczki których używaliśmy wspierały coś innego niż Cpython).


Mieliśmy jeszcze nadzieję na Cpythona >3.12 i kilkunastoprocentowe przyspieszenie, ale najwcześniej nową wersję moglibyśmy użyć pod koniec roku, a potrzebowaliśmy przyspieszania już teraz.

rm-rf

@krokietowy spoko rozumiem o czym mówisz bo u nas była potrzeba przyspieszyć z obrabiałem jednego requestu tylko, że w lambda i zmuszeni byliśmy zrobić to i w python i w node I w Java dla porównania. Koniec końców najwolniejszy był node, python dawał sobie radę i na przykład dla 10k requestow był najszybszy ale dla 150k Java wygrywała po rozgrzaniu jvm.

Zaloguj się aby komentować