¿Por qué leer líneas de stdin es mucho más lento en C ++ que Python?

1841

Quería comparar la lectura de líneas de entrada de cadena desde stdin usando Python y C ++ y me sorprendió ver que mi código C ++ se ejecutaba un orden de magnitud más lento que el código Python equivalente. Como mi C ++ está oxidado y todavía no soy un Pythonista experto, por favor dígame si estoy haciendo algo mal o si estoy malinterpretando algo.


(Respuesta TLDR: incluya la declaración: cin.sync_with_stdio(false)o simplemente use fgetsen su lugar.

Resultados de TLDR: desplácese hasta el final de mi pregunta y mire la tabla).


Código C ++:

#include <iostream>
#include <time.h>

using namespace std;

int main() {
    string input_line;
    long line_count = 0;
    time_t start = time(NULL);
    int sec;
    int lps;

    while (cin) {
        getline(cin, input_line);
        if (!cin.eof())
            line_count++;
    };

    sec = (int) time(NULL) - start;
    cerr << "Read " << line_count << " lines in " << sec << " seconds.";
    if (sec > 0) {
        lps = line_count / sec;
        cerr << " LPS: " << lps << endl;
    } else
        cerr << endl;
    return 0;
}

// Compiled with:
// g++ -O3 -o readline_test_cpp foo.cpp

Equivalente a Python:

#!/usr/bin/env python
import time
import sys

count = 0
start = time.time()

for line in  sys.stdin:
    count += 1

delta_sec = int(time.time() - start_time)
if delta_sec >= 0:
    lines_per_sec = int(round(count/delta_sec))
    print("Read {0} lines in {1} seconds. LPS: {2}".format(count, delta_sec,
       lines_per_sec))

Aquí están mis resultados:

$ cat test_lines | ./readline_test_cpp
Read 5570000 lines in 9 seconds. LPS: 618889

$cat test_lines | ./readline_test.py
Read 5570000 lines in 1 seconds. LPS: 5570000

Debo señalar que probé esto tanto en Mac OS X v10.6.8 (Snow Leopard) como en Linux 2.6.32 (Red Hat Linux 6.2). El primero es un MacBook Pro, y el segundo es un servidor muy robusto, no es que esto sea demasiado pertinente.

$ for i in {1..5}; do echo "Test run $i at `date`"; echo -n "CPP:"; cat test_lines | ./readline_test_cpp ; echo -n "Python:"; cat test_lines | ./readline_test.py ; done
Test run 1 at Mon Feb 20 21:29:28 EST 2012
CPP:   Read 5570001 lines in 9 seconds. LPS: 618889
Python:Read 5570000 lines in 1 seconds. LPS: 5570000
Test run 2 at Mon Feb 20 21:29:39 EST 2012
CPP:   Read 5570001 lines in 9 seconds. LPS: 618889
Python:Read 5570000 lines in 1 seconds. LPS: 5570000
Test run 3 at Mon Feb 20 21:29:50 EST 2012
CPP:   Read 5570001 lines in 9 seconds. LPS: 618889
Python:Read 5570000 lines in 1 seconds. LPS: 5570000
Test run 4 at Mon Feb 20 21:30:01 EST 2012
CPP:   Read 5570001 lines in 9 seconds. LPS: 618889
Python:Read 5570000 lines in 1 seconds. LPS: 5570000
Test run 5 at Mon Feb 20 21:30:11 EST 2012
CPP:   Read 5570001 lines in 10 seconds. LPS: 557000
Python:Read 5570000 lines in  1 seconds. LPS: 5570000

Pequeño anexo de referencia y resumen

Para completar, pensé que actualizaría la velocidad de lectura para el mismo archivo en el mismo cuadro con el código C ++ original (sincronizado). Nuevamente, esto es para un archivo de línea de 100M en un disco rápido. Aquí está la comparación, con varias soluciones / enfoques:

