Escribir un archivo binario en C ++ muy rápido

Intento escribir grandes cantidades de datos en mi SSD (unidad de estado sólido). Y en grandes cantidades quiero decir 80GB.

Busqué soluciones en la web, pero lo mejor que se me ocurrió fue esto:

#include  const unsigned long long size = 64ULL*1024ULL*1024ULL; unsigned long long a[size]; int main() { std::fstream myfile; myfile = std::fstream("file.binary", std::ios::out | std::ios::binary); //Here would be some error handling for(int i = 0; i < 32; ++i){ //Some calculations to fill a[] myfile.write((char*)&a,size*sizeof(unsigned long long)); } myfile.close(); } 

Comstackdo con Visual Studio 2010 y optimizaciones completas y ejecutado en Windows 7, este progtwig alcanza un máximo de 20 MB / s. Lo que realmente me molesta es que Windows puede copiar archivos de otro SSD a este SSD en algún lugar entre 150MB / sy 200MB / s. Así que al menos 7 veces más rápido. Por eso creo que debería poder ir más rápido.

¿Alguna idea de cómo puedo acelerar mi escritura?

Editar: ahora comstack.

Esto hizo el trabajo:

 #include  const unsigned long long size = 8ULL*1024ULL*1024ULL; unsigned long long a[size]; int main() { FILE* pFile; pFile = fopen("file.binary", "wb"); for (unsigned long long j = 0; j < 1024; ++j){ //Some calculations to fill a[] fwrite(a, 1, size*sizeof(unsigned long long), pFile); } fclose(pFile); return 0; } 

Acabo de sincronizar 8GB en 36sec, que son aproximadamente 220MB / sy creo que eso maximiza mi SSD. También vale la pena tener en cuenta que el código en la pregunta utilizó un núcleo 100%, mientras que este código solo usa 2-5%.

Muchas gracias a todos.

Actualización : han pasado 5 años. Los comstackdores, el hardware, las bibliotecas y mis requisitos han cambiado. Es por eso que hice algunos cambios al código e hice algunas mediciones.

Primero el código:

 #include  #include  #include  #include  #include  #include  #include  #include  #include  std::vector GenerateData(std::size_t bytes) { assert(bytes % sizeof(uint64_t) == 0); std::vector data(bytes / sizeof(uint64_t)); std::iota(data.begin(), data.end(), 0); std::shuffle(data.begin(), data.end(), std::mt19937{ std::random_device{}() }); return data; } long long option_1(std::size_t bytes) { std::vector data = GenerateData(bytes); auto startTime = std::chrono::high_resolution_clock::now(); auto myfile = std::fstream("file.binary", std::ios::out | std::ios::binary); myfile.write((char*)&data[0], bytes); myfile.close(); auto endTime = std::chrono::high_resolution_clock::now(); return std::chrono::duration_cast(endTime - startTime).count(); } long long option_2(std::size_t bytes) { std::vector data = GenerateData(bytes); auto startTime = std::chrono::high_resolution_clock::now(); FILE* file = fopen("file.binary", "wb"); fwrite(&data[0], 1, bytes, file); fclose(file); auto endTime = std::chrono::high_resolution_clock::now(); return std::chrono::duration_cast(endTime - startTime).count(); } long long option_3(std::size_t bytes) { std::vector data = GenerateData(bytes); std::ios_base::sync_with_stdio(false); auto startTime = std::chrono::high_resolution_clock::now(); auto myfile = std::fstream("file.binary", std::ios::out | std::ios::binary); myfile.write((char*)&data[0], bytes); myfile.close(); auto endTime = std::chrono::high_resolution_clock::now(); return std::chrono::duration_cast(endTime - startTime).count(); } int main() { const std::size_t kB = 1024; const std::size_t MB = 1024 * kB; const std::size_t GB = 1024 * MB; for (std::size_t size = 1 * MB; size <= 4 * GB; size *= 2) std::cout << "option1, " << size / MB << "MB: " << option_1(size) << "ms" << std::endl; for (std::size_t size = 1 * MB; size <= 4 * GB; size *= 2) std::cout << "option2, " << size / MB << "MB: " << option_2(size) << "ms" << std::endl; for (std::size_t size = 1 * MB; size <= 4 * GB; size *= 2) std::cout << "option3, " << size / MB << "MB: " << option_3(size) << "ms" << std::endl; return 0; } 

Ahora el código se comstack con Visual Studio 2017 y g ++ 7.2.0 (que ahora es uno de mis requisitos). Dejo que el código se ejecute con dos configuraciones:

  • Ordenador portátil, Core i7, SSD, Ubuntu 16.04, g ++ Versión 7.2.0 con -std = c ++ 11 -march = native -O3
  • Escritorio, Core i7, SSD, Windows 10, Visual Studio 2017 Versión 15.3.1 con / Ox / Ob2 / Oi / Ot / GT / GL / Gy

