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);
}
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
'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). –
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