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





problemas visual (10)


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

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

    if (!cin.eof())
        line_count++;
};

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

(Respuesta de TLDR: incluya la declaración: cin.sync_with_stdio(false) o simplemente use fgets 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 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 tener en cuenta 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 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

Adenda de referencia minúscula 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



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

Agregar las siguientes declaraciones a la versión de C ++ justo antes del bucle while lo pone en línea 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 la configuración de un búfer más grande lo redujo a 1 segundo.




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'; });
}

Vence todos mis intentos de Python por más de un factor 2.




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

syscalls 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

syscalls sudo dtruss -c ./a.py < in

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



getline , stream operadores, scanf , pueden ser convenientes 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 importa, realmente debería almacenar el archivo entero en la memoria (asumiendo 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 envolver 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.




Por defecto, cin está sincronizado con stdio, lo que hace que evite cualquier búfer de entrada. Si agrega esto a la parte superior de su main, debería ver un rendimiento mucho mejor:

std::ios_base::sync_with_stdio(false);

Normalmente, cuando un flujo de entrada se almacena en búfer, en lugar de leer un carácter a la vez, el flujo se leerá en partes más grandes. Esto reduce el número de llamadas al sistema, que suelen ser relativamente caras. Sin embargo, dado que el stdio y iostreams basados ​​en FILE* menudo tienen implementaciones separadas y, por lo tanto, buffers separados, esto podría llevar a un problema si ambos se usaron juntos. Por ejemplo:

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

Si cin leyó más entradas de las que realmente necesitaba, entonces el segundo valor entero no estaría disponible para la función scanf , 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 hacer que el cin lea cada personaje uno a la vez, según sea necesario, utilizando stdio funciones de stdio . Desafortunadamente, esto introduce una gran cantidad de gastos generales. Para pequeñas cantidades de información, esto no es un gran problema, pero cuando estás 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 deshabilitar esta función para obtener un mejor rendimiento si supiera lo que estaba haciendo, por lo que proporcionaron el método sync_with_stdio .




Un primer elemento de una respuesta: <iostream> es lento. Maldita sea lento Obtengo un gran aumento de rendimiento con scanf como se muestra a continuación, pero sigue siendo 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;
}



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 char de espacio (espacio, tabulador, nueva línea).

También, sí, CPython hace algo de almacenamiento en caché para evitar lecturas de disco duro.




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

Por cierto, no sabía acerca de esa cosa de sincronización, bonito. Pero aún deberías probar fgets .




Puede ser viejo C ++ y optimizaciones. En mi computadora obtuve casi la misma velocidad:

un bucle: 1.577 ms dos bucles: 1.507 ms

Ejecuto VS2015 en el procesador E5-1620 3.5Ghz con un ram de 16Gb







python c++ benchmarking iostream getline