Implementation      Lines per second
python (default)           3,571,428
cin (default/naive)          819,672
cin (no sync)             12,500,000
fgets                     14,285,714
wc (not fair comparison)  54,644,808
JJC
fuente
14
¿Ejecutó sus pruebas varias veces? Tal vez hay un problema de caché de disco.
Vaughn Cato
99
@JJC: Veo dos posibilidades (suponiendo que haya eliminado el problema de almacenamiento en caché sugerido por David): 1) el <iostream>rendimiento apesta. No es la primera vez que sucede. 2) Python es lo suficientemente inteligente como para no copiar los datos en el bucle for porque no lo usa. Podrías volver a probar tratando de usar scanfy a char[]. Alternativamente, podría intentar reescribir el bucle para que se haga algo con la cadena (por ejemplo, mantenga la quinta letra y concatene en un resultado).
JN
15
El problema es la sincronización con stdio: mira mi respuesta.
Vaughn Cato
19
Dado que nadie parece haber mencionado por qué obtienes una línea adicional con C ++: ¡No pruebes cin.eof()! Ponga la getlinellamada en la declaración 'if`.
Xeo
21
wc -les rápido porque lee la transmisión más de una línea a la vez (puede ser una fread(stdin)/memchr('\n')combinación). Los resultados de Python están en el mismo orden de magnitud, por ejemplo,wc-l.py
jfs

Respuestas:

1644

De forma predeterminada, cinse sincroniza con stdio, lo que hace que evite cualquier almacenamiento en búfer de entrada. Si agrega esto a la parte superior de su página principal, debería ver un rendimiento mucho mejor:

std::ios_base::sync_with_stdio(false);

Normalmente, cuando una secuencia de entrada está almacenada, en lugar de leer un carácter a la vez, la secuencia se leerá en fragmentos más grandes. Esto reduce la cantidad de llamadas al sistema, que generalmente son relativamente caras. Sin embargo, dado que las implementaciones FILE*basadas stdioya iostreamsmenudo tienen implementaciones separadas y, por lo tanto, memorias intermedias separadas, esto podría generar un problema si ambas se usaran juntas. Por ejemplo:

int myvalue1;
cin >> myvalue1;
int myvalue2;
scanf("%d",&myvalue2);

Si se leyera más entrada cinde la que realmente necesitaba, entonces el segundo valor entero no estaría disponible para la scanffunción, que tiene su propio búfer independiente. Esto llevaría a resultados inesperados.

Para evitar esto, de forma predeterminada, las secuencias se sincronizan con stdio. Una forma común de lograr esto es haber cinleído cada carácter uno a la vez según sea necesario usando stdiofunciones. Desafortunadamente, esto introduce muchos gastos generales. Para pequeñas cantidades de entrada, este no es un gran problema, pero cuando está leyendo millones de líneas, la penalización de rendimiento es significativa.

Afortunadamente, los diseñadores de la biblioteca decidieron que también debería poder desactivar esta función para obtener un mejor rendimiento si supiera lo que estaba haciendo, por lo que proporcionaron el sync_with_stdiométodo.

Vaughn Cato
fuente
142
Esto debería estar en la parte superior. Es casi seguro que es correcto. La respuesta no puede estar en reemplazar la lectura con una fscanfllamada, porque eso simplemente no hace tanto trabajo como Python. Python debe asignar memoria para la cadena, posiblemente varias veces, ya que la asignación existente se considera inadecuada, exactamente como el enfoque de C ++ std::string. Es casi seguro que esta tarea está vinculada a E / S y hay demasiados FUD sobre el costo de crear std::stringobjetos en C ++ o usarlos <iostream>en sí mismos.
Karl Knechtel
51
Sí, agregar esta línea inmediatamente sobre mi bucle while original aceleró el código para superar incluso a Python. Estoy a punto de publicar los resultados como la edición final. ¡Gracias de nuevo!
JJC
66
Sí, esto también se aplica a cout, cerr y clog también.
Vaughn Cato
2
Para hacer cout, cin, cerr y clog más rápido, hágalo de esta manera std :: ios_base :: sync_with_stdio (false);
01100110
56
Tenga en cuenta que sync_with_stdio()es una función miembro estática, y una llamada a esta función en cualquier objeto de flujo (por ejemplo cin) activa o desactiva la sincronización para todos los objetos estándar de iostream.
John Zwinck
171

Solo por curiosidad, he echado un vistazo a lo que sucede debajo del capó, y he usado dtruss / strace en cada prueba.

C ++

./a.out < in
Saw 6512403 lines in 8 seconds.  Crunch speed: 814050

llamadas al sistema sudo dtruss -c ./a.out < in

CALL                                        COUNT
__mac_syscall                                   1
<snip>
open                                            6
pread                                           8
mprotect                                       17
mmap                                           22
stat64                                         30
read_nocancel                               25958

Pitón

./a.py < in
Read 6512402 lines in 1 seconds. LPS: 6512402

