Estoy tratando de convertir algo de código de Python a C ++ en un esfuerzo por ganar un poco de velocidad y agudizar mis oxidadas habilidades en C ++. Ayer me sorprendió cuando una implementación ingenua de leer líneas de stdin era mucho más rápida en Python que en C ++ (ver esto ). Hoy, finalmente descubrí cómo dividir una cadena en C ++ con delimitadores de fusión (semántica similar a split () de Python), ¡y ahora estoy experimentando un deja vu! Mi código C ++ tarda mucho más en hacer el trabajo (aunque no un orden de magnitud más, como fue el caso de la lección de ayer).
Código Python:
#!/usr/bin/env python
from __future__ import print_function
import time
import sys
count = 0
start_time = time.time()
dummy = None
for line in sys.stdin:
dummy = line.split()
count += 1
delta_sec = int(time.time() - start_time)
print("Python: Saw {0} lines in {1} seconds. ".format(count, delta_sec), end='')
if delta_sec > 0:
lps = int(count/delta_sec)
print(" Crunch Speed: {0}".format(lps))
else:
print('')
Código C ++:
#include <iostream>
#include <string>
#include <sstream>
#include <time.h>
#include <vector>
using namespace std;
void split1(vector<string> &tokens, const string &str,
const string &delimiters = " ") {
// Skip delimiters at beginning
string::size_type lastPos = str.find_first_not_of(delimiters, 0);
// Find first non-delimiter
string::size_type pos = str.find_first_of(delimiters, lastPos);
while (string::npos != pos || string::npos != lastPos) {
// Found a token, add it to the vector
tokens.push_back(str.substr(lastPos, pos - lastPos));
// Skip delimiters
lastPos = str.find_first_not_of(delimiters, pos);
// Find next non-delimiter
pos = str.find_first_of(delimiters, lastPos);
}
}
void split2(vector<string> &tokens, const string &str, char delim=' ') {
stringstream ss(str); //convert string to stream
string item;
while(getline(ss, item, delim)) {
tokens.push_back(item); //add token to vector
}
}
int main() {
string input_line;
vector<string> spline;
long count = 0;
int sec, lps;
time_t start = time(NULL);
cin.sync_with_stdio(false); //disable synchronous IO
while(cin) {
getline(cin, input_line);
spline.clear(); //empty the vector for the next line to parse
//I'm trying one of the two implementations, per compilation, obviously:
// split1(spline, input_line);
split2(spline, input_line);
count++;
};
count--; //subtract for final over-read
sec = (int) time(NULL) - start;
cerr << "C++ : Saw " << count << " lines in " << sec << " seconds." ;
if (sec > 0) {
lps = count / sec;
cerr << " Crunch speed: " << lps << endl;
} else
cerr << endl;
return 0;
//compiled with: g++ -Wall -O3 -o split1 split_1.cpp
Tenga en cuenta que probé dos implementaciones divididas diferentes. Uno (split1) usa métodos de cadena para buscar tokens y es capaz de fusionar múltiples tokens, así como manejar numerosos tokens (viene de aquí ). El segundo (split2) usa getline para leer la cadena como una secuencia, no fusiona delimitadores y solo admite un único carácter delimitador (ese fue publicado por varios usuarios de StackOverflow en las respuestas a las preguntas de división de cadenas).
Ejecuté esto varias veces en varios órdenes. Mi máquina de prueba es una Macbook Pro (2011, 8GB, Quad Core), no es que importe mucho. Estoy probando con un archivo de texto de línea de 20M con tres columnas separadas por espacios que se parecen a esto: "foo.bar 127.0.0.1 home.foo.bar"
Resultados:
$ /usr/bin/time cat test_lines_double | ./split.py
15.61 real 0.01 user 0.38 sys
Python: Saw 20000000 lines in 15 seconds. Crunch Speed: 1333333
$ /usr/bin/time cat test_lines_double | ./split1
23.50 real 0.01 user 0.46 sys
C++ : Saw 20000000 lines in 23 seconds. Crunch speed: 869565
$ /usr/bin/time cat test_lines_double | ./split2
44.69 real 0.02 user 0.62 sys
C++ : Saw 20000000 lines in 45 seconds. Crunch speed: 444444
¿Qué estoy haciendo mal? ¿Hay una mejor manera de dividir cadenas en C ++ que no dependa de bibliotecas externas (es decir, sin impulso), que admita la combinación de secuencias de delimitadores (como la división de Python), sea segura para subprocesos (por lo tanto, no strtok) y cuyo rendimiento sea al menos a la par con Python?
Editar 1 / ¿Solución parcial ?:
Intenté hacer una comparación más justa haciendo que Python restableciera la lista ficticia y la agregara cada vez, como lo hace C ++. Esto todavía no es exactamente lo que está haciendo el código C ++, pero está un poco más cerca. Básicamente, el ciclo es ahora:
for line in sys.stdin:
dummy = []
dummy += line.split()
count += 1
El rendimiento de Python ahora es aproximadamente el mismo que el de la implementación split1 de C ++.
/usr/bin/time cat test_lines_double | ./split5.py
22.61 real 0.01 user 0.40 sys
Python: Saw 20000000 lines in 22 seconds. Crunch Speed: 909090
Todavía me sorprende que, incluso si Python está tan optimizado para el procesamiento de cadenas (como sugirió Matt Joiner), estas implementaciones de C ++ no serían más rápidas. Si alguien tiene ideas sobre cómo hacer esto de una manera más óptima usando C ++, por favor comparta su código. (Creo que mi próximo paso será intentar implementar esto en C puro, aunque no voy a sacrificar la productividad del programador para volver a implementar mi proyecto general en C, por lo que este será solo un experimento para la velocidad de división de cadenas).
Gracias a todos por vuestra ayuda.
Edición / solución final:
Consulte la respuesta aceptada de Alf. Dado que Python trata con cadenas estrictamente por referencia y las cadenas STL a menudo se copian, el rendimiento es mejor con las implementaciones vanilla de Python. A modo de comparación, compilé y ejecuté mis datos a través del código de Alf, y aquí está el rendimiento en la misma máquina que todas las demás ejecuciones, esencialmente idéntico a la implementación de Python ingenua (aunque más rápida que la implementación de Python que restablece / agrega la lista, como mostrado en la edición anterior):
$ /usr/bin/time cat test_lines_double | ./split6
15.09 real 0.01 user 0.45 sys
C++ : Saw 20000000 lines in 15 seconds. Crunch speed: 1333333
Mi única queja restante es la cantidad de código necesario para que C ++ funcione en este caso.
Una de las lecciones aquí de este problema y del problema de lectura de la línea stdin de ayer (vinculado arriba) es que siempre se debe comparar en lugar de hacer suposiciones ingenuas sobre el rendimiento relativo "predeterminado" de los idiomas. Agradezco la educación.
¡Gracias nuevamente a todos por sus sugerencias!