2010-07-08 14 views
6

Ich suche die effizienteste/direkte Art und Weise dieses einfachen C/C++ Betrieb zu tun:direkt lesen große binäre Datei in C# w/out Kopieren

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

in C#/NET.. (Ich ignoriere Rückgabewerte für Klarheit - Produktionscode würde sie überprüfen.) Insbesondere muss ich viele (potentiell 10 bis 100 Millionen) 2-Byte (16-Bit) "ushort" Ganzzahl-Datenabtastwerte (festes Format) einlesen , kein Parsen erforderlich), das in einer Datei in einer binären Datei gespeichert ist. Das Schöne an der C-Methode ist, dass sie die Samples direkt in den "uint16 *" -Puffer ohne CPU-Beteiligung liest und nicht kopiert. Ja, es ist möglicherweise "unsicher", da es void * -Zeiger auf Puffer unbekannter Größe verwendet, aber es scheint, als sollte es eine "sichere" .NET-Alternative geben.

Was ist der beste Weg, dies in C# zu erreichen? Ich habe mich umgesehen und bin auf ein paar Hinweise gestoßen ("Unionen" mit FieldOffset, "unsicheren" Code mit Hilfe von Zeigern, Marshalling), aber keine scheint für diese Situation zu funktionieren, ohne eine Art von Kopieren/Konvertierung zu verwenden. Ich möchte BinaryReader.ReadUInt16() vermeiden, da das sehr langsam und CPU-intensiv ist. Auf meinem Rechner gibt es einen 25-fachen Geschwindigkeitsunterschied zwischen einer for() - Schleife mit ReadUInt16() und das Lesen der Bytes direkt in ein byte [] -Array mit einem einzigen Read(). Dieses Verhältnis könnte bei nicht blockierenden E/A sogar noch höher sein (überlappende "nützliche" Verarbeitung während des Wartens auf die Platten-E/A).

Idealerweise würde ich einfach ein ushort [] Array als Array byte [] tarnen wollen, also könnte ich es direkt mit Read() füllen, oder irgendwie muss Read() das Array ushort [] direkt füllen:

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

Aber es gibt keine Read() Methode, die einen ushort [] Array erfolgt, nur ein byte [] -Array.

Kann dies direkt in C# erfolgen, oder muss ich nicht verwalteten Code oder eine Bibliothek eines Drittanbieters verwenden oder muss ich auf eine rechenintensive Konvertierung von Sample zu Sample zurückgreifen? Obwohl "sicher" bevorzugt wird, ist es in Ordnung, "unsicheren" Code zu verwenden, oder einen Trick mit Marshal, ich habe es noch nicht herausgefunden.

Danke für jede Anleitung!


[UPDATE]

Ich wollte etwas Code hinzufügen, wie von dtb vorgeschlagen, wie es scheint um sehr wenige Beispiele von Readarray zu sein. Dies ist ein sehr einfaches, ohne dass eine Fehlerüberprüfung angezeigt wird.

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

Die Daten werden sicher in Ihr übergebenes Array übertragen. Sie können auch einen Typ für komplexere Typen angeben. Es scheint in der Lage einfache Typen auf seinem eigenen zu schließen, aber mit dem Typspezifizierer, würde es so aussehen:

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

[UPATE2]

ich den Code von Bens wie vorgeschlagen hinzufügen wollte gewinnende Antwort, in "nackten Knochen" Form, ähnlich wie oben, zum Vergleich. Dieser Code wurde kompiliert und getestet und funktioniert und ist SCHNELL. Ich habe den SafeFileHandle-Typ direkt im DllImport (anstelle des üblicheren IntPtr) verwendet, um die Dinge zu vereinfachen.

[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); 
} 
+0

Wenn Sie 'BinaryReader.ReadUInt16();' nicht verwenden möchten, müssen Sie wahrscheinlich die Daten in ein Bytearray einlesen und dann das Bytearray verarbeiten. Selbst wenn Sie es in Stücke zerlegen, 100M 2-Byte-Stücke von Daten sind ~ 200MB, so sollten Sie in der Lage sein, das in Speicher auf einmal zu lesen, und verarbeiten Sie es. – Nate

+2

