System.IO.FileStream очень медленный на огромных файлах
У меня есть фрагмент кода, который должен иметь возможность модифицировать несколько байтов в конце файла. Проблема в том, что файлы огромны. До 100+ Гб.
Мне нужно, чтобы операция была как можно быстрее, но после нескольких часов Googeling, она выглядит как .Net здесь довольно ограничена.
В основном я пытаюсь использовать System.IO.FileStream и не знаю других методов. "Реверсивный" поток будет делать, но я знаю, как его создать (пишите с конца, а не из начала).
Вот что я делаю: (Примечание: время уходит при закрытии потока)
static void Main(string[] args)
{
//Simulate a large file
int size = 1000 * 1024 * 1024;
string filename = "blah.dat";
FileStream fs = new FileStream(filename, FileMode.Create);
fs.SetLength(size);
fs.Close();
//Modify the last byte
fs = new FileStream(filename, FileMode.Open);
//If I don't seek, the modification happens instantly
fs.Seek(-1, SeekOrigin.End);
fs.WriteByte(255);
//Now, since I am modifying the last byte,
//this last step is very slow
fs.Close();
}
}
Ответы
Ответ 1
Как уже отмечал Дарин, это артефакт вашего "симулятора" большого файла.
Задержка - это фактически "заполнение" файла, задержка происходит только в первый раз. Если вы повторите часть с //Modify the last byte
до fs.Close();
, она будет очень быстрой.
Ответ 2
Я выполнил несколько тестов, и результаты немного запутывают. Если вы создадите файл и измените его в одной программе, он будет медленным:
static void Main(string[] args)
{
//Simulate a large file
int size = 100 * 1024 * 1024;
string filename = "blah.datn";
using (var fs = new FileStream(filename, FileMode.Create))
{
fs.SetLength(size);
}
using (var fs = new FileStream(filename, FileMode.Open))
{
fs.Seek(-1, SeekOrigin.End);
fs.WriteByte(255);
}
}
Но если файл существует, и вы только пытаетесь изменить последний байт, это быстро:
static void Main(string[] args)
{
string filename = "blah.datn";
using (var fs = new FileStream(filename, FileMode.Open))
{
fs.Seek(-1, SeekOrigin.End);
fs.WriteByte(255);
}
}
Хммм...
Забастовкa >
UPDATE:
Пожалуйста, проигнорируйте мои предыдущие наблюдения и отмените это как ответ, потому что это все неправильно.
Дальнейшее исследование проблемы я заметил следующий шаблон. Предположим, что вы выделяете файл с заданным размером с нулевыми байтами:
using (var stream = File.OpenWrite("blah.dat"))
{
stream.SetLength(100 * 1024 * 1024);
}
Эта операция выполняется очень быстро и создает файл размером 100 МБ, заполненный нулями.
Теперь, если в какой-либо другой программе вы попытаетесь изменить последний байт, закрытие потока будет медленным:
using (var stream = File.OpenWrite("blah.dat"))
{
stream.Seek(-1, SeekOrigin.End);
stream.WriteByte(255);
}
Я понятия не имею о внутренних функциях файловой системы или о том, как именно создан этот файл, но у меня есть ощущение, что он не полностью инициализирован, пока вы не попытаетесь его модифицировать, и закрытие дескриптора будет медленным.
Чтобы подтвердить это, я тестировал неуправляемый код (не стесняйтесь исправить любую аберрацию, так как мой C очень ржавый):
void main()
{
int size = 100 * 1024 * 1024 - 1;
FILE *handle = fopen("blah.dat", "wb");
if (handle != NULL) {
fseek(handle, size, SEEK_SET);
char buffer[] = {0};
fwrite(buffer, 1, 1, handle);
fclose(handle);
}
}
Это ведет себя так же, как в .NET = > он выделяет файл размером 100 МБ, заполненный нулями, и это очень быстро.
Теперь, когда я пытаюсь изменить последний байт этого файла:
void main()
{
int size = 100 * 1024 * 1024 - 1;
FILE *handle = fopen("blah.datn", "rb+");
if (handle != NULL) {
fseek(handle, -1, SEEK_END);
char buffer[] = {255};
fwrite(buffer, 1, 1, handle);
fclose(handle);
}
}
Последний fclose(handle)
медленный. Я надеюсь, что некоторые эксперты принесут немного света.
Кажется, что изменение последнего байта реального файла (не разреженного) с использованием предыдущих методов выполняется очень быстро.
Ответ 3
Возможно, самый быстрый способ работы с большими файлами с помощью MemoryMappedFile. Файл с отображением памяти - это файл, который отображается (не загружен) в виртуальную память, поэтому вы можете получить доступ к случайным байтам в нем, не обращаясь к определенному местоположению, буферам загрузки и т.д. Вы также можете читать целые структуры непосредственно из файла без через десериализацию.
Следующий код, выходящий прямо из MSDN, загружает и сохраняет структуру MyColor в середине файла размером 512 МБ:
static void Main(string[] args)
{
long offset = 0x10000000; // 256 megabytes
long length = 0x20000000; // 512 megabytes
// Create a memory-mapped view of a portion of
// an extremely large image, from the 256th megabyte (the offset)
// to the 768th megabyte (the offset plus length).
using (var mmf =
MemoryMappedFile.CreateFromFile(@"c:\ExtremelyLargeImage.data",
FileMode.Open,"ImgA"))
{
using (var accessor = mmf.CreateViewAccessor(offset, length))
{
int colorSize = Marshal.SizeOf(typeof(MyColor));
MyColor color;
// Make changes to the view.
for (long i = 0; i < length; i += colorSize)
{
accessor.Read(i, out color);
color.Brighten(10);
accessor.Write(i, ref color);
}
}
}
}
public struct MyColor
{
public short Red;
public short Green;
public short Blue;
public short Alpha;
// Make the view brigher.
public void Brighten(short value)
{
Red = (short)Math.Min(short.MaxValue, (int)Red + value);
Green = (short)Math.Min(short.MaxValue, (int)Green + value);
Blue = (short)Math.Min(short.MaxValue, (int)Blue + value);
Alpha = (short)Math.Min(short.MaxValue, (int)Alpha + value);
}
}
Вы можете найти дополнительную информацию и образцы в Файлы с памятью
Ответ 4
Я предлагаю вам попробовать его с реальным файлом, а не с "смоделированным" файлом.
Возможно, что .net использует какой-то разреженный механизм распределения и только записывает файл до последнего байта, на самом деле написанного.
Итак, когда вы пишете в начале файла, ему нужно только записать несколько байтов, но когда вы пишете в конец файла, ему действительно нужно записать весь файл.