Непосредственное чтение большого двоичного файла в C # без копирования - PullRequest
6 голосов
/ 08 июля 2010

Я ищу наиболее эффективный / прямой способ сделать эту простую операцию C / C ++:

void ReadData(FILE *f, uint16 *buf, int startsamp, int nsamps)
{
   fseek(f, startsamp*sizeof(uint16), SEEK_SET);
   fread(buf, sizeof(uint16), nsamps, f);
}

в C # /. NET.(Я игнорирую возвращаемые значения для ясности - производственный код будет их проверять.) В частности, мне нужно прочитать во многих (потенциально от 10 до 100 миллионов) 2-байтовые (16-битные) "короткие" целочисленные образцы данных (фиксированный формат), анализ не требуется) хранится в двоичном виде в файле на диске.Хорошая вещь о способе C состоит в том, что он считывает образцы непосредственно в буфер "uint16 *" без участия ЦП и без копирования.Да, это потенциально «небезопасно», так как использует буферы void * для буферов неизвестного размера, но, похоже, должна быть «безопасная» альтернатива .NET.

Каков наилучший способ сделать этов C #?Я осмотрелся и наткнулся на несколько советов («союзы», использующие FieldOffset, «небезопасный» код с использованием указателей, Marshalling), но ни один из них не вполне подходит для этой ситуации, без какого-либо копирования / преобразования.Я хотел бы избежать BinaryReader.ReadUInt16 (), так как это очень медленно и интенсивно использует процессор.На моей машине разница между циклом for () с ReadUInt16 () и чтением байтов напрямую в массив byte [] с помощью одного Read () составляет примерно 25-кратную разницу.Это соотношение может быть даже выше при неблокирующем вводе / выводе (перекрывая «полезную» обработку при ожидании дискового ввода / вывода).

В идеале, я бы хотел просто «замаскировать» массив ushort []как массив byte [], чтобы я мог заполнить его непосредственно с помощью Read (), или как-то с помощью Read () заполнить массив ushort [] напрямую:

// DOES NOT WORK!!
public void GetData(FileStream f, ushort [] buf, int startsamp, int nsamps)
{
    f.Position = startsamp*sizeof(ushort);
    f.Read(buf, 0, nsamps);
}

Но нет метода Read (), который принимаетмассив ushort [], только массив byte [].

Может ли это быть сделано непосредственно в C #, или мне нужно использовать неуправляемый код, или стороннюю библиотеку, или я должен прибегнуть к CPU-интенсивное преобразование по образцу?Хотя «сейф» предпочтительнее, я в порядке с «небезопасным» кодом или каким-то трюком с Маршалом, я просто еще не понял его.

Спасибо за любые указания!


[ОБНОВЛЕНИЕ]

Я хотел добавить некоторый код в соответствии с предложением dtb, так как кажется, что вокруг него есть несколько примеров ReadArray.Это очень просто, без проверки ошибок.

public void ReadMap(string fname, short [] data, int startsamp, int nsamps)
{
    var mmf = MemoryMappedFile.CreateFromFile(fname);
    var mmacc = mmf.CreateViewAccessor();

    mmacc.ReadArray(startsamp*sizeof(short), data, 0, nsamps);
}

Данные благополучно выгружаются в переданный массив.Вы также можете указать тип для более сложных типов.Кажется, он может самостоятельно выводить простые типы, но с помощью спецификатора типа это будет выглядеть так:

    mmacc.ReadArray<short>(startsamp*sizeof(short), data, 0, nsamps);

[UPATE2]

Я хотел добавить кодв соответствии с победным ответом Бена, в виде «голых костей», аналогичном приведенному выше, для сравнения.Этот код был скомпилирован и протестирован, работает и БЫСТРО.Я использовал тип SafeFileHandle непосредственно в DllImport (вместо более обычного IntPtr) для упрощения вещей.

[DllImport("kernel32.dll", SetLastError=true)]
[return:MarshalAs(UnmanagedType.Bool)]
static extern bool ReadFile(SafeFileHandle handle, IntPtr buffer, uint numBytesToRead, out uint numBytesRead, IntPtr overlapped);

