c++ performance cout string-literals

c++ - ¿Por qué se prefiere ''/ n'' sobre “/ n” para las secuencias de salida?



performance cout (4)

En this respuesta podemos leer que:

Supongo que hay poca diferencia entre usar ''/n'' o usar "/n" , pero este último es una matriz de (dos) caracteres, que tiene que imprimirse carácter por carácter, para lo cual debe configurarse un bucle, que es más complejo que generar un solo carácter .

énfasis mío

Eso tiene sentido para mí. Creo que la salida de un const char* requiere un bucle que probará null-terminator, que debe introducir más operaciones que, digamos, un simple putchar (no implica que std::cout con delegados de char llame a eso, es solo una simplificación para presentar un ejemplo).

Eso me convenció de usar

std::cout << ''/n''; std::cout << '' '';

más bien que

std::cout << "/n"; std::cout << " ";

Vale la pena mencionar aquí que soy consciente de que la diferencia de rendimiento es bastante insignificante. Sin embargo, algunos pueden argumentar que el enfoque anterior tiene la intención de pasar un solo carácter, en lugar de un literal de cadena que resultó ser un char largo ( dos caracteres largos si cuenta el ''/0'' ).

Últimamente he hecho una pequeña revisión de código para alguien que estaba usando este último enfoque. Hice un pequeño comentario sobre el caso y seguí adelante. El desarrollador luego me agradeció y dijo que ni siquiera había pensado en esa diferencia (centrándose principalmente en la intención). No tuvo ningún impacto (como era de esperar), pero el cambio fue adoptado.

Entonces comencé a preguntarme cómo es exactamente ese cambio significativo, así que corrí a Godbolt. Para mi sorpresa, mostró los siguientes resultados cuando se probó en GCC (troncal) con los -std=c++17 -O3 . El ensamblado generado para el siguiente código:

#include <iostream> void str() { std::cout << "/n"; } void chr() { std::cout << ''/n''; } int main() { str(); chr(); }

me sorprendió, porque parece que chr() realidad está generando exactamente el doble de instrucciones que str() :

.LC0: .string "/n" str(): mov edx, 1 mov esi, OFFSET FLAT:.LC0 mov edi, OFFSET FLAT:_ZSt4cout jmp std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long) chr(): sub rsp, 24 mov edx, 1 mov edi, OFFSET FLAT:_ZSt4cout lea rsi, [rsp+15] mov BYTE PTR [rsp+15], 10 call std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long) add rsp, 24 ret

¿Porqué es eso? ¿Por qué ambos finalmente llaman a la misma función std::basic_ostream con el argumento const char* ? ¿Significa que el enfoque literal de caracteres no solo no es mejor , sino que en realidad es peor que el literal de cadena?


En lugar de ''/ n'', no se recomienda usar std :: endl para mayor legibilidad.


Ninguna de las otras respuestas explica realmente por qué el compilador genera el código que hace en su enlace Godbolt, por lo que pensé en incorporarlo.

Si observa el código generado, puede ver que:

std::cout << ''/n'';

Compila a, en efecto:

char c = ''/n''; std::cout.operator<< (&c, 1);

y para que esto funcione, el compilador tiene que generar un marco de pila para la función chr() , que es de donde provienen muchas de las instrucciones adicionales.

Por otro lado, al compilar esto:

std::cout << "/n";

el compilador puede optimizar str() para simplemente '' operator<< (const char *) cola'' operator<< (const char *) , lo que significa que no se necesita un marco de pila.

Por lo tanto, sus resultados están algo sesgados por el hecho de que coloca las llamadas al operator<< en funciones separadas. Es más revelador hacer estas llamadas en línea, consulte: https://godbolt.org/z/OO-8dS

Ahora puede ver que, aunque la salida ''/n'' sigue siendo un poco más costosa (porque no hay una sobrecarga específica para ofstream::operator<< (char) ), la diferencia es menos marcada que en su ejemplo.


Sí, para esta implementación particular, por ejemplo, la versión de char es un poco más lenta que la versión de cadena.

Ambas versiones llaman a una función de estilo de write(buffer, bufferSize) . Para la versión de cadena, bufferSize se conoce en tiempo de compilación (1 byte), por lo que no es necesario encontrar el tiempo de ejecución del terminador cero. Para la versión char , el compilador crea un pequeño búfer de 1 byte en la pila, coloca el carácter en él y pasa este búfer para escribir. Entonces, la versión char es un poco más lenta.


Sin embargo, tenga en cuenta que lo que ve en el ensamblado es solo la creación de la pila de llamadas, no la ejecución de la función real.

std::cout << ''/n''; sigue siendo mucho más rápido que std::cout << "/n";

Creé este pequeño programa para medir el rendimiento y es aproximadamente 20 veces un poco más rápido en mi máquina con g ++ -O3. ¡Inténtalo tú mismo!

Editar: Lo siento, noté error tipográfico en mi programa y no es mucho más rápido. Apenas puede medir alguna diferencia más. A veces uno es más rápido. Otras veces el otro.

#include <chrono> #include <iostream> class timer { private: decltype(std::chrono::high_resolution_clock::now()) begin, end; public: void start() { begin = std::chrono::high_resolution_clock::now(); } void stop() { end = std::chrono::high_resolution_clock::now(); } template<typename T> auto duration() const { return std::chrono::duration_cast<T>(end - begin).count(); } auto nanoseconds() const { return duration<std::chrono::nanoseconds>(); } void printNS() const { std::cout << "Nanoseconds: " << nanoseconds() << std::endl; } }; int main(int argc, char** argv) { timer t1; t1.start(); for (int i{0}; 10000 > i; ++i) { std::cout << ''/n''; } t1.stop(); timer t2; t2.start(); for (int i{0}; 10000 > i; ++i) { std::cout << "/n"; } t2.stop(); t1.printNS(); t2.printNS(); }

Editar: como sugirió geza, probé 100000000 iteraciones para ambos y lo envié a / dev / null y lo ejecuté cuatro veces. ''/ n'' fue una vez más lento y 3 veces más rápido pero nunca mucho, pero podría ser diferente en otras máquinas:

Nanoseconds: 8668263707 Nanoseconds: 7236055911 Nanoseconds: 10704225268 Nanoseconds: 10735594417 Nanoseconds: 10670389416 Nanoseconds: 10658991348 Nanoseconds: 7199981327 Nanoseconds: 6753044774

Supongo que en general no me importaría demasiado.