'fread' ist wahrscheinlich nicht zero-copy I/O, es ist gepuffert (alle' stdio.h' Funktionen können gepuffert werden und sind in den meisten Implementierungen). –

+0

Ben, gewährt dem Betriebssystem kann kopieren unter der Haube, aber es ist das zusätzliche Kopieren im Programm selbst, das ich versuchte zu vermeiden. – dale

Antwort

2

dtb's answer ist eine noch bessere Möglichkeit (tatsächlich hat es die Daten auch, keinen Gewinn dort kopieren), aber ich wollte nur darauf hinweisen, dass Werte aus einem Byte-Array zu extrahieren ushort Sie BitConverter verwenden sollten nicht BinaryReader

EDIT: Beispielcode für p/Aufruf 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

Ben, danke, ich habe mir BitConverter() angesehen, aber ich bin mir nicht sicher, ob ich deinen Vorschlag verstehe. BinaryReader() dient zum Lesen von Dateien (was ich gerade mache), und BitConverter() dient zur Umwandlung existierender byte [] - Arrays in andere Typen. Bin nicht BinaryReader(). ReadUInt16() entspricht dem Einlesen der Bytes in ein Array und dem Aufruf von BitConverter(). ToUInt16()? Vielleicht bin ich Missverständnis ... – dale

+0

Aber 'ReadUInt16' liest nur ein Element zu einer Zeit ... was eine lausige Art ist, I/O zu tun. –

+0

Nein, ist es nicht. BinaryReader ist verantwortlich für die Konvertierung der Bytes eines zugrunde liegenden Datenstroms in den angeforderten Typ, nicht die Bytes von IO an erster Stelle zu lesen. –

8

Sie können eine MemoryMappedFile verwenden. Nachdem Sie die Datei speicherabgebildet haben, können Sie eine Ansicht erstellen (d. H. Eine MemoryMappedViewAccessor), die eine ReadArray<T>-Methode bereitstellt.Diese Methode kann Strukturen aus der Datei ohne Marshalling lesen und funktioniert mit primitiven Typen ushort.

+2

Dies ist ein guter Ansatz, wenn Sie .NET 4 haben. Es hat sogar weniger kopieren als der C-Code, den Dale emulieren wollte. In älteren Versionen von .NET müssten Sie wahrscheinlich 'ReadFile' aufrufen, um den C-Code zu emulieren, oder p/invoke' CreateFileMapping' für diesen schnelleren Weg. –

+0

dtb, danke, ich hatte ReadArray() nicht gesehen, und sogar Google ist sich dessen noch nicht sehr bewusst! Es sieht wie ein sehr praktisches Werkzeug aus. Ich habe etwas Timing gemacht, und es ist etwa doppelt so schnell wie eine for() -Schleife mit ReadUInt16(), also vermute ich, dass es einige Kopien unter der Haube (das Lesen von Bytes ohne Konvertierung ist immer noch etwa 10x schneller). Ich sehe, dass die Accessor-Klasse viele ähnliche Methoden wie BinaryReader hat. Ich frage mich, ob MS schließlich eine ReadArray() - Methode zu BinaryReader hinzufügen könnte, dann könnten wir Strukturen direkt aus einem Stream lesen, ohne die Speicherzuordnung durchlaufen zu müssen. – dale

+0

Sie haben natürlich recht, da .NET-Metadaten im selben Speicherblock wie der Inhalt gespeichert sind, hat es keine andere Wahl als zu kopieren. Wenn Sie 'CreateFile' und' ReadFile' aufrufen, übergeben Sie einen Zeiger auf das erste Element Ihres 'ushort []' (erfordert unsicheren Code), sollten Sie die gleiche Geschwindigkeit wie beim Lesen eines 'byte []' bekommen. –

1

ich hier ein bisschen spät, um das Spiel sein könnte ... aber die schnellste Methode, die ich eine Kombination der vorherigen Antworten wurde mit gefunden .

Wenn ich Folgendes tun:

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

Das war ~ 2x schneller als die akzeptierte Antwort.

Für mich war die unsafe Methode die gleiche wie die Buffer.BlockCopy ohne die MemoryMappedFile. Die MemoryMappedFile Kürzung auf ein bisschen Zeit.