llamadas al sistema sudo dtruss -c ./a.py < in

CALL                                        COUNT
__mac_syscall                                   1
<snip>
open                                            5
pread                                           8
mprotect                                       17
mmap                                           21
stat64                                         29
2mia
fuente
159

Estoy unos años atrás aquí, pero:

En 'Editar 4/5/6' de la publicación original, está utilizando la construcción:

$ /usr/bin/time cat big_file | program_to_benchmark

Esto está mal en un par de formas diferentes:

  1. En realidad, estás cronometrando la ejecución cat, no tu punto de referencia. El uso de la CPU 'usuario' y 'sys' que se muestran timeson aquellos de cat, no su programa de referencia. Peor aún, el tiempo "real" tampoco es necesariamente preciso. Dependiendo de la implementación de caty de las tuberías en su sistema operativo local, es posible que catescriba un búfer gigante final y salga mucho antes de que el proceso del lector termine su trabajo.

  2. El uso de cates innecesario y de hecho contraproducente; Estás agregando partes móviles. Si estuvieras en un sistema suficientemente antiguo (es decir, con una sola CPU y, en ciertas generaciones de computadoras, E / S más rápido que la CPU), el solo hecho de que se catestuviera ejecutando podría colorear sustancialmente los resultados. También está sujeto a lo que catpueda hacer el almacenamiento en búfer de entrada y salida y otros procesos . (Esto probablemente le otorgaría un premio de 'Uso inútil del gato' si fuera Randal Schwartz.

Una mejor construcción sería:

$ /usr/bin/time program_to_benchmark < big_file

En esta declaración, es el shell el que abre big_file, pasándolo a su programa (bueno, en realidad al timeque luego ejecuta su programa como un subproceso) como un descriptor de archivo ya abierto. El 100% de la lectura del archivo es estrictamente responsabilidad del programa que está intentando comparar. Esto le brinda una lectura real de su rendimiento sin complicaciones espurias.

Mencionaré dos 'arreglos' posibles, pero realmente incorrectos, que también podrían considerarse (pero los 'numero' de manera diferente ya que no son cosas que estaban mal en la publicación original):

R. Puede "arreglar" esto cronometrando solo su programa:

$ cat big_file | /usr/bin/time program_to_benchmark

B. o cronometrando toda la tubería:

$ /usr/bin/time sh -c 'cat big_file | program_to_benchmark'

Estos son incorrectos por las mismas razones que el n. ° 2: todavía se usan catinnecesariamente. Los menciono por algunas razones:

  • son más "naturales" para las personas que no se sienten completamente cómodas con las funciones de redirección de E / S del shell POSIX

  • puede haber casos en los que cat se necesita (por ejemplo: el archivo para ser leído requiere algún tipo de privilegio de acceso, y usted no desea conceder ese privilegio al programa a partir de referencias: sudo cat /dev/sda | /usr/bin/time my_compression_test --no-output)

  • en la práctica , en máquinas modernas, lo agregado caten la tubería probablemente no tenga consecuencias reales.

Pero digo lo último con algunas dudas. Si examinamos el último resultado en 'Editar 5' -

$ /usr/bin/time cat temp_big_file | wc -l
0.01user 1.34system 0:01.83elapsed 74%CPU ...

- esto afirma que catconsumió el 74% de la CPU durante la prueba; y de hecho 1.34 / 1.83 es ​​aproximadamente el 74%. Quizás una serie de:

$ /usr/bin/time wc -l < temp_big_file

¡Hubiera tomado solo los .49 segundos restantes! Probablemente no: cataquí tuvo que pagar las read()llamadas al sistema (o equivalentes) que transfirieron el archivo desde el 'disco' (en realidad la memoria caché del búfer), así como las escrituras de tubería para entregarlas wc. La prueba correcta aún habría tenido que hacer esas read()llamadas; solo se habrían guardado las llamadas de escritura a canalización y lectura de canalización, y deberían ser bastante baratas.

Aún así, predigo que sería capaz de medir la diferencia entre cat file | wc -ly wc -l < filey encontrar una diferencia notable (porcentaje de 2 dígitos). Cada una de las pruebas más lentas habrá pagado una penalización similar en tiempo absoluto; que sin embargo equivaldría a una fracción menor de su tiempo total más grande.

De hecho, hice algunas pruebas rápidas con un archivo de basura de 1.5 gigabytes, en un sistema Linux 3.13 (Ubuntu 14.04), obteniendo estos resultados (estos son en realidad 'el mejor de 3' resultados; después de preparar el caché, por supuesto):

$ time wc -l < /tmp/junk
real 0.280s user 0.156s sys 0.124s (total cpu 0.280s)
$ time cat /tmp/junk | wc -l
real 0.407s user 0.157s sys 0.618s (total cpu 0.775s)
$ time sh -c 'cat /tmp/junk | wc -l'
real 0.411s user 0.118s sys 0.660s (total cpu 0.778s)

Observe que los dos resultados de la canalización afirman haber tomado más tiempo de CPU (usuario + sys) que el tiempo real del reloj de pared. Esto se debe a que estoy usando el comando 'time' incorporado del shell (bash), que conoce la canalización; y estoy en una máquina de múltiples núcleos donde los procesos separados en una tubería pueden usar núcleos separados, acumulando tiempo de CPU más rápido que en tiempo real. Al usar /usr/bin/time, veo un tiempo de CPU menor que el tiempo real, lo que muestra que solo puede cronometrar el elemento de tubería único que se le pasó en su línea de comando. Además, la salida del shell da milisegundos, mientras que /usr/bin/timesolo da centésimas de segundo.

Entonces, en el nivel de eficiencia de wc -l, cathace una gran diferencia: 409/283 = 1.453 o 45.3% más en tiempo real, y 775/280 = 2.768, ¡o se usa un sorprendente 177% más de CPU! En mi cuadro de prueba aleatorio, estaba allí en el momento.

Debo agregar que hay al menos otra diferencia significativa entre estos estilos de prueba, y no puedo decir si es un beneficio o una falla; tienes que decidir esto tú mismo:

Cuando ejecuta cat big_file | /usr/bin/time my_program, su programa recibe información de una tubería, exactamente al ritmo enviado por cat, y en fragmentos no más grandes que los escritos por cat.

Cuando ejecuta /usr/bin/time my_program < big_file, su programa recibe un descriptor de archivo abierto para el archivo real. Su programa, o en muchos casos las bibliotecas de E / S del idioma en el que se escribió, puede tomar diferentes medidas cuando se le presenta un descriptor de archivo que hace referencia a un archivo normal. Puede usarse mmap(2)para asignar el archivo de entrada a su espacio de direcciones, en lugar de usar read(2)llamadas explícitas del sistema. Estas diferencias podrían tener un efecto mucho mayor en sus resultados de referencia que el pequeño costo de ejecutar el catbinario.

Por supuesto, es un resultado de referencia interesante si el mismo programa se desempeña de manera significativamente diferente entre los dos casos. Muestra que, de hecho, el programa o sus bibliotecas de E / S están haciendo algo interesante, como usar mmap(). Entonces, en la práctica, podría ser bueno ejecutar los puntos de referencia en ambos sentidos; quizás descontando el catresultado por algún pequeño factor para "perdonar" el costo de ejecutarse cat.

Bela Lubkin
fuente
26
¡Guau, eso fue bastante perspicaz! Si bien he sido consciente de que cat es innecesario para alimentar la entrada al stdin de los programas y que se prefiere la redirección <shell, generalmente me he adherido a cat debido al flujo de datos de izquierda a derecha que el método anterior conserva visualmente cuando razoné sobre las tuberías. Las diferencias de rendimiento en tales casos me han parecido insignificantes. Pero, aprecio que nos hayas educado, Bela.
JJC
11
La redirección se analiza desde la línea de comandos del shell en una etapa temprana, lo que le permite hacer uno de estos, si le da una apariencia más agradable de flujo de izquierda a derecha: $ < big_file time my_program $ time < big_file my_program Esto debería funcionar en cualquier shell POSIX (es decir, no `csh `y no estoy seguro acerca de exótica como` rc`:)
Bela Lubkin
66
Una vez más, aparte de la diferencia de rendimiento incremental quizás no interesante debido a que el binario `cat` se ejecuta al mismo tiempo, está renunciando a la posibilidad de que el programa bajo prueba pueda mapear () el archivo de entrada. Esto podría marcar una profunda diferencia en los resultados. Esto es cierto incluso si escribió los puntos de referencia usted mismo, en los distintos idiomas, utilizando solo el idioma de "líneas de entrada de un archivo". Depende del funcionamiento detallado de sus diversas bibliotecas de E / S.
Bela Lubkin
2
Nota al margen: la construcción de Bash timeestá midiendo toda la tubería en lugar del primer programa. time seq 2 | while read; do sleep 1; doneimprime 2 segundos, /usr/bin/time seq 2 | while read; do sleep 1; doneimprime 0 segundos.
folkol
1
@folkol: sí, << Tenga en cuenta que los resultados de dos canales [muestran] más CPU [que] en tiempo real [usando] el comando 'tiempo' incorporado (Bash); ... / usr / bin / time ... solo puede cronometrar el elemento de tubería único que se le pasó en su línea de comando. >> '
Bela Lubkin
90

Reproduje el resultado original en mi computadora usando g ++ en una Mac.

Agregar las siguientes declaraciones a la versión de C ++ justo antes de que el whilebucle lo alinee con la versión de Python :

std::ios_base::sync_with_stdio(false);
char buffer[1048576];
std::cin.rdbuf()->pubsetbuf(buffer, sizeof(buffer));

sync_with_stdio mejoró la velocidad a 2 segundos, y establecer un búfer más grande lo redujo a 1 segundo.

karunski
fuente
55
Es posible que desee probar diferentes tamaños de búfer para obtener información más útil. Sospecho que verá rendimientos decrecientes rápidamente.
Karl Knechtel
8
Fui demasiado apresurado en mi respuesta; establecer el tamaño del búfer en algo diferente al predeterminado no produjo una diferencia apreciable.
karunski
109
También evitaría configurar un búfer de 1 MB en la pila. Puede conducir a un desbordamiento de pila (¡aunque supongo que es un buen lugar para debatir sobre esto!)
Matthieu M.
11
Matthieu, Mac usa una pila de proceso de 8MB por defecto. Linux usa 4MB por hilo predeterminado, IIRC. 1 MB no es un gran problema para un programa que transforma la entrada con una profundidad de pila relativamente baja. Sin embargo, lo más importante es que std :: cin destruirá la pila si el búfer se sale del alcance.
SEK
22
@SEK El tamaño de pila predeterminado de Windows es de 1 MB.
Étienne
39

getline, operadores de transmisión scanf, puede ser conveniente si no le importa el tiempo de carga de archivos o si está cargando archivos de texto pequeños. Pero, si el rendimiento es algo que le interesa, realmente debería almacenar todo el archivo en la memoria (suponiendo que se ajuste).

Aquí hay un ejemplo:

//open file in binary mode
std::fstream file( filename, std::ios::in|::std::ios::binary );
if( !file ) return NULL;

//read the size...
file.seekg(0, std::ios::end);
size_t length = (size_t)file.tellg();
file.seekg(0, std::ios::beg);

//read into memory buffer, then close it.
char *filebuf = new char[length+1];
file.read(filebuf, length);
filebuf[length] = '\0'; //make it null-terminated
file.close();

Si lo desea, puede ajustar una secuencia alrededor de ese búfer para un acceso más conveniente como este:

std::istrstream header(&filebuf[0], length);

Además, si tiene el control del archivo, considere usar un formato de datos binarios planos en lugar de texto. Es más confiable leer y escribir porque no tiene que lidiar con todas las ambigüedades del espacio en blanco. También es más pequeño y mucho más rápido de analizar.

Stu
fuente
20

El siguiente código fue más rápido para mí que el otro código publicado aquí hasta ahora: (Visual Studio 2013, 64 bits, archivo de 500 MB con longitud de línea uniforme en [0, 1000)).

const int buffer_size = 500 * 1024;  // Too large/small buffer is not good.
std::vector<char> buffer(buffer_size);
int size;
while ((size = fread(buffer.data(), sizeof(char), buffer_size, stdin)) > 0) {
    line_count += count_if(buffer.begin(), buffer.begin() + size, [](char ch) { return ch == '\n'; });
}

Supera todos mis intentos de Python en más de un factor 2.

Petter
fuente
Puede ser aún más rápido que eso con un pequeño programa C personalizado pero completamente sencillo que iterativamente realiza readsyscalls sin búfer en un búfer de longitud estático BUFSIZEo mediante las mmapsyscalls correspondientes equivalentes , y luego se desplaza a través de ese búfer contando nuevas líneas a la for (char *cp = buf; *cp; cp++) count += *cp == "\n". Sin BUFSIZEembargo, tendrá que ajustar su sistema, que stdio ya habrá hecho por usted. Pero ese forbucle debería compilarse en instrucciones de lenguaje de ensamblador increíblemente rápidas para el hardware de su caja.
tchrist
3
count_if y una lambda también se compilan en "ensamblador increíblemente rápido".
Petter
17

Por cierto, la razón por la cual el recuento de líneas para la versión C ++ es uno mayor que el recuento para la versión de Python es que el indicador eof solo se establece cuando se intenta leer más allá de eof. Entonces el bucle correcto sería:

while (cin) {
    getline(cin, input_line);

    if (!cin.eof())
        line_count++;
};
Gregg
fuente
70
El ciclo realmente correcto sería: while (getline(cin, input_line)) line_count++;
Jonathan Wakely
2
@ JonathanWakely Sé que llego bastante tarde, pero uso ++line_count;y no line_count++;.
Val dice reinstalar a Mónica
77
@val si eso hace alguna diferencia, su compilador tiene un error. La variable es a long, y el compilador es bastante capaz de decir que no se usa el resultado del incremento. Si no genera un código idéntico para el postincremento y el preincremento, está roto.
Jonathan Wakely
2
De hecho, cualquier compilador decente podrá detectar un mal uso posterior al incremento y sustituirlo por un preincremento, pero no se requiere que los compiladores lo hagan . Entonces no, no está roto incluso si el compilador no realiza la sustitución. Además, escribir en ++line_count;lugar de line_count++;no dolería :)
Fareanor
1
@valsaysReinstateMonica En este ejemplo específico, ¿por qué se preferiría cualquiera? El resultado no se usa aquí de ninguna manera, por lo que se leería después del while, ¿verdad? ¿Importaría si hubiera algún tipo de error y quisieras asegurarte de que line_countfuera correcto? Solo estoy adivinando pero no entiendo por qué importaría.
TankorSmash
14

