Kiedy powinienem używać szablonów wyrażeń C ++ w informatyce, a kiedy * nie * powinienem ich używać?

24

Załóżmy, że pracuję nad kodem naukowym w C ++. W niedawnej dyskusji z kolegą argumentowano, że szablony wyrażeń mogą być bardzo złe, potencjalnie czyniąc oprogramowanie kompilowalnym tylko w niektórych wersjach gcc. Podobno problem ten wpłynął na kilka kodów naukowych, jak wspomniano w podtytułach tej parodii Downfall . (Są to jedyne znane mi przykłady, stąd link).

Jednak inne osoby twierdziły, że szablony wyrażeń są przydatne, ponieważ mogą przynieść wzrost wydajności, jak w tym artykule w SIAM Journal of Scientific Computing , unikając przechowywania wyników pośrednich w zmiennych tymczasowych.

Nie znam się dużo na metaprogramowaniu szablonów w C ++, ale wiem, że jest to jedno podejście stosowane w automatycznym różnicowaniu i arytmetyki przedziałowej, i tak oto wdałem się w dyskusję na temat szablonów wyrażeń. Biorąc pod uwagę zarówno potencjalne zalety w wydajności, jak i potencjalne wady w utrzymaniu (jeśli to nawet właściwe słowo), kiedy powinienem używać szablonów wyrażeń C ++ w informatyce i kiedy powinienem ich unikać?

Geoff Oxberry
źródło
Ach, wideo jest zbyt zabawne. Nie wiedziałem, że istnieje. Kto to zrobił, wiesz?
Wolfgang Bangerth,
Brak pomysłu; kilka osób z PETSc przesłało mi w pewnym momencie linki. Myślę, że udało się to twórcy FEniCS.
Geoff Oxberry
Link do filmu jest zepsuty i umieram z ciekawości. Nowy link?
Praxeolitic
O cholera, nieważne, widzę, że youtube przyszedł do naszych filmów Hitlera.
Praxeolitic

Odpowiedzi:

17

Mój problem z szablonami wyrażeń polega na tym, że są one bardzo nieszczelną abstrakcją. Dużo pracy spędzasz na pisaniu bardzo skomplikowanego kodu, aby wykonać proste zadanie z ładniejszą składnią. Ale jeśli chcesz zmienić algorytm, musisz zadzierać z brudnym kodem, a jeśli wpadniesz na różne typy lub składnię, otrzymasz całkowicie niezrozumiałe komunikaty o błędach. Jeśli Twoja aplikacja doskonale odwzorowuje się na bibliotekę opartą na szablonach wyrażeń, warto rozważyć, ale jeśli nie jesteś pewien, polecam po prostu napisać normalny kod. Jasne, kod wysokiego poziomu jest mniej ładny, ale możesz po prostu zrobić to, co trzeba. Korzyścią jest to, że czas kompilacji i rozmiary plików binarnych znacznie spadną i nie będziesz musiał radzić sobie z ogromną zmiennością wydajności dzięki wyborowi kompilatora i flagi kompilacji.

Jed Brown
źródło
Tak, widziałem niektóre z długich komunikatów o błędach z pierwszej ręki, gdy musiałem przenieść kod z gcc 2.95 na gcc 4.x, a kompilator zgłaszał różnego rodzaju błędy dotyczące szablonów. Mój kolega z laboratorium opracowuje bibliotekę szablonów do arytmetyki interwałów w C ++ (dodając nowe funkcje, których nie ma w Boost :: Interval w celu przeprowadzenia dalszych badań), i nie chcę, aby kod stał się koszmarem kompilować.
Geoff Oxberry
12

Inni komentowali kwestię tego, jak trudno jest pisać programy ET, a także złożoność zrozumienia komunikatów o błędach. Pozwólcie mi skomentować kwestię kompilatorów: Prawdą jest, że jakiś czas temu jednym z dużych problemów było znalezienie kompilatora, który jest wystarczająco zgodny ze standardem C ++, aby wszystko działało i było przenośne. W rezultacie znaleźliśmy wiele błędów - mam na imię 2-300 raportów o błędach, rozpowszechnianych na gcc, Intel icc, IBM xlC i Portland's pgicc. W związku z tym skrypt konfiguracyjny deal.II jest repozytorium dużej liczby testów błędów kompilatora, głównie w zakresie szablonów, deklaracji znajomych, przestrzeni nazw itp.