Que dio las siguientes medidas (después de abandonar los valores de 1MB, porque eran valores atípicos obvios): enter image description here enter image description here Ambas veces la opción 1 y la opción 3 agotan mi SSD. No esperaba que esto se viera, porque option2 solía ser el código más rápido en mi máquina en ese momento.

TL; DR : Mis mediciones indican que se usa std::fstream sobre FILE .

Pruebe lo siguiente, en orden:

  • Tamaño de búfer más pequeño. Escribir ~ 2 MiB a la vez puede ser un buen comienzo. En mi última computadora portátil, ~ 512 KiB fue el punto ideal, pero aún no lo he probado en mi SSD.

    Nota: Noté que los buffers muy grandes tienden a disminuir el rendimiento. He notado pérdidas de velocidad con el uso de búferes de 16 MiB en lugar de búferes de 512 KiB antes.

  • Use _open (o _topen si quiere tener la _topen Windows) para abrir el archivo, luego use _write . Esto probablemente evitará una gran cantidad de almacenamiento en búfer, pero no es seguro.

  • Uso de funciones específicas de Windows como WriteFile y WriteFile . Eso evitará cualquier almacenamiento en la biblioteca estándar.

No veo ninguna diferencia entre std :: stream / FILE / device. Entre el almacenamiento en búfer y no el almacenamiento en búfer.

También tenga en cuenta:

  • Las unidades SSD “tienden” a disminuir la velocidad (tasas de transferencia más bajas) a medida que se llenan.
  • Las unidades SSD “tienden” a disminuir la velocidad (tasas de transferencia más bajas) a medida que envejecen (debido a bits que no funcionan).

Veo que el código se ejecuta en 63 segundos.
Por lo tanto, una tasa de transferencia de: 260M / s (mi SSD se ve un poco más rápido que el suyo).

 64 * 1024 * 1024 * 8 /*sizeof(unsigned long long) */ * 32 /*Chunks*/ = 16G = 16G/63 = 260M/s 

No obtengo ningún aumento al moverme a FILE * desde std :: fstream.

 #include  using namespace std; int main() { FILE* stream = fopen("binary", "w"); for(int loop=0;loop < 32;++loop) { fwrite(a, sizeof(unsigned long long), size, stream); } fclose(stream); } 

Por lo tanto, la transmisión en C ++ funciona tan rápido como lo permita la biblioteca subyacente.

Pero creo que es injusto comparar el sistema operativo con una aplicación que se basa en el sistema operativo. La aplicación no puede hacer suposiciones (no sabe que las unidades son SSD) y, por lo tanto, utiliza los mecanismos de archivos del sistema operativo para la transferencia.

Si bien el sistema operativo no necesita hacer ninguna suposición. Puede indicar los tipos de unidades implicadas y utilizar la técnica óptima para transferir los datos. En este caso, una memoria directa a la transferencia de memoria. Intente escribir un progtwig que copie 80G de 1 ubicación en la memoria a otra y vea qué tan rápido es eso.

Editar

Cambié mi código para usar las llamadas de nivel inferior:
es decir, sin almacenamiento en búfer.

 #include  #include  const unsigned long long size = 64ULL*1024ULL*1024ULL; unsigned long long a[size]; int main() { int data = open("test", O_WRONLY | O_CREAT, 0777); for(int loop = 0; loop < 32; ++loop) { write(data, a, size * sizeof(unsigned long long)); } close(data); } 

Esto no hizo ninguna diferencia.

NOTA : Mi unidad es una unidad SSD si tiene una unidad normal, es posible que vea una diferencia entre las dos técnicas anteriores. Pero como esperaba, el almacenamiento en búfer y el almacenamiento en búfer (al escribir fragmentos grandes mayores que el tamaño del búfer) no hacen ninguna diferencia.

Editar 2:

¿Has probado el método más rápido para copiar archivos en C ++?

 int main() { std::ifstream input("input"); std::ofstream output("ouptut"); output << input.rdbuf(); } 

La mejor solución es implementar una escritura asíncrona con doble almacenamiento en búfer.

Mire la línea de tiempo:

 ------------------------------------------------> FF|WWWWWWWW|FF|WWWWWWWW|FF|WWWWWWWW|FF|WWWWWWWW| 

La ‘F’ representa el tiempo para llenar el búfer, y ‘W’ representa el tiempo para escribir el búfer en el disco. Entonces, el problema en perder el tiempo entre escribir búferes en el archivo. Sin embargo, al implementar la escritura en un hilo separado, puede comenzar a llenar el siguiente búfer de inmediato de la siguiente manera:

 ------------------------------------------------> (main thread, fills buffers) FF|ff______|FF______|ff______|________| ------------------------------------------------> (writer thread) |WWWWWWWW|wwwwwwww|WWWWWWWW|wwwwwwww| 

