Často se setkávám s tím, že se v C++ k ukončení řádku ve výstupu používá std::endl
místo klasického '\n'
, které známe z Céčka. Důvod bývá různý, ale většinou je to něco ve stylu: "je to C++kovější řešení než '\n'
" či "je to přenositelnější". V tomto příspěvku se dozvíte, že ani jeden důvod není pravdivý a že použití '\n'
místo std::endl
vám může značně urychlit běh programu.
Motivační příklad na začátek
Dnešní příspěvek začnu poněkud netradičně, a to motivačním příkladem. Mějme následující dva kusy kódu:
// (1) for (size_t i = 0; i < 10000000; ++i) { std::cout << "Here comes a string..." << std::endl; }
// (2) for (size_t i = 0; i < 10000000; ++i) { std::cout << "Here comes a string..." << '\n'; }
Když oba dva kódy přeložíme a spustíme, dostaneme identický výstup. Co je ale zajímavé, tak je čas běhu jednotlivých řešení, uvedený níže.
// (1)
real 1m3.699s
user 0m4.030s
sys 0m34.203s
// (2)
real 0m2.066s
user 0m0.810s
sys 0m0.687s
Celkem síla, že? Všechny testy proběhly na systému s 64b Arch Linux, kernel 3.9.3, Intel Core 2 Duo T8300 2.4 GHz, 4 GB RAM, 320 GB SATA WDC WD3200BEVT-2 (5400rpm). Použitá verze gcc je 4.8.0 a libstdc++5 je ve verzi 3.3.6-4. Použité parametry při překladu byly -std=c++11 -pedantic -O2
. Testy proběhly pětkrát za sebou a jejich čas byl zprůměrován. Výstup byl přesměrován do souboru.
Vysvětlení
Důvod, proč je první řešení tak pomalé, je v tom, že std::endl
plní (oproti '\n'
) dvě funkce: první je odřádkování a druhá je vyprázdnění vyrovnávací paměti [27.7.3.8 v ISO C++11]. Standardní proud std::cout
je implicitně tzv. bufferovaný, což znamená, že je využita vyrovnávací paměť. K zápisu na výstup (či disk) pak dojde až ve chvíli, kdy je vyrovnávací paměť plná nebo explicitně vyprázdněná (v C++ k tomu lze využít standardní manipulátor std::flush
, ale implicitně to udělá i std::endl
). Co je třeba si uvědomit, tak je, že při každém vyprázdnění vyrovnávací paměti je třeba proces uspat, přejít z uživatelského režimu do režimu jádra, provést zápis na disk (nejpomalejší část z celé operace) a vrátit se zpět do uživatelského režimu. Pokud použijeme '\n'
, tak se tato činnost provede až tehdy, když se vyrovnávací paměť naplní. Tento rozdíl je názorně vidět u položky sys
výše, která značí, kolik času bylo stráveno v jádru.
Jak je to tedy s tím rozdílem mezi std::endl
a '\n'
?
Jediný rozdíl je v tom, že std::endl
oproti '\n'
vždy provede vyprázdnění vyrovnávací paměti. Použití tohoto manipulátoru nemá žádný vliv na přenositelnost programu, ani se nejedná o C++kovější řešení, než '\n'
.
Co tedy používat?
Pokud používáte ladicí výpisy či chcete mít výstup okamžitě k dispozici (např. do logu), tak std::endl
, jinak '\n'
. V případě ladicích výpisů ale zvažte spíše použití std::cerr
, který je implicitně nebufferovaný (tj. bez vyrovnávací paměti) a není tak třeba používat std::endl
ani std::flush
. Můžete pak vždy používat '\n'
, což může být rychlejší, viz náš motivační příklad; obzvlášť, pokud výpis provádíte frekventovaně a ve velkém množství (u prográmků ve stylu "Ahoj světe" se to neprojeví). Co je typicky naprostý nesmysl, který však běžně vídávám, tak je
std::cerr << "error: xxx" << std::endl; // Takto ne...
Ono volání std::endl
je zbytečné (std:cerr
není bufferovaný). Ideální je pak volat
std::cerr << "error: xxx\n";
optimalizace
Ahoj Petře,
s jakou úrovní optimalizace jsi to zkoušel?
Kuba
Re: optimalizace
Vidíš, to jsem tam úplně zapomněl zmínit. Všechno jsem testoval s -O2 (doplnil jsem to tam).
CRLF ?
Ahoj Petře,
myslel jsem, že
std::endl
se dává kvůli různému chápání konce řádku, např.CR LF
.Jak pozná, že chci na konci řádku dva znaky místo jednoho?
Ota
Re: CRLF ?
Díky za dotaz. Jak jsem již nastínil v článku, tak
je ekvivalentní s
Nyní záleží na tom, co je
stream
zač. Pokud se jedná textový výstup (cout
, soubor otevřený v textovém režimu apod.), tak systém automaticky převede'\n'
na ukončovač řádku na dané platformě. Pokud tedy tento kód spustíš na MS Windows, tak se'\n'
zapíše jako"\r\n"
. Pokud to spustíš na Linuxu, tak bude výsledkem'\n'
. Pokud je to stream otevřený v binárním režimu, tak se zapíše pouze'\n'
(bez konverze).Suma sumárum, použití
std::endl
nezvyšuje přenositelnost, protože použitím'\n'
dosáhneš stejného efektu, čili pro streamy otevřené v textovém režimu se automaticky zapíše konec řádku dle zvyklostí na dané platformě. Příklad: pokud spustíš následující kód na MS Windowstak bude výstupem
"\r\n\r\n"
. Pokud jej spustíš na Linuxu, tak bude výstupem"\n\n"
. To, že použitístd::endl
zvyšuje přenositelnost, je mýtus. Viz sekce 27.7.3.8 v ISO C++11 normě, kde je detailně specifikované, costd::endl
dělá.