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


Estoy tratando de escribir grandes cantidades de datos en mi SSD(unidad de estado sólido). Y por grandes cantidades me refiero a 80 GB.

Navegué por la web en busca de soluciones, pero lo mejor que se me ocurrió fue esto:

#include <fstream>
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();
}

Compilado con Visual Studio 2010 y optimizaciones completas y ejecutado bajo Windows7 este programa alcanza un máximo de alrededor de 20MB/s. Lo que realmente me molesta es que Windows puede copiar archivos de otro SSD a este SSD en algún lugar entre 150MB/s y 200MB/s. Por lo menos 7 veces más rápido. Es por eso que creo que debería ser capaz de ir más rápido.

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

Edit: Ahora se compila.

Author: plasmacel, 2012-07-19

12 answers

Esto hizo el trabajo:

#include <stdio.h>
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 cronometrar 8 GB en 36 segundos, que es de aproximadamente 220 MB/s y creo que eso maximiza mi SSD. También vale la pena señalar, el código en la pregunta utiliza un núcleo 100%, mientras que este código solo utiliza 2-5%.

Muchas gracias a todos.

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

En primer lugar código:

#include <fstream>
#include <chrono>
#include <vector>
#include <cstdint>
#include <numeric>
#include <random>
#include <algorithm>
#include <iostream>
#include <cassert>

std::vector<uint64_t> GenerateData(std::size_t bytes)
{
    assert(bytes % sizeof(uint64_t) == 0);
    std::vector<uint64_t> 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<uint64_t> 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<std::chrono::milliseconds>(endTime - startTime).count();
}

long long option_2(std::size_t bytes)
{
    std::vector<uint64_t> 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<std::chrono::milliseconds>(endTime - startTime).count();
}