En su segundo ejemplo (con scanf ()), la razón por la que esto es aún más lento podría deberse a que scanf ("% s") analiza la cadena y busca cualquier carácter de espacio (espacio, tabulación, nueva línea).

Además, sí, CPython almacena en caché para evitar lecturas de disco duro.

davinchi
fuente
12

Un primer elemento de una respuesta: <iostream>es lento. Maldita sea lento. Obtuve un gran aumento de rendimiento scanfcomo en el siguiente, pero aún es dos veces más lento que Python.

#include <iostream>
#include <time.h>
#include <cstdio>

using namespace std;

int main() {
    char buffer[10000];
    long line_count = 0;
    time_t start = time(NULL);
    int sec;
    int lps;

    int read = 1;
    while(read > 0) {
        read = scanf("%s", buffer);
        line_count++;
    };
    sec = (int) time(NULL) - start;
    line_count--;
    cerr << "Saw " << line_count << " lines in " << sec << " seconds." ;
    if (sec > 0) {
        lps = line_count / sec;
        cerr << "  Crunch speed: " << lps << endl;
    } 
    else
        cerr << endl;
    return 0;
}
JN
fuente
No vi esta publicación hasta que hice mi tercera edición, pero gracias de nuevo por su sugerencia. Curiosamente, no hay 2x hit para mí frente a python ahora con la línea scanf en edit3 arriba. Estoy usando 2.7, por cierto.
JJC
10
Después de arreglar la versión c ++, esta versión stdio es sustancialmente más lenta que la versión c ++ iostreams en mi computadora. (3 segundos vs 1 segundo)
karunski
10

Bueno, veo que en tu segunda solución cambiaste cina scanf, que fue la primera sugerencia que te haría (cin es sloooooooooooow). Ahora, si cambia de scanfa fgets, vería otro aumento en el rendimiento: fgetses la función C ++ más rápida para la entrada de cadenas.

Por cierto, no sabía sobre esa cosa de sincronización, agradable. Pero igual deberías intentarlo fgets.

José Ernesto Lara Rodríguez
fuente
2
Excepto fgetsque será incorrecto (en términos de recuento de líneas y en términos de división de líneas en bucles si realmente necesita usarlas) para líneas suficientemente grandes, sin verificaciones adicionales para líneas incompletas (e intentar compensarlo implica asignar buffers innecesariamente grandes , donde std::getlinemaneja la reasignación para que coincida con la entrada real sin problemas). Lo rápido y lo incorrecto es fácil, pero casi siempre vale la pena usarlo "un poco más lento, pero correcto", lo que te apaga sync_with_stdio.
ShadowRanger