Okazuje się jednak, że twórcy kompilatorów naprawdę dobrze sobie poradzili: dzisiaj gcc i icc przeszły wszystkie nasze testy i łatwo jest napisać kod, który jest przenośny między nimi. Powiedziałbym, że PGI nie jest daleko w tyle, ale ma wiele dziwactw, które wydają się nie ustępować przez lata. Z drugiej strony xlC to zupełnie inna historia - naprawiają błąd co 6 miesięcy, ale pomimo przesyłania raportów o błędach od lat, postęp jest bardzo powolny, a xlC nigdy nie był w stanie skompilować oferty.

Oznacza to wszystko: jeśli będziesz trzymać się dwóch dużych kompilatorów, możesz spodziewać się, że działają one tylko dzisiaj. Ponieważ większość dzisiejszych komputerów i systemów operacyjnych zazwyczaj ma co najmniej jeden z nich, to wystarczy. Jedyną platformą, na której jest trudniej, jest BlueGene, gdzie kompilatorem systemowym jest zazwyczaj xlC ze wszystkimi jego błędami.

Wolfgang Bangerth
źródło
Z ciekawości próbowałeś kompilować się z nowymi kompilatorami xlc na / Q?
Aron Ahmadia
Nie. Przyznaję, że zrezygnowałem z XLC.
Wolfgang Bangerth
5

Dawno temu eksperymentowałem trochę z ET, kiedy, jak wspomniałeś, kompilatory wciąż miały z nimi problemy. Użyłem biblioteki blitz do algebry liniowej w moim kodzie. Problem polegał na uzyskaniu dobrego kompilatora, a ponieważ nie jestem doskonałym programistą C ++, interpretuję komunikatów o błędach kompilatora. Ten ostatni był po prostu niemożliwy do zarządzania. Kompilator generuje średnio około 1000 wierszy komunikatów o błędach. W żaden sposób nie byłem w stanie szybko znaleźć mojego błędu programistycznego.

Więcej informacji można znaleźć na stronie oonumerycznej (odbywają się dwa warsztaty ET).

Ale trzymałbym się od nich z daleka ...

GertVdE
źródło
Komunikaty o błędach kompilatora są rzeczywiście jednym z moich problemów. Po kompilacji niektórych szablonów kodu C ++ w celu tworzenia bibliotek dla moich projektów kompilator może generować setki wierszy komunikatów ostrzegawczych. Jednak to nie jest mój kod, nie rozumiem go i ogólnie mówiąc, działa, więc zostawiam go w spokoju. Długie, tajemnicze komunikaty o błędach nie wróży dobrze debugowania.
Geoff Oxberry
4

Problem zaczyna się już od terminu „szablony wyrażeń (ET)”. Nie wiem, czy istnieje na to dokładna definicja. Ale w powszechnym użyciu w jakiś sposób łączy „sposób kodowania wyrażeń algebry liniowej” i „sposób obliczania”. Na przykład:

Kodujesz operację wektorową

v = 2*x + 3*y + 4*z;                    // (1)

I jest obliczany przez pętlę

for (int i=0; i<n; ++i)                 // (2)
    v(i) = 2*x(i) + 3*y(i) + 4*z(i);

Moim zdaniem są to dwie różne rzeczy i należy je oddzielić: (1) to interfejs i (2) jedna możliwa implementacja. Mam na myśli, że jest to powszechna praktyka w programowaniu. Pewnie (2) może być dobrą domyślną implementacją, ale ogólnie chcę móc korzystać ze specjalistycznej, dedykowanej implementacji. Na przykład chcę, aby ta funkcja była jak

myGreatVecSum(alpha, x, beta, y, gamma, z, result);    // (3)

zostań wywołany, gdy koduję (1). Może (3) po prostu używa wewnętrznie pętli jak w (2). Jednak w zależności od rozmiaru wektora inne implementacje mogą być bardziej wydajne. W każdym razie, jakiś ekspert od wysokiej wydajności może wdrożyć i dostroić (3) w jak największym stopniu. Jeśli więc (1) nie można zmapować na wywołanie (3), raczej unikam cukru syntaktycznego (1) i od razu wywołuję (3).

