Wpis z mikrobloga

Mam pytanko: jaki jest sens porównywania wydajności języków kompilowanych jak Rust czy C++ jeśli często korzystają z tego samego kompilatora? Skąd miałaby się brać różnica w wydajności?
Z tego co widzę to domyślny kompilator Rusta wykorzystuje LLVM, a do C++ przecież też jest Clang na LLVM. Więc dlaczego sam język miałby sprawiać że jeden czy drugi kod maszynowy będzie szybszy?
Czy chodzi o to że jakiś język w lepszy sposób wyraża pewne założenia dla kompilatora i ten może poczynić więcej optymalizacji?
Czy bardziej o to że np. frontend C++ dla LLVM jest bardzie dopracowany niż frontent Rusta i generuje lepszej jakości LLVM IR? Ale czy wtedy porównanie wydajności języków nie zamienią się w porównanie wydajności frontendów kompilatorów?
#programowanie #rustlang #cpp #informatyka
  • 10
Czy chodzi o to że jakiś język w lepszy sposób wyraża pewne założenia dla kompilatora i ten może poczynić więcej optymalizacji?


@Passer93: poniekąd tak. Dużo łatwiej jest napisać wyżyłowany kompilator dla C, gdzie język jest praktycznie "przenośnym assemblerem" i jest projektowany z myślą o kompilatorach (np. wprowadza się keywordy specjalnie dla nich - restrict, register) niż dla języka o bardzo wysokim poziomie abstrakcji, gdzie masz dużo więcej konstrukcji, idiomów
Czy chodzi o to że jakiś język w lepszy sposób wyraża pewne założenia dla kompilatora i ten może poczynić więcej optymalizacji?


@Passer93: tak. Np. w Ruscie nie masz problemów z aliasowaniem, przez co kompilator może czasem poczynić optymalizacje niemożliwe w innym wypadku. W innych wypadkach frontend może wypluć kod lepszej jakości, który będzie łatwiej przełknąć w backendzie. Ogólnie w takich językach starasz się porównać wydajność idiomatycznego kodu, bo to taki chcesz
@Passer93:

1. brandzlowanie się do minimalnych różnic wydajności między językami i kompilatorami kiedy przepisanie kodu z lepszymi strukturami danych i unikając cache miss często przyśpieszy go 1000-krotnie jest bez sensu. Jak chcesz mieć szybki kod to zastanów się jakie operacje będziesz wykonywał i jak zaprojektować struktury danych żeby te operacje nie ruszały dużych obszarów pamięci. A potem puści profiler i zoptymalizuj co większe hotspoty.

2. jeśli już wchodzimy w kwestie różnych
@Passer93: Dla prostych programów Clang i Rust generuje identyczny kod maszynowy.
Na pewno inaczej będzie dla dynamicznych traitów niż metod wirtualnych bo mają inną zasadę działania.
Myślę, że warto by też porównać np Arc z std::shared_ptr.
Ciekawi mnie jak jest z lambdami, szczególnie że Rust posiada FnOnce.
@Passer93: w większości nowych języków możesz pisać bardzo wydajne i przy dużym uporze możesz generować taki sam kod. Problemem są abstrakcje, które dostarcza język. Przykładowo taka Java generuje kod porównywalny z C++. Dodatkowo przez to, że kompilacja jest w runtime to dostajesz za darmo kompilację pod dany procesor + LTO+ PGO, czyli zestaw, który jest stosowany dla naprawdę małej ilości programów w C++ (np. przeglądarki), bo postawienie takiego stacku jest cholernie
@Passer93: strict-aliasing mówi, że pointery na inne typy nie będą wskazywały na tę samą lokację (pomijając różne wyjątki). Kompilator nie może jednak założyć, że np. 2 pointery na int jako parametry nie wskazują na tę samą lokację w pamięci. W ruście często może, bo np. jeśli funkcja przyjmuje dwie referencje mutable, to nie mogą wskazywać na tę samą lokację.

Z drugiej strony Rust w niektórych wersjach nie korzystał z tego założenia,
@tell_me_more: @lionbest: Dzięki wielkie za odpowiedzi.

@Saly: Jeśli chodzi o kompilacje pod dany procesor w C++ to w gcc flagi -march i -mtune pozwalają to osiągnać. Co do setupu pod LTO to nie mam pojęcia jak to wygląda, ale z tego co pamiętam to PGO w gcc było całkiem lajtowe, najpierw się budowało execa z dodatkową flagą, potem się normalnie profilowało program który podczas runtime generował dodatkowy plik z
Jeśli chodzi o kompilacje pod dany procesor w C++ to w gcc flagi -march i -mtune pozwalają to osiągnać. Co do setupu pod LTO to nie mam pojęcia jak to wygląda, ale z tego co pamiętam to PGO w gcc było całkiem lajtowe, najpierw się budowało execa z dodatkową flagą, potem się normalnie profilowało program który podczas runtime generował dodatkowy plik z informacjami dla kompilatora i na samym końcu kompilowało się program