Generalnie w zależności od kompilatora wersja z std::cout << "Hello \n"; (z/bez std:: nie robi różnicy) będzie najszybsza*, ale to de facto zależy bardzo od systemu, środowiska w którym uruchamiamy program, i tego jak bardzo kompilator optymalizuje. To powiedziawszy, mierzenie czasu wypisywania samego Hello jest ciekawe w zasadzie tylko w rozumieniu akademickim, bo w praktyce jeśli chce się wypisać dużo danych, to... wpadamy do króliczej dziury pełnej ciekawych problemów ;). A jeśli chcemy wypisać mało danych raz na jakiś czas to to jest bez większego znaczenia.
Natomiast czemu napisałem, że ta wersja będzie najszybsza, a potem i tak powiedziałem "to zależy". Na początek rozważmy każdy z przypadków (pomijając z/bez std::, bo to znika w momencie kompilacji):
cout << "Hello " << endl;
W pierwszym przypadku dzieją się 3 rzeczy:
- Najpierw wywoływany jest operator<< na obiekcie cout z podaną tablicą znaków "Hello ". To z kolei powoduje dodanie tej sekwencji do wew. bufora danych.
- Następnie wywołany jest operator<< na obiekcie cout z podanym wskaźnikiem na funkcję endl (taak, endl to jest funkcja, nie ciąg znaków).
- W następstwie tego wywołana zostaje funkcja endl, która powoduje dopisanie znaku końca linii do wewnętrznego bufora, a następnie flush, czyli wypisanie danych znajdujących się w wewnętrznym buforze.
cout << "Hello " << '\n';
W drugim przypadku dzieją się 2 rzeczy (lub 1 lub 3 rzeczy, ale do tego wrócę później):
- Pierwszy punkt jest identyczny z tym z pierwszego przypadku.
- W drugim kroku wywoływany jest operator<< na obiekcie cout z podaną liczbą typu char (czyli de facto znakiem końca linii). Ta liczba/znak będzie w rejestrze/na stosie (w cache), więc dostęp do niej będzie natychmiastowy.
Można tutaj już zauważyć, że nie było flusha. Ale do tego wrócę za chwilę, bo to jest trochę bardziej skomplikowane.
cout << "Hello " << "\n";
Trzeci przypadek jest prawie identyczny z drugim, z tą różnicą, że w przypadku drugiego wywołania funkcja operator<< dostanie adres ciągu. Ten ciąg może, ale nie musi być w pamięci podręcznej procesora. Osobiście stawiam, że będzie, bo w najgorszym wypadku CPU przed chwilą musiał z RAMu i tak ściągać "Hello ", a kompilator pewnie obie te rzeczy wrzucił koło siebie (a więc w ten sam cache-line).
cout << "Hello \n";
Czwarty przypadek to tylko jedno wywołanie, bez flush, więc z definicji powinno być najszybsze.
Póki co całkiem prosto - na papierze najszybszy jest czwarty wariant (1 wywołanie), potem drugi wariant (2 wywołania, ale drugie ma parametr w rejestrze), potem trzeci (2 wywołania, ale drugie ma parametr w pamięci), ostatecznie pierwszy (3 wywołania + flush).
Teraz do tego wszystkiego dodajmy ten cały "flush", czyli faktycznie przesłanie rzeczy z wewnętrznego bufora na standardowe wejście.
W praktyce jest tak:
- endl zawsze bezwzględnie spowoduje flush.
- W implementacjach biblioteki standardowej pod Windowsem Microsoft lubił robić flush trochę częściej, np. przy każdym wywołaniu printf. Natomiast tutaj to nie powinno mieć miejsca.
- Jeśli program jest uruchomiony ze standardowym wyjściem połączonym z terminalem, to zazwyczaj wewnętrzny bufor jest tzw trybie "buforowania linii", co przekłada się na to, że za każdym razem jak w buforze pojawi się \n, to bufor zostanie zflushowany. A więc flush będzie miał miejsce we wszystkich 4 przypadkach (co nadal uczyni czwarty przypadek najszybszym, bo mniej do roboty ogólnie).
- Jeśli natomiast program jest uruchomiony ze standardowym wyjściem podpiętym do czegokolwiek innego (potok, socket, /dev/null, etc), to bufor wewnętrzny jest w trybie buforowania aż do uzbierania ileś tam danych (np. 4096 bajtów), i flush nastąpi dopiero wtedy. W tym przypadku tylko przykład z endl zrobi flush (więc będzie najwolniejszy, bo cała reszta flusha po prostu w ogóle nie zrobi).
I teraz dochodzimy do ostatniej ważnej zmiennej, czyli tego co zrobi kompilator. Generalnie w C/C++ jest zasada "as-if", która mówi że kompilator może sobie dowolnie przekształcić kod tak długo jak ostateczny efekt będzie taki sam. I znając kompilatory, stawiam że przerobią przypadki 2 oraz 3 na przypadek 4 (tj. dodadzą \n do stringa), co de facto zrówna je wszystkie w szybkości działania.
(btw jeśli lubicie takie rozkminy, to zachęcam do rzucenia okiem na artykuł, który kiedyś ze znajomymi napisałem o tym jak działa hello world od podszewki - https://gynvael.coldwind.pl/?id=754)