c++ - sirve - ¿Cómo hacer que IOStream funcione mejor?
que es cout en c++ (3)
La mayoría de los usuarios de C ++ que aprendieron C prefieren usar la familia de funciones printf
/ scanf
, incluso cuando están codificando en C ++.
Aunque admito que la interfaz me parece mucho mejor (especialmente en el formato y localización POSIX), parece que el rendimiento es una preocupación abrumadora.
Tomando en cuenta esta pregunta:
¿Cómo puedo acelerar la lectura línea por línea de un archivo?
Parece que la mejor respuesta es usar fscanf
y que el C ++ ifstream
es consistentemente 2-3 veces más lento.
Pensé que sería genial si pudiéramos compilar un depósito de "consejos" para mejorar el rendimiento de IOStreams, lo que funciona y lo que no funciona.
Puntos a considerar
- buffering (
rdbuf()->pubsetbuf(buffer, size)
) - sincronización (
std::ios_base::sync_with_stdio
) - manejo de la configuración regional (¿Podríamos usar una configuración regional modificada o eliminarla por completo?)
Por supuesto, otros enfoques son bienvenidos.
Nota: se mencionó una implementación "nueva", por Dietmar Kuhl, pero no pude encontrar muchos detalles al respecto. Las referencias anteriores parecen ser enlaces muertos.
Dos mejoras más:
Issue std::cin.tie(nullptr);
antes de una entrada / salida pesada.
Citando http://en.cppreference.com/w/cpp/io/cin :
Una vez que se construye std :: cin, std :: cin.tie () devuelve & std :: cout, y asimismo, std :: wcin.tie () devuelve & std :: wcout. Esto significa que cualquier operación de entrada formateada en std :: cin fuerza una llamada a std :: cout.flush () si hay caracteres pendientes para la salida.
Puede evitar descargar el búfer al desvincular std::cin
de std::cout
. Esto es relevante con múltiples llamadas mixtas a std::cin
y std::cout
. Tenga en cuenta que al llamar a std::cin.tie(std::nullptr);
hace que el programa no sea apto para ejecutarse interactivamente por el usuario, ya que la salida puede retrasarse.
Punto de referencia relevante:
Archivo test1.cpp
:
#include <iostream>
using namespace std;
int main()
{
ios_base::sync_with_stdio(false);
int i;
while(cin >> i)
cout << i << ''/n'';
}
Archivo test2.cpp
:
#include <iostream>
using namespace std;
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int i;
while(cin >> i)
cout << i << ''/n'';
cout.flush();
}
Ambos compilados por g++ -O2 -std=c++11
. Versión del compilador: g++ (Ubuntu 4.8.4-2ubuntu1~14.04) 4.8.4
(sí, lo sé, bastante viejo).
Resultados de referencia:
work@mg-K54C ~ $ time ./test1 < test.in > test1.in
real 0m3.140s
user 0m0.581s
sys 0m2.560s
work@mg-K54C ~ $ time ./test2 < test.in > test2.in
real 0m0.234s
user 0m0.234s
sys 0m0.000s
( test.in
consta de 1179648 líneas, cada una de las cuales consta de solo 5
Son 2,4 MB, así que lo siento por no publicarlo aquí).
Recuerdo haber resuelto una tarea algorítmica en la que el juez en línea rehusaba mi programa sin cin.tie(nullptr)
pero lo aceptaba con cin.tie(nullptr)
o printf
/ scanf
lugar de cin
/ cout
.
Use ''/n''
lugar de std::endl
.
Citando http://en.cppreference.com/w/cpp/io/manip/endl :
Inserta un carácter de nueva línea en la secuencia de salida os y lo vacía como si llamara a os.put (os.widen (''/ n'')) seguido de os.flush ().
Puede evitar enjuagar el documento imprimiendo ''/n''
lugar de endl
.
Punto de referencia relevante:
Archivo test1.cpp
:
#include <iostream>
using namespace std;
int main()
{
ios_base::sync_with_stdio(false);
for(int i = 0; i < 1179648; ++i)
cout << i << endl;
}
Archivo test2.cpp
:
#include <iostream>
using namespace std;
int main()
{
ios_base::sync_with_stdio(false);
for(int i = 0; i < 1179648; ++i)
cout << i << ''/n'';
}
Ambos compilados como arriba.
Resultados de referencia:
work@mg-K54C ~ $ time ./test1 > test1.in
real 0m2.946s
user 0m0.404s
sys 0m2.543s
work@mg-K54C ~ $ time ./test2 > test2.in
real 0m0.156s
user 0m0.135s
sys 0m0.020s
Esto es lo que he reunido hasta ahora:
Almacenamiento en búfer :
Si por defecto el buffer es muy pequeño, aumentar el tamaño del buffer definitivamente puede mejorar el rendimiento:
- reduce la cantidad de hits HDD
- reduce la cantidad de llamadas al sistema
El búfer se puede establecer accediendo a la implementación de streambuf
subyacente.
char Buffer[N];
std::ifstream file("file.txt");
file.rdbuf()->pubsetbuf(Buffer, N);
// the pointer reader by rdbuf is guaranteed
// to be non-null after successful constructor
Advertencia cortesía de @iavr: según cppreference , es mejor llamar a pubsetbuf
antes de abrir el archivo. Varias implementaciones de bibliotecas estándar tienen comportamientos diferentes.
Manejo de Localidad:
Locale puede realizar la conversión de caracteres, el filtrado y trucos más inteligentes en los que están involucrados números o fechas. Pasan por un complejo sistema de despacho dinámico y llamadas virtuales, por lo que eliminarlos puede ayudar a recortar el golpe de penalización.
La configuración regional C
predeterminada está diseñada para no realizar ninguna conversión, así como para ser uniforme en todas las máquinas. Es un buen valor predeterminado para usar.
Sincronización:
No pude ver ninguna mejora en el rendimiento con esta instalación.
Se puede acceder a una configuración global (miembro estático de std::ios_base
) utilizando la función estática sync_with_stdio
.
Mediciones:
Jugando con esto, he jugado con un programa simple, compilado usando gcc 3.4.2
en SUSE 10p3 con -O2
.
C: 7.76532e + 06
C ++: 1.0874e + 07
Lo que representa una desaceleración de alrededor del 20%
... para el código predeterminado. De hecho, alterar el búfer (en C o C ++) o los parámetros de sincronización (C ++) no produjo ninguna mejora.
Resultados por otros:
@Irfy en g ++ 4.7.2-2ubuntu1, -O3, Ubuntu 11.10 virtualizado, 3.5.0-25-generic, x86_64, suficiente ram / cpu, 196MB de varias ejecuciones "find / >> largefile.txt"
C: 634572 C ++: 473222
C ++ 25% más rápido
@Matteo Italia en g ++ 4.4.5, -O3, Ubuntu Linux 10.10 x86_64 con un archivo aleatorio de 180 MB
C: 910390
C ++: 776016
C ++ 17% más rápido
@Bogatyr en g ++ i686-apple-darwin10-g ++ - 4.2.1 (GCC) 4.2.1 (Apple Inc. compilación 5664), mac mini, 4GB ram, inactivo excepto para esta prueba con un archivo de datos de 168MB
C: 4.34151e + 06
C ++: 9.14476e + 06
C ++ 111% más lento
@Asu en clang ++ 3.8.0-2ubuntu4, Kubuntu 16.04 Linux 4.8-rc3, 8GB ram, i5 Haswell, SSD Crucial, archivo de datos de 88MB (archivo tar.xz)
C: 270895 C ++: 162799
C ++ 66% más rápido
Entonces la respuesta es: es una cuestión de calidad de implementación, y realmente depende de la plataforma: /
El código completo aquí para aquellos interesados en la evaluación comparativa:
#include <fstream>
#include <iostream>
#include <iomanip>
#include <cmath>
#include <cstdio>
#include <sys/time.h>
template <typename Func>
double benchmark(Func f, size_t iterations)
{
f();
timeval a, b;
gettimeofday(&a, 0);
for (; iterations --> 0;)
{
f();
}
gettimeofday(&b, 0);
return (b.tv_sec * (unsigned int)1e6 + b.tv_usec) -
(a.tv_sec * (unsigned int)1e6 + a.tv_usec);
}
struct CRead
{
CRead(char const* filename): _filename(filename) {}
void operator()() {
FILE* file = fopen(_filename, "r");
int count = 0;
while ( fscanf(file,"%s", _buffer) == 1 ) { ++count; }
fclose(file);
}
char const* _filename;
char _buffer[1024];
};
struct CppRead
{
CppRead(char const* filename): _filename(filename), _buffer() {}
enum { BufferSize = 16184 };
void operator()() {
std::ifstream file(_filename, std::ifstream::in);
// comment to remove extended buffer
file.rdbuf()->pubsetbuf(_buffer, BufferSize);
int count = 0;
std::string s;
while ( file >> s ) { ++count; }
}
char const* _filename;
char _buffer[BufferSize];
};
int main(int argc, char* argv[])
{
size_t iterations = 1;
if (argc > 1) { iterations = atoi(argv[1]); }
char const* oldLocale = setlocale(LC_ALL,"C");
if (strcmp(oldLocale, "C") != 0) {
std::cout << "Replaced old locale ''" << oldLocale << "'' by ''C''/n";
}
char const* filename = "largefile.txt";
CRead cread(filename);
CppRead cppread(filename);
// comment to use the default setting
bool oldSyncSetting = std::ios_base::sync_with_stdio(false);
double ctime = benchmark(cread, iterations);
double cpptime = benchmark(cppread, iterations);
// comment if oldSyncSetting''s declaration is commented
std::ios_base::sync_with_stdio(oldSyncSetting);
std::cout << "C : " << ctime << "/n"
"C++: " << cpptime << "/n";
return 0;
}
Interesante usted dice que los programadores de C prefieren printf cuando escriben C ++, ya que veo una gran cantidad de código que es C, aparte de usar cout
y iostream
para escribir el resultado.
Los usos a menudo pueden obtener un mejor rendimiento al usar filebuf
directamente (Scott Meyers lo mencionó en STL efectivo) pero hay relativamente poca documentación en el uso de filebuf directo y la mayoría de los desarrolladores prefieren std::getline
que es más simple la mayoría de las veces.
Con respecto a la configuración regional, si crea facetas, a menudo obtendrá un mejor rendimiento creando una configuración regional una vez con todas sus facetas, manteniéndola almacenada e introduciéndola en cada secuencia que use.
Vi otro tema aquí recientemente, por lo que está cerca de ser un duplicado.