To, co opisuję, nie jest niczym nowym. Wręcz przeciwnie, to jest idea stojąca za BLAS / LPACK:

  • Wszystkie operacje krytyczne dla wydajności w LAPACK są wykonywane przez wywołanie funkcji BLAS.
  • BLAS właśnie definiuje interfejs dla tych często używanych wyrażeń algebry liniowej.
  • Dla BLAS istnieją różne zoptymalizowane implementacje.

Jeśli zakres BLAS nie jest wystarczający (np. Nie zapewnia funkcji takiej jak (3)), wówczas można rozszerzyć zakres BLAS. Tak więc ten dinozaur z lat 60. i 70. dzięki swojemu narzędziu z epoki kamiennej dokonuje czystego i ortogonalnego rozdziału interfejsu i implementacji. To zabawne, że (większość) numerycznych bibliotek C ++ nie osiąga tego poziomu jakości oprogramowania. Chociaż sam język programowania jest o wiele bardziej wyrafinowany. Nic więc dziwnego, że BLAS / LAPACK wciąż żyje i aktywnie się rozwija.

Więc moim zdaniem ET nie są złe same w sobie. Jednak sposób, w jaki są one powszechnie stosowane w bibliotekach numerycznych C ++, przyniósł im bardzo złą reputację w naukowych kręgach komputerowych.

Michael Lehn
źródło
Michael, myślę, że brakuje ci jednego z szablonów wyrażeń. Twój przykład kodu (1) w rzeczywistości nie odwzorowuje żadnych zoptymalizowanych wywołań BLAS. W rzeczywistości, nawet jeśli istnieje procedura BLAS, narzut wywołania funkcji BLAS sprawia, że ​​jest ona dość straszna dla małych wektorów i macierzy. Zaawansowane biblioteki szablonów wyrażeń, takie jak Blaze i Eigen, mogą korzystać z odroczonej oceny wyrażeń, aby uniknąć użycia elementów tymczasowych, ale jestem przekonany, że prawie nic innego niż język specyficzny dla domeny nie będzie w stanie pokonać ręcznie zwiniętej algebry liniowej.
Aron Ahmadia
Nie, myślę, że nie rozumiesz sedna sprawy. Musisz rozróżnić (a) BLAS jako specyfikację często potrzebnej operacji algebry liniowej (b) implementację BLAS, taką jak ATLAS, GotoBLAS itp. BTW, jak to działa w FLENS: Domyślnie wyrażenie takie jak (1) być oceniane przez trzykrotne wywołanie axpy z BLAS. Ale bez modyfikacji (1) mógłbym również ocenić to tak, jak w (2). Logiczne jest więc to, co następuje: jeśli operacja taka jak w (1) jest ważna, to zestaw określonych operacji BLAS (a) można rozszerzyć.
Michael Lehn
Kluczową kwestią jest: notacja taka jak „v = x + y + z” i sposób jej ostatecznego obliczenia powinny zostać rozdzielone. Eigen, MTL, BLITZ, blaze-lib całkowicie zawodzą pod tym względem.
Michael Lehn
1
Zgadza się, ale liczba często potrzebnych operacji algebry liniowej jest kombinatoryczna. Jeśli zamierzasz używać języka takiego jak C ++, masz do wyboru albo implementację według potrzeb za pomocą szablonów wyrażeń (jest to podejście Eigen / Blaze) poprzez inteligentne połączenie podbloków i algorytmów przy użyciu odroczonej oceny lub implementację ogromnej biblioteka wszystkich możliwych procedur. Nie popieram żadnego z tych podejść, ponieważ ostatnie prace w Numba i Cython pokazują, że możemy uzyskać podobną lub lepszą wydajność, pracując z języków skryptowych wysokiego poziomu, takich jak Python.
Aron Ahmadia
Ale znowu narzekam na fakt, że tak wyrafinowane (w sensie skomplikowanych, ale nieelastycznych) bibliotek, takie jak Eigen, ściśle łączą notację i mechanizm oceny, a nawet uważają, że to dobrze. Jeśli używam narzędzia takiego jak Matlab, chcę po prostu kodować rzeczy i polegać na tym, że Matlab robi to, co najlepsze. Jeśli używam języka takiego jak C ++, chcę mieć kontrolę. Doceń, jeśli istnieje domyślny mechanizm oceny, ale musi istnieć możliwość jego zmiany. W przeciwnym razie wracam i wywołuję funkcje bezpośrednio w C ++.
Michael Lehn