F – llenando el primer buffer
f – llenando el segundo buffer
W – escribiendo el primer buffer al archivo
w – escribiendo el segundo buffer en el archivo
_ – espere mientras se completa la operación

Este enfoque con swaps de buffer es muy útil cuando llenar un buffer requiere un cálculo más complejo (por lo tanto, más tiempo). Siempre implemento una clase CSequentialStreamWriter que oculta la escritura asincrónica en el interior, por lo que para el usuario final la interfaz solo tiene funciones de escritura.

Y el tamaño del búfer debe ser un múltiplo del tamaño del clúster del disco. De lo contrario, terminará con un rendimiento deficiente al escribir un único búfer en 2 clústeres de discos adyacentes.

Escribiendo el último buffer.
Cuando llame a la función Escribir por última vez, debe asegurarse de que el búfer en uso esté siendo escrito también en el disco. Por lo tanto, CSequentialStreamWriter debe tener un método diferente, digamos Finalize (final buffer flush), que debe escribir en el disco la última porción de datos.

Manejo de errores.
Mientras el código comienza a llenar el segundo búfer, y el primero se escribe en un subproceso separado, pero la escritura falla por alguna razón, el hilo principal debe tener en cuenta ese error.

 ------------------------------------------------> (main thread, fills buffers) FF|fX| ------------------------------------------------> (writer thread) __|X| 

Supongamos que la interfaz de un CSequentialStreamWriter tiene la función Write que devuelve bool o arroja una excepción, por lo que tiene un error en un hilo separado, debe recordar ese estado, por lo que la próxima vez que llame Write o Finilize en el hilo principal, el método devolverá Falso o lanzará una excepción. Y realmente no importa en qué punto dejaste de llenar un búfer, incluso si escribiste algunos datos después de la falla, lo más probable es que el archivo esté dañado e inútil.

Sugiero probar el mapeo de archivos . Utilicé mmap en el pasado, en un entorno UNIX, y quedé impresionado por el alto rendimiento que podía lograr

¿Podría usar FILE* lugar y medir el rendimiento que ha obtenido? Un par de opciones es usar fwrite/write lugar de fstream :

 #include  int main () { FILE * pFile; char buffer[] = { 'x' , 'y' , 'z' }; pFile = fopen ( "myfile.bin" , "w+b" ); fwrite (buffer , 1 , sizeof(buffer) , pFile ); fclose (pFile); return 0; } 

Si decides usar write , prueba algo similar:

 #include  #include  int main(void) { int filedesc = open("testfile.txt", O_WRONLY | O_APPEND); if (filedesc < 0) { return -1; } if (write(filedesc, "This will be output to testfile.txt\n", 36) != 36) { write(2, "There was an error writing to testfile.txt\n", 43); return -1; } return 0; } 

También te aconsejaría que busques en el memory map . Esa puede ser tu respuesta. Una vez tuve que procesar un archivo de 20GB en otro para almacenarlo en la base de datos, y el archivo ni siquiera se abrió. Entonces la solución es utilizar el mapa moemory. Aunque hice eso en Python .

Intente utilizar las llamadas a la API open () / write () / close () y experimente con el tamaño del búfer de salida. Quiero decir, no pase el búfer entero “many-many-bytes” a la vez, haga un par de escrituras (es decir, TotalNumBytes / OutBufferSize). OutBufferSize puede ser de 4096 bytes a megabyte.

Otro bash: use WinAPI OpenFile / CreateFile y use este artículo de MSDN para desactivar el almacenamiento en búfer (FILE_FLAG_NO_BUFFERING). Y este artículo de MSDN en WriteFile () muestra cómo obtener el tamaño de bloque para que la unidad conozca el tamaño de búfer óptimo.

De todos modos, std :: ofstream es un contenedor y puede haber un locking en las operaciones de E / S. Tenga en cuenta que atravesar toda la matriz N-gigabyte también lleva algo de tiempo. Mientras escribe un pequeño búfer, llega al caché y funciona más rápido.

Intente utilizar archivos mapeados en memoria.

Si copia algo del disco A al disco B en el explorador, Windows emplea DMA. Eso significa que para la mayoría del proceso de copia, la CPU básicamente no hará nada más que decirle al controlador del disco dónde colocar y obtener datos de, eliminando un paso completo en la cadena, y uno que no está optimizado para mover grandes cantidades de datos, y me refiero a hardware.

Lo que haces involucra mucho a la CPU. Quiero señalarle los “Algunos cálculos para completar una parte []”. Lo cual creo que es esencial. Generas un [], y luego copias desde un [] a un buffer de salida (eso es lo que hace fstream :: write), luego generas de nuevo, etc.

