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?

cnd
źródło
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 ++).

Doktor Brown
źródło
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.

rwong
źródło
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ę).

Paddy Landau
źródło
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,