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 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 150 MB / sy 200 MB / s. Entonces 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?
c++
performance
optimization
file-io
io
Dominic Hofer
fuente
fuente
fwrite()
podría obtener alrededor del 80% de las velocidades máximas de escritura. Solo conFILE_FLAG_NO_BUFFERING
alguna vez pude obtener la velocidad máxima.Respuestas:
Esto hizo el trabajo (en el año 2012):
Acabo de cronometrar 8 GB en 36 segundos, que es de unos 220 MB / sy creo que eso maximiza mi SSD. También vale la pena señalar que el código en la pregunta usaba un núcleo 100%, mientras que este código solo usa 2-5%.
Muchas gracias a todos.
Actualización : han pasado 5 años, es 2017 ahora. 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 medidas nuevas.
Primero el código:
Este código se compila con Visual Studio 2017 y g ++ 7.2.0 (nuevos requisitos). Ejecuté el código con dos configuraciones:
Lo que dio las siguientes medidas (después de deshacerse de los valores de 1 MB, porque eran valores atípicos obvios): Ambas veces la opción 1 y la opción 3 maximizan mi SSD. No esperaba que esto viera, porque la opción 2 solía ser el código más rápido en mi vieja máquina en ese entonces.
TL; DR : Mis medidas indican que debo usar
std::fstream
másFILE
.fuente
FILE*
Es más rápido que las transmisiones. No hubiera esperado tal diferencia ya que "debería" haber estado vinculada a E / S de todos modos.ios::sync_with_stdio(false);
hace alguna diferencia para el código con stream? Tengo curiosidad por saber la gran diferencia entre usar esta línea y no, pero no tengo el disco lo suficientemente rápido como para comprobar el caso de la esquina. Y si hay alguna diferencia real.Pruebe lo siguiente, en orden:
Tamaño de tampón 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 buffers 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.
Use
_open
(o_topen
si desea ser correcto para Windows) para abrir el archivo, luego use_write
. Esto probablemente evitará una gran cantidad de almacenamiento en búfer, pero no es seguro.Usando funciones específicas de Windows como
CreateFile
yWriteFile
. Eso evitará cualquier almacenamiento en búfer en la biblioteca estándar.fuente
FILE_FLAG_NO_BUFFERING
, en el que los búferes más grandes tienden a ser mejores. Ya que creo queFILE_FLAG_NO_BUFFERING
es más o menos DMA.No veo diferencia entre std :: stream / FILE / device. Entre buffering y no buffering.
También tenga en cuenta:
Estoy viendo el código correr 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 suyo).
Obtengo un aumento sin moverme a FILE * desde std :: fstream.
Por lo tanto, el flujo de C ++ funciona tan rápido como lo permite la biblioteca subyacente.
Pero creo que es injusto comparar el sistema operativo con una aplicación construida sobre 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 archivo del sistema operativo para la transferencia.
Si bien el sistema operativo no necesita hacer ninguna suposición. Puede indicar los tipos de unidades involucradas y utilizar 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 una 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.
Esto no hizo 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, el no almacenamiento en búfer y el almacenamiento en búfer (cuando se escriben fragmentos grandes mayores que el tamaño del búfer) no hacen ninguna diferencia.
Edición 2:
¿Has probado el método más rápido de copiar archivos en C ++
fuente
FILE*
.La mejor solución es implementar una escritura asíncrona con doble búfer.
Mira la línea de tiempo:
La 'F' representa el tiempo para llenar el búfer y la 'W' representa el tiempo para escribir el búfer en el disco. Entonces, el problema es perder el tiempo entre escribir buffers para archivar. Sin embargo, al implementar la escritura en un hilo separado, puede comenzar a llenar el siguiente búfer de inmediato de esta manera:
F - llenado del 1er búfer
f - llenado del 2o búfer
W - escritura del 1er búfer en el archivo
w - escritura del 2o búfer en el archivo
_ - espere mientras se completa la operación
Este enfoque con intercambios 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 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 de disco. De lo contrario, terminará con un rendimiento deficiente escribiendo un único búfer en 2 grupos de discos adyacentes.
Escribiendo el último búfer.
Cuando llama a la función Escribir 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 debería tener un método separado, digamos Finalizar (descarga de búfer final), que debería escribir en el disco la última porción de datos.
Manejo de errores.
Mientras que el código comienza a llenar el segundo búfer, y el primero se está escribiendo en un hilo separado, pero la escritura falla por alguna razón, el hilo principal debe ser consciente de ese error.
Supongamos que la interfaz de un CSequentialStreamWriter tiene una función Write que devuelve bool o lanza una excepción, por lo tanto, al tener un error en un hilo separado, debe recordar ese estado, por lo que la próxima vez que llame a Write o Finilize en el hilo principal, el método devolverá Falso o arrojará una excepción. Y realmente no importa en qué punto dejaste de llenar un búfer, incluso si escribiste algunos datos después del error, lo más probable es que el archivo esté dañado e inútil.
fuente
Sugeriría probar el mapeo de archivos . Utilicé
mmap
en el pasado, en un entorno UNIX, y me impresionó el alto rendimiento que podía lograrfuente
¿Podría usar
FILE*
en su lugar y medir el rendimiento que ha ganado? Un par de opciones es usar enfwrite/write
lugar defstream
:Si decides usarlo
write
, prueba algo similar:También te aconsejaría que lo investigaras
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 no se abría. Entonces, la solución es utilizar el mapa de memoria. Lo hicePython
sin embargo.fuente
FILE*
equivalente directo del código original que usa el mismo búfer de 512 MB obtiene toda su velocidad. Su búfer actual es demasiado pequeño.2
corresponde al error estándar, pero aún se recomienda que lo use enSTDERR_FILENO
lugar de2
. Otro problema importante es que un posible error que no puede obtener es EINTR para cuando recibe una señal de interrupción, este no es un error real y simplemente debe intentarlo nuevamente.Intente usar las llamadas API open () / write () / close () y experimente con el tamaño del búfer de salida. Quiero decir, no pase todo el búfer "muchos-muchos-bytes" a la vez, haga 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 almacenamiento en búfer (FILE_FLAG_NO_BUFFERING). Y este artículo de MSDN sobre WriteFile () muestra cómo obtener el tamaño de bloque para que la unidad conozca el tamaño óptimo del búfer.
De todos modos, std :: ofstream es un contenedor y puede haber bloqueo en las operaciones de E / S. Tenga en cuenta que atravesar toda la matriz de N-gigabytes también lleva algún tiempo. Mientras escribe un pequeño búfer, llega al caché y funciona más rápido.
fuente
fstream
Los 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 carácter a la vez en el 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 1500 MB / s (la velocidad máxima de mi SSD M.2) con el
fstream
uso de este código:Probé este código en otras plataformas (Ubuntu, FreeBSD) y no noté diferencias de velocidad de E / S, pero sí una diferencia de uso de CPU de aproximadamente 8: 1 (
fstream
usó 8 veces más CPU ). Así que uno puede imaginar, si tuviera un disco más rápido, lafstream
escritura se ralentizaría antes que lastdio
versión.fuente
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ñalarle la parte "Algunos cálculos para llenar una parte []". Lo cual creo que es esencial. Usted genera un [], luego copia de un [] a un búfer de salida (eso es lo que hace fstream :: write), luego genera de nuevo, etc.
¿Qué hacer? Multithreading! (Espero que tengas un procesador multi-core)
fuente
Intenta usar archivos mapeados en memoria.
fuente
ReadFile
y similares para el acceso secuencial, aunque para el acceso aleatorio pueden ser mejores.Si desea escribir rápidamente en secuencias de archivos, puede hacer que la secuencia de lectura del búfer sea más grande:
Además, cuando se escriben muchos datos en 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 archivo lógico es compatible con Windows llamando
SetFileValidData
oxfsctl
conXFS_IOC_RESVSP64
sistemas XFS.fuente
Estoy compilando mi programa en gcc en GNU / Linux y mingw en win 7 y win xp y funcionó bien
puedes usar mi programa y para crear un archivo de 80 GB simplemente cambia la línea 33 a
cuando salga del programa, el archivo se destruirá y luego verifique el archivo cuando se esté ejecutando
para tener el programa que desea simplemente cambie 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
fuente