¿Qué hacer? ¡Multithreading! (Espero que tengas un procesador multi-core)

  • tenedor.
  • Use un hilo para generar datos []
  • Utilice el otro para escribir datos de un [] en el disco
  • Necesitará dos matrices a1 [] y a2 [] y cambiará entre ellas
  • Necesitará algún tipo de sincronización entre sus hilos (semáforos, cola de mensajes, etc.)
  • Use funciones de nivel inferior, sin búfer, como la función WriteFile mencionada por Mehrdad

fstream s no son más lentos que los flujos C, per se, pero usan más CPU (especialmente si el almacenamiento en búfer no está configurado correctamente). Cuando una CPU se satura, limita la velocidad de E / S.

Al menos la implementación de MSVC 2015 copia 1 char a la vez en el búfer de salida cuando no está configurado un búfer de flujo (ver streambuf::xsputn ). Así que asegúrese de establecer un buffer de flujo (> 0) .

Puedo obtener una velocidad de escritura de 1500MB / s (la velocidad máxima de mi M.2 SSD) con fstream usando este código:

 #include  #include  #include  #include  #include  #ifdef __linux__ #include  #endif using namespace std; using namespace std::chrono; const size_t sz = 512 * 1024 * 1024; const int numiter = 20; const size_t bufsize = 1024 * 1024; int main(int argc, char**argv) { unique_ptr data(new char[sz]); unique_ptr buf(new char[bufsize]); for (size_t p = 0; p < sz; p += 16) { memcpy(&data[p], "BINARY.DATA.....", 16); } unlink("file.binary"); int64_t total = 0; if (argc < 2 || strcmp(argv[1], "fopen") != 0) { cout << "fstream mode\n"; ofstream myfile("file.binary", ios::out | ios::binary); if (!myfile) { cerr << "open failed\n"; return 1; } myfile.rdbuf()->pubsetbuf(buf.get(), bufsize); // IMPORTANT for (int i = 0; i < numiter; ++i) { auto tm1 = high_resolution_clock::now(); myfile.write(data.get(), sz); if (!myfile) cerr << "write failed\n"; auto tm = (duration_cast(high_resolution_clock::now() - tm1).count()); cout << tm << " ms\n"; total += tm; } myfile.close(); } else { cout << "fopen mode\n"; FILE* pFile = fopen("file.binary", "wb"); if (!pFile) { cerr << "open failed\n"; return 1; } setvbuf(pFile, buf.get(), _IOFBF, bufsize); // NOT important auto tm1 = high_resolution_clock::now(); for (int i = 0; i < numiter; ++i) { auto tm1 = high_resolution_clock::now(); if (fwrite(data.get(), sz, 1, pFile) != 1) cerr << "write failed\n"; auto tm = (duration_cast(high_resolution_clock::now() - tm1).count()); cout << tm << " ms\n"; total += tm; } fclose(pFile); auto tm2 = high_resolution_clock::now(); } cout << "Total: " << total << " ms, " << (sz*numiter * 1000 / (1024.0 * 1024 * total)) << " MB/s\n"; } 

Intenté este código en otras plataformas (Ubuntu, FreeBSD) y no noté diferencias en las tasas de E / S, pero una diferencia de uso de la CPU de aproximadamente 8: 1 ( fstream usa 8 veces más CPU ). Así que uno puede imaginarse, si tuviera un disco más rápido, la escritura de fstream se ralentizaría antes que la versión de stdio .

Si desea escribir rápidamente en las transmisiones de archivos, entonces podría hacer que la transmisión del búfer de lectura sea más grande:

 wfstream f; const size_t nBufferSize = 16184; wchar_t buffer[nBufferSize]; f.rdbuf()->pubsetbuf(buffer, nBufferSize); 

Además, cuando se escriben muchos datos en los archivos, a veces es más rápido extender lógicamente el tamaño del archivo en lugar de hacerlo físicamente, esto se debe a que al extender lógicamente un archivo, el sistema de archivos no pone a cero el nuevo espacio antes de escribir en él. También es inteligente extender lógicamente el archivo más de lo que realmente necesita para evitar muchas extensiones de archivo. La extensión de archivos lógicos es compatible con Windows llamando a SetFileValidData o xfsctl con XFS_IOC_RESVSP64 en sistemas XFS.

Estoy comstackndo mi progtwig en gcc en GNU / Linux y mingw en win 7 y win xp y funcionó bien

puede usar mi progtwig y para crear un archivo de 80 GB simplemente cambie la línea 33 a

 makeFile("Text.txt",1024,8192000); 

cuando salga del progtwig, el archivo se destruirá y luego revisará el archivo cuando se está ejecutando

tener el progtwig que quieres solo cambia el progtwig

primero es el progtwig de Windows y el segundo es para GNU / Linux

http://mustafajf.persiangig.com/Projects/File/WinFile.cpp

http://mustafajf.persiangig.com/Projects/File/File.cpp