long long option_3(std::size_t bytes)
{
    std::vector<uint64_t> 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<std::chrono::milliseconds>(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 compila 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:

  • Laptop, 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 obvios valores atípicos): introduzca la descripción de la imagen aquí introduzca la descripción de la imagen aquí Tanto option1 como option3 maximizan 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 entonces.

TL; DR : Mis medidas indican usar std::fstream sobre FILE.

 173
Author: Dominic Hofer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-20 22:59:52

Intente lo siguiente, en orden:

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

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

  • Utilice _open (o _topen si desea que Windows sea correcto) para abrir el archivo, luego use _write. Esto probablemente evitará un montón de buffering, pero no es seguro.

  • Usando funciones específicas de Windows como CreateFile y WriteFile. Esto evitará cualquier buffering en la biblioteca estándar.

 23
Author: Mehrdad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-19 16:09:48

No veo ninguna diferencia entre std::stream/FILE/device. Entre buffering y no buffering.

También tenga en cuenta:

  • Las unidades SSD "tienden" a ralentizarse (tasas de transferencia más bajas) a medida que se llenan.
  • Las unidades SSD "tienden" a ralentizarse (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 velocidad de transferencia de: 260M/s (mi SSD se ve un poco más rápido que el tuyo).

64 * 1024 * 1024 * 8 /*sizeof(unsigned long long) */ * 32 /*Chunks*/

= 16G
= 16G/63 = 260M/s

Consigo un no hay aumento moviéndose a ARCHIVO * desde std:: fstream.

#include <stdio.h>

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

}

Así que el flujo de C++ está funcionando tan rápido como lo permita la biblioteca subyacente.

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

Mientras que el sistema operativo no necesita hacer ninguna suposición. Puede decir los tipos de las unidades involucradas y el uso la técnica óptima para transferir los datos. En este caso una transferencia directa de memoria a memoria. Intente escribir un programa que copie 80G de 1 ubicación en memoria a otra y vea qué tan rápido es.

Editar

Cambié mi código para usar las llamadas de nivel inferior:
es decir, sin buffering.

#include <fcntl.h>
#include <unistd.h>


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, puede ver una diferencia entre las dos técnicas anteriores. Pero como esperaba no buffering y buffering (cuando se escriben trozos grandes mayores que el tamaño del búfer) no hacen ninguna diferencia.

Editar 2:

¿Ha probado el método más rápido de copiar archivos en C++

int main()
{
    std::ifstream  input("input");
    std::ofstream  output("ouptut");

    output << input.rdbuf();
}
 20
Author: Martin York,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-19 18:16:18

La mejor solución es implementar una escritura asíncrona con doble buffering.

Mira la línea de tiempo:

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

La 'F' representa el tiempo para el llenado del búfer, y la 'W' representa el tiempo para escribir el búfer en el disco. Así que el problema en perder el tiempo entre la escritura de búferes a 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-llenado 1er tampón
f-relleno 2nd buffer
W - escritura 1st buffer to file
w-escribiendo 2nd buffer a file
_ - espere mientras se completa la operación

Este enfoque con swaps de búfer es muy útil cuando llenar un búfer requiere un cálculo más complejo (por lo tanto, más tiempo). Siempre implemento una clase CSequentialStreamWriter que oculta la escritura asíncrona dentro, 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 de disco. De lo contrario, terminarás con rendimiento deficiente al escribir un solo búfer en 2 clústeres de disco adyacentes.

Escribiendo el último buffer.
Cuando llama a la función de escritura por última vez, debe asegurarse de que el búfer actual se está llenando también debe escribirse en el disco. Por lo tanto, CSequentialStreamWriter debe tener un método separado, digamos Finalize (final buffer flush), que debe escribir en el disco la última porción de datos.

Manejo de errores.
Mientras que el código comienza a llenar el 2do búfer, y el 1st one se está escribiendo en un hilo separado, pero write falla por alguna razón, el hilo principal debe ser consciente de ese error.

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

Supongamos que la interfaz de un CSequentialStreamWriter tiene la función Write devuelve bool o lanza una excepción, por lo tanto, al tener un error en un subproceso separado, debe recordar ese estado, por lo que la próxima vez que llame a Write o Finilize en el subproceso principal, el método devolverá False o lanzará una excepción. Y realmente no importa en qué punto dejó de llenar un búfer, incluso si escribió algunos datos por delante después del error, lo más probable es que el archivo esté dañado e inútil.

 12
Author: HandMadeOX,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-28 00:56:25

Sugeriría probar mapeo de archivos. Usé mmap en el pasado, en un entorno UNIX, y me impresionó el alto rendimiento que pude lograr

 10
Author: Ralph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-19 21:35:11

¿Podrías usar FILE* en su lugar, y la medida del rendimiento que has ganado? Un par de opciones es usar fwrite/write en lugar de fstream:

#include <stdio.h>

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 <unistd.h>
#include <fcntl.h>

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 les aconsejaría que investiguen memory map. Esa puede ser tu respuesta. Una vez tuve que procesar un archivo de 20 GB en otro para almacenarlo en la base de datos, y el archivo ni siquiera se abre. Así que la solución en cuanto a utilizar moemory mapa. Hice eso en Python sin embargo.

 7
Author: cybertextron,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-19 15:55:57

Intente usar llamadas a la API open()/write()/close() y experimente con el tamaño del búfer de salida. Me refiero a no pasar todo el búfer" muchos-muchos-bytes " a la vez, hacer un par de escrituras (es decir, TotalNumBytes / OutBufferSize). OutBufferSize puede ser de 4096 bytes a megabyte.

Otro intento - use WinAPI OpenFile/CreateFile y use este artículo de MSDN para desactivar el buffering (FILE_FLAG_NO_BUFFERING). And this MSDN article on WriteFile () shows how to get the block size for the conduzca para conocer el tamaño óptimo del búfer.

De todos modos, std::ofstream es un wrapper y podría haber bloqueo en las operaciones de E/S. Tenga en cuenta que atravesar toda la matriz de N-gigabyte también lleva algún tiempo. Mientras escribes un pequeño búfer, llega a la caché y funciona más rápido.

 6
Author: Viktor Latypov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-19 15:25:44

Intente usar archivos mapeados en memoria.

 3
Author: qehgt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-19 15:43:08

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

Lo que haces involucra mucho a la CPU. Quiero señalar a la" Algunos cálculos para llenar una [] " parte. Que yo pensar es esencial. Generas un [], luego copias desde un [] a un búfer de salida (eso es lo que hace fstream::write), luego generas de nuevo, etc.

¿Qué hacer? ¡Multihilo! (Espero que tenga un procesador multinúcleo)

  • tenedor.
  • Utilice un hilo para generar un [] data
  • Use el otro para escribir datos desde un[] al disco
  • Necesitará dos matrices a1[] y a2[] y cambiar entre ellas
  • Necesitará algún tipo de sincronización entre su hilos (semáforos, cola de mensajes, etc.))
  • Use funciones de nivel inferior, sin búfer, como la función the WriteFile mencionada por Mehrdad
 3
Author: dualed,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-19 16:33:00

fstreams no son más lentos que los flujos de C, per se, pero usan más CPU (especialmente si el buffering 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 carácter a la vez al búfer de salida cuando no se establece un búfer de flujo (ver streambuf::xsputn). Así que asegúrese de establecer un búfer de flujo(>0).

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

#include <iostream>
#include <fstream>
#include <chrono>
#include <memory>
#include <stdio.h>
#ifdef __linux__
#include <unistd.h>
#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<char[]> data(new char[sz]);
  unique_ptr<char[]> 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<milliseconds>(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<milliseconds>(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";
}

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

 3
Author: rustyx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-14 08:32:37

Si desea escribir rápidamente en secuencias de archivos, puede hacer que la secuencia 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 archivos, a veces es más rápido lógicamente extender el tamaño del archivo en lugar de físicamente, esto se debe a que cuando se extiende lógicamente un archivo, el sistema de archivos no pone cero el nuevo espacio antes de escribirlo. También es inteligente extender lógicamente el archivo más de lo que realmente necesita para evitar muchas extensiones de archivos. Archivo lógico la extensión se admite en Windows llamando a SetFileValidData o xfsctl con XFS_IOC_RESVSP64 en sistemas XFS.

 1
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-02 18:17:21

Estoy compilando mi programa en gcc en GNU / Linux y mingw en win 7 y win xp y funcionó bien

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

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

Cuando salga del programa, el archivo se destruirá y luego verifique el archivo cuando se esté ejecutando

Para tener el programa que desea simplemente cambiar el programa

El primero es el programa 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

 0
Author: MostafaJF,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-16 02:14:43