Czy podczas pisania kodu muszę myśleć o skompilowanym kodzie maszynowym?


20

Na przykład mam następujący kod:

auto z = [](int x) -> int {
    if (x > 0) {
        switch (x) {
            case 2: return 5;
            case 3: return 6;
            default: return 1;
            }
        }
    return 0;
    };

A potem dzwonię kilka razy. W kodzie asm widzę wywołania zewnętrzne z lambda .... coś ... To staje się trudne do odczytania i myślę, że może to również powodować wydajność. Więc może wygrywam w metaprogramowaniu, ale czy przegrywam w debugowaniu asm i wydajności? Czy powinienem unikać nowoczesnych funkcji językowych, makr i innych aspektów programowania, aby mieć pewność co do wydajności i prostoty debugowania?


1
W zależności od wersji kompilatora i dołączonej biblioteki standardowej lambda może być rzeczywiście nieefektywnie zaimplementowana. Zobacz to pytanie na Stackoverflow. Jednak odpowiedzialność za ulepszenia powinna spoczywać na dostawcy kompilatora.
— rwong

15
Nie powinieneś debugować kodu asemblera, chyba że jesteś w ścieżce krytycznej dla wydajności. Również „czysty kod”! = „Dobre wyniki”.
— BЈовић

Napraw swoje wcięcie, proszę. Próbowałem to zrobić, ale wydaje się, że nie można edytować tylko białych znaków.
— Christoffer Hammarström

3
@Heather: Wygląda na to, że używasz stylu Ratliff , którego nigdy wcześniej nie widziałem i trudno ci go czytać. Jest to z pewnością jeden z mniej znanych. Odniosłem wrażenie, że nie zrobiłeś właściwego wcięcia. Nieważne, jeśli uznasz to za czytelne, po prostu się nie zgadzam.
— Christoffer Hammarström

1
Jest ifto całkowicie zbędne w przykładowym kodzie i chociaż kompilator prawdopodobnie złapie, że nie ma powodu, aby kusić złą prognozę gałęzi.
— dmckee,

Odpowiedzi:


59

Czy podczas pisania kodu muszę myśleć o skompilowanym kodzie maszynowym?

Nie , nie kiedy piszesz swój kod po raz pierwszy i nie odczuwasz żadnych realnych, mierzalnych problemów z wydajnością. W przypadku większości zadań jest to standardowy przypadek. Zbyt wczesne myślenie o optymalizacji nazywa się „przedwczesną optymalizacją”, i istnieją dobre powody, dla których D. Knuth nazwał to „źródłem wszelkiego zła” .

Tak , kiedy mierzysz prawdziwe, dające się udowodnić wąskie gardło wydajności i identyfikujesz ten konkretny konstrukt lambda jako główną przyczynę. W takim przypadku dobrym pomysłem może być zapamiętanie „prawa nieszczelnych abstrakcji” Joela Spolsky'ego i przemyślenie, co może się wydarzyć na poziomie asm. Ale uwaga, możesz być zaskoczony, jak niewielki będzie wzrost wydajności, gdy zamienisz konstruktor lambda na „niezbyt nowoczesny” konstrukt językowy (przynajmniej przy korzystaniu z porządnego kompilatora C ++).


2
+1 Zwięzłe, dokładne i łatwe do naśladowania dla zwykłych Doktorów, cieszę się, że mamy cię tutaj.
— Jimmy Hoffa

Uzgodniona, bardzo jasna odpowiedź.
— cnd

8

Wybór pomiędzy lambda a klasą funktorów jest kompromisem.

Zysk z lambda jest w większości składniowy, ponieważ minimalizuje liczbę podstawek i pozwala na wpisanie kodu związanego z koncepcją wewnątrz funkcji, która będzie go używać (natychmiast lub później).

Pod względem wydajności nie jest to gorsze niż klasa funktorów , która jest strukturą C ++ lub klasą zawierającą pojedynczą „metodę”. W rzeczywistości kompilatory traktują lambda nie inaczej niż generowana przez kompilator klasa funktorów za sceną.

// define the functor method somewhere
struct some_computer_generated_gibberish_0123456789
{
    int operator() (int x) const
    {
        if (x == 2) return 5;
        if (x == 3) return 6;
        return 0;
    }
};

// make a call
some_computer_generated_gibberish_0123456789 an_instance_of_0123456789;
int outputValue = an_instance_of_0123456789(inputValue);

W twoim kodzie pod względem wydajności nie różni się niczym od wywołania funkcji, ponieważ ta klasa funktorów nie ma żadnego stanu (ponieważ ma pustą klauzulę przechwytywania), a zatem nie wymaga alokacji, konstruktora ani zniszczenia.

int some_computer_generated_gibberish_0123456789_method_more_gibberish(int x)
{
    if (...) return ...;
    return ...;
}

Debugowanie dowolnego nietrywialnego kodu C ++ za pomocą deasemblera zawsze było trudnym zadaniem. Jest to prawdą z użyciem lambda lub bez. Jest to spowodowane zaawansowaną optymalizacją kodu przez kompilator C ++, która spowodowała zmianę kolejności, przeplatanie i eliminację martwego kodu.

Aspekt zmieniający nazwy jest nieco niesmaczny, a obsługa debuggera dla lambda jest wciąż w powijakach . Można mieć tylko nadzieję, że z czasem poprawi się obsługa debuggera.

Obecnie najlepszym sposobem debugowania kodu lambda jest użycie debugera, który obsługuje ustawianie punktów przerwania na poziomie kodu źródłowego, tj. Poprzez podanie nazwy pliku źródłowego i numeru linii.


3

Aby dodać do odpowiedzi @DocBrown, pamiętaj, że obecnie procesory są tanie, ale praca jest droga.

W ogólnym koszcie programu sprzęt jest zwykle trywialny w porównaniu z kosztami utrzymania, które są zdecydowanie najdroższą częścią typowego projektu (nawet więcej niż jego rozwój).

Dlatego Twój kod musi optymalizować konserwację ponad wszystko inne, z wyjątkiem sytuacji, gdy wydajność ma krytyczne znaczenie (i nawet wtedy należy rozważyć konserwację).


Tylko częściowo prawda. Jeśli twój kod działa O (n ^ 2) (kwadratowy) i możesz zrobić coś lepszego, powiedz O (log (n)) (logarytmiczny), to sprzęt nigdy nie będzie miał większego wzrostu wydajności niż zmiana kodu. W przypadku określonym przez oryginalny plakat jest to bardzo mało prawdopodobne.
— gnash117

@ gnash117 - tak, masz rację, jeśli kod ma być uruchamiany wiele razy; dziękuję za zwrócenie na to uwagi. W takich przypadkach udokumentowanie kodu w sposób przejrzysty zapewni utrzymanie go przy jednoczesnym zwiększeniu wydajności.
— Paddy Landau

„praca jest droga” - Zgadza się. Czas twojego klienta jest bardzo ważny i często drogi.
— Cerad,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.