[DllImport("kernel32.dll", SetLastError=true)]
[return:MarshalAs(UnmanagedType.Bool)]
static extern bool SetFilePointerEx(SafeFileHandle hFile, long liDistanceToMove, out long lpNewFilePointer, uint dwMoveMethod);

unsafe void ReadPINV(FileStream f, short[] buffer, int startsamp, int nsamps)
{
    long unused; uint BytesRead;
    SafeFileHandle nativeHandle = f.SafeFileHandle; // clears Position property
    SetFilePointerEx(nativeHandle, startsamp*sizeof(short), out unused, 0);

    fixed(short* pFirst = &buffer[0])
        ReadFile(nativeHandle, (IntPtr)pFirst, (uint)nsamps*sizeof(short), out BytesRead, IntPtr.Zero);
}

Ответы [ 3 ]

8 голосов
/ 08 июля 2010

Вы можете использовать MemoryMappedFile . После сопоставления файла с памятью вы можете создать представление (то есть a MemoryMappedViewAccessor ), которое предоставляет метод ReadArray . Этот метод может читать структуры из файла без маршалинга, и он работает с примитивными типами lie ushort.

2 голосов
/ 08 июля 2010

ответ dtb - это еще лучший способ (на самом деле, он также должен копировать данные, но никакого выигрыша нет), но я просто хотел отметить, что для извлечения ushortзначения из байтового массива, которые вы должны использовать BitConverter не BinaryReader

РЕДАКТИРОВАТЬ: пример кода для p / invoking ReadFile :

[DllImport("kernel32.dll", SetLastError=true)]
[return:MarshalAs(UnmanagedType.Bool)]
static extern bool ReadFile(IntPtr handle, IntPtr buffer, uint numBytesToRead, out uint numBytesRead, IntPtr overlapped);

[DllImport("kernel32.dll", SetLastError=true)]
[return:MarshalAs(UnmanagedType.Bool)]
static extern bool SetFilePointerEx(IntPtr hFile, long liDistanceToMove, out long lpNewFilePointer, uint dwMoveMethod);

unsafe bool read(FileStream fs, ushort[] buffer, int offset, int count)
{
  if (null == fs) throw new ArgumentNullException();
  if (null == buffer) throw new ArgumentNullException();
  if (offset < 0 || count < 0 || offset + count > buffer.Length) throw new ArgumentException();
  uint bytesToRead = 2 * count;
  if (bytesToRead < count) throw new ArgumentException(); // detect integer overflow
  long offset = fs.Position;
  SafeFileHandle nativeHandle = fs.SafeFileHandle; // clears Position property
  try {
    long unused;
    if (!SetFilePositionEx(nativeHandle, offset, out unused, 0);
    fixed (ushort* pFirst = &buffer[offset])
      if (!ReadFile(nativeHandle, new IntPtr(pFirst), bytesToRead, out bytesToRead, IntPtr.Zero)
        return false;
    if (bytesToRead < 2 * count)
      return false;
    offset += bytesToRead;
    return true;
  }
  finally {
    fs.Position = offset; // restore Position property
  }
}
1 голос
/ 01 апреля 2013

Я мог бы немного опоздать к игре здесь ... но самый быстрый метод, который я нашел, использовал комбинацию предыдущих ответов.

Если я делаю следующее:

MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(somePath);
Stream io = mmf.CreateViewStream();

int count;
byte[] byteBuffer = new byte[1024 << 2];
ushort[] dataBuffer = new ushort[buffer.Length >> 1];

while((count = io.Read(byteBuffer, 0, byteBuffer.Length)) > 0)
  Buffer.BlockCopy(buffer, 0, dataBuffer, 0, count);

Это было примерно в 2 раза быстрее принятого ответа.

Для меня метод unsafe был таким же, как Buffer.BlockCopy без MemoryMappedFile.MemoryMappedFile сократил немного времени.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...