Я столкнулся с ситуацией, когда у меня есть довольно большой файл, из которого мне нужно прочитать двоичные данные.
Следовательно, я понял, что реализация BinaryReader по умолчанию в .NET довольно медленная. Посмотрев на него с помощью .NET Reflector я наткнулся на это:
public virtual int ReadInt32()
{
if (this.m_isMemoryStream)
{
MemoryStream stream = this.m_stream as MemoryStream;
return stream.InternalReadInt32();
}
this.FillBuffer(4);
return (((this.m_buffer[0] | (this.m_buffer[1] << 8)) | (this.m_buffer[2] << 0x10)) | (this.m_buffer[3] << 0x18));
}
Это кажется мне крайне неэффективным, если подумать о том, как компьютеры были разработаны для работы с 32-разрядными значениями с момента изобретения 32-разрядного ЦП.
Поэтому я создал собственный (небезопасный) класс FastBinaryReader с таким кодом:
public unsafe class FastBinaryReader :IDisposable
{
private static byte[] buffer = new byte[50];
//private Stream baseStream;
public Stream BaseStream { get; private set; }
public FastBinaryReader(Stream input)
{
BaseStream = input;
}
public int ReadInt32()
{
BaseStream.Read(buffer, 0, 4);
fixed (byte* numRef = &(buffer[0]))
{
return *(((int*)numRef));
}
}
...
}
Что гораздо быстрее - мне удалось сэкономить 5-7 секунд за время, необходимое для чтения файла размером 500 МБ, но в целом это все еще довольно медленно (первоначально 29 секунд и ~ 22 секунды с моим FastBinaryReader
) .
Это все еще немного сбивает меня с толку относительно того, почему все еще требуется так много времени, чтобы прочитать такой относительно небольшой файл. Если я скопирую файл с одного диска на другой, это займет всего пару секунд, поэтому пропускная способность диска не является проблемой.
Я также добавил вызовы ReadInt32 и т. Д., И у меня получился следующий код:
using (var br = new FastBinaryReader(new FileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, 0x10000, FileOptions.SequentialScan)))
while (br.BaseStream.Position < br.BaseStream.Length)
{
var doc = DocumentData.Deserialize(br);
docData[doc.InternalId] = doc;
}
}
public static DocumentData Deserialize(FastBinaryReader reader)
{
byte[] buffer = new byte[4 + 4 + 8 + 4 + 4 + 1 + 4];
reader.BaseStream.Read(buffer, 0, buffer.Length);
DocumentData data = new DocumentData();
fixed (byte* numRef = &(buffer[0]))
{
data.InternalId = *((int*)&(numRef[0]));
data.b = *((int*)&(numRef[4]));
data.c = *((long*)&(numRef[8]));
data.d = *((float*)&(numRef[16]));
data.e = *((float*)&(numRef[20]));
data.f = numRef[24];
data.g = *((int*)&(numRef[25]));
}
return data;
}
Есть еще идеи, как сделать это еще быстрее? Я подумал, может быть, я мог бы использовать маршаллинг для отображения всего файла прямо в память поверх какой-либо пользовательской структуры, поскольку данные являются линейными, фиксированного размера и последовательными.
РЕШЕНО: Я пришел к выводу, что буферизация FileStream / BufferedStream несовершенна. Пожалуйста, посмотрите принятый ответ и мой собственный ответ (с решением) ниже.