| 

.NET C# Java Javascript Exception

2
Hallo,

ein (durch 64 teilbares) byte-Array (Länge: 1024 * 1024 * 128) soll blockweise (je 64 Bit) sehr schnell und natürlich sicher kopiert werden.

Bedingungen:

- GC darf nicht dazwischen funken
- Security soll nicht gering sein
- es soll die schnellste Lösung sein, die mom. verfügbar ist

Meine folgend dargestellte Lösung scheint allen Bedingungen Stand zu halten und läuft (unsafe) sehr gut. Buffer.BlockCopy war auf meinem PC langsamer - folglich scheidet auch Array.Copy aus, weil das (in diesem Fall) noch langsamer zu sein scheint.


internal static unsafe void Copy_2(byte[] src, int srcOffset, byte[] dest, int destOffset, int count)
{
if ((src == null) || (dest == null))
throw new System.ArgumentException();

if ((srcOffset < 0) || (destOffset < 0) || (count < 0))
throw new System.ArgumentException();

if ((src.Length - srcOffset < count) || (dest.Length - destOffset < count))
throw new System.ArgumentException();

fixed (byte* psrc = src, pdest = dest)
{
byte* ps = psrc + srcOffset, pt = pdest + destOffset;
int i = 0;

while(i++ < count)
{
*pt = *ps;
pt++; ps++;
}
}
}



Ich würde mich freun, wenn jemand eine noch schnellere Version hat, aber auch über Kritik an meiner Lösung ist mir sehr gelegen. Ev. lässt sich meine Lösung steigern!?

Vielen Dank für eure Mühe

Falkner

Update

Ich habe jetzt meine ursprüngliche Methode um eine zus. Methode erweitert u. beide in die Klasse "Copy1" gepackt. In der Klasse "Copy2" sind ebenfalls 2 Methoden, die den selben Zweck erfüllen, diese wurden aber mit "MoveMemory" bestückt.

Beide Klassen wurden extremen Tests ausgesetzt. Ein Zeitvergleich zeigte, dass die Klasse "Copy1" (also Aufbau nach meiner ursprümglichen Methode) ca. 1/4 schneller war als "Copy2". Das Ergebnis zeigte sich bei beiden Methoden.

Falls jemand das nachvollziehen möchte, hier nachfolgend die zwei Klassen. Die Frage nach einer schnelleren Lösung ist akso damit nicht vom Tisch.

Klasse: Copy1
using System;

namespace TestCopy
{
internal static class Copy1
{
/*Komplettes Array wird kopiert*/
internal static unsafe void Copy(byte[] src, byte[] dest)
{
if ((src == null) || (dest == null) )
throw new System.ArgumentException("Parameter cannot be null", "src or dest");
if (src.Length != dest.Length)
throw new System.ArgumentException("Arrays are different sizes", "src, dest");

fixed(byte* pSrc = src, pDest = dest)
{
byte* ps = pSrc, pd = pDest;
int i = 0;

while(i++ < src.Length)
{
*pd = *ps;
pd++; ps++;
}
}
}

/*Kopiert einen Block über Positionsangaben*/
internal static unsafe void Copy(byte[] src, int srcOffset, byte[] dest, int destOffset, int count)
{
if ((src == null) || (dest == null))
throw new System.ArgumentException("Parameter cannot be null", "src or dest");

if ((srcOffset < 0) || (destOffset < 0) || (count < 0))
throw new System.ArgumentException("Parameter cannot be < 0", "srcOffset, destOffset or count");

if ((src.Length - srcOffset < count) || (dest.Length - destOffset < count))
throw new System.ArgumentException();

fixed (byte* psrc = src, pdest = dest)
{
byte* ps = psrc + srcOffset, pt = pdest + destOffset;
int i = 0;

while(i++ < count)
{
*pt = *ps;
pt++; ps++;
}
}
}
}
}


Klasse: Copy2

using System;
using System.Runtime.InteropServices;

namespace TestCopy
{
internal static class Copy2
{
[DllImport("Kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = false)]
static extern void MoveMemory(IntPtr dest, IntPtr src, int size);

/*Komplettes Array wird kopiert*/
internal static unsafe void Copy(byte[] src, byte[] dest)
{
if ((src == null) || (dest == null) )
throw new System.ArgumentException("Parameter cannot be null", "src or dest");
if (src.Length != dest.Length)
throw new System.ArgumentException("Arrays are different sizes", "src, dest");

fixed(byte* pSrc = src, pDest = dest)
{
IntPtr psrc = new IntPtr((void *) pSrc), pdest = new IntPtr((void *) pDest);
MoveMemory(pdest, psrc, src.Length);
}
}

/*Kopiert einen Block über Positionsangaben*/
internal static unsafe void Copy(byte[] src, int srcOffset, byte[] dest, int destOffset, int count)
{
if ((src == null) || (dest == null))
throw new System.ArgumentException("Parameter cannot be null", "src or dest");

if ((srcOffset < 0) || (destOffset < 0) || (count < 0))
throw new System.ArgumentException("Parameter cannot be < 0", "srcOffset, destOffset or count");

if ((src.Length - srcOffset < count) || (dest.Length - destOffset < count))
throw new System.ArgumentException();

fixed(byte* pSrc = src, pDest = dest)
{
byte* ps = pSrc + srcOffset, pd = pDest + destOffset;
IntPtr psrc = new IntPtr((void *) ps), pdest = new IntPtr((void *) pd);

MoveMemory(pdest, psrc, count);
}
}
}
}
04.11.2013
Falkner 795 3 9
Falkner 795 3 9
Ich verstehe das Problem nicht ganz: Wenn du ohnehin mit 'unsafe' code arbeitest, warum gehst du nicht einen Schritt weiter und verwendest die RtlMoveMemory Api (siehe: http://www.pinvoke.net/default.aspx/kernel32/movememory.html)?
nabuchodonossor 04.11.2013
@nabuchodonossor: Dank f. d. Link, ich habe aber keine Ahnung, wie das helfen soll. Dort steht:

[DllImport("Kernel32.dll", EntryPoint="RtlMoveMemory", SetLastError=false)]
static extern void MoveMemory(IntPtr dest, IntPtr src, int size);

Ich benötige zum. Positionsangaben. Habe ich da was falsch verstanden?
Falkner 04.11.2013
@nabuchodonossor: Gerade habe ich die nicht akzeptable SOFTWARE LICENSE eingesehen. Pinvoke scheidet def. aus!
Falkner 04.11.2013
@Falkner: Was für eine SOFTWARE LICENSE??? P/Invoke ist ein Standard-.NET-Feature, und kernel32.dll/MoveMemory ist Teil der Windows-API.

Hast Du da vielleicht etwas mit dem PInvoke Visual Studio Add-in verwechselt? Das brauchst Du nicht, um mit P/Invoke zu programmieren. Es erspart Dir nur das Nachschlagen auf der pinvoke.net-Webseite oder in der MSDN.

Und den Vorschlag von @nabuchodonossor würde ich mir definitv mal ansehen. Es gibt in MoveMemory zwar keine Parameter für offsets in die Arrays, aber es sollte möglich sein, die beiden IntPtr gleich inklusive offset zu initialisieren.
Matthias Hlawatsch 05.11.2013
@nabuchodonossor, @Matthias Hlawatsch: Mein Fehler, alles zurück!!! Ich hatte da tatsächlich was verwechselt. Danke für den Hinweis! Ich werde mich im Verlauf d. Woche da mal etwas vertiefen.
Falkner 05.11.2013
2 Antworten
1
Schau Dir mal diesen Beitrag an:

http://nadeausoftware.com/articles/2012/05/c_c_tip_how_copy_memory_quickly

Dein Ansatz scheint dort der Methode 2 "Loop with pointers" zu entsprechen.

Was ich diesem Beitrag entnehme:

  • Der erzielbare Durchsatz hängt von der Array-Größe ab.
  • memcpy() ist am schnellsten, jedenfalls für die meisten Array-Größen.
  • Compiler-Optimierungen spielen eine große Rolle


Was heißt das für Dein Problem und Deine, gerade in Bezug auf memcpy()/CopyMemory abweichenden, Testergebnisse?

  • Versuche zu ergründen, warum Du auf andere Werte kommst. Möglicherweise gibt es bei Dir besondere Rahmenbedingungen. Wenn Du die kennst, kannst Du sie auch gezielt in Deiner Lösung berücksichtigen. Eventuell stimmt aber auch Dein Testaufbau nicht. Oder - mein Hauptverdacht - die von Dir gewählte Blockgröße ist so klein, daß die (vielen) Aufrufe von managed nach unmanaged mehr Zeit verbrauchen als Du durch die Bibliotheksfunktion gewinnst. In dem Beitrag gab es die besten Ergebnisse mit 16KB, Du verwendest nur 64 Bit.
  • Eine "Meta-Lösung" für Dein Problem könnte sein, die komplette Aufgabe in unmanaged Code zu verlagern (also eine externe Funktion, die Du nur einmal aufrufen mußt). Diesen Code kannst Du optimieren, so weit es irgendwie geht.
11.11.2013
Matthias Hlawatsch 13,2k 4 9
Vielen Dank f. den Link. Ich werde mich morgen darin vertiefen. Mein Testaufbau scheint OK zu sein u. die Messung ist auch korrekt. Ich werde das aber auch noch mal mit einem anderen Rechner durchprobieren.
Falkner 11.11.2013
Die verwendeten Testgr. d. Arrays sind 0x8000000 u. 0xC000000. Blocksize ist 8 und nRounds ist Array/ Blocksize. Ich suche immer noch die Stelle, an d. ich einen Fehler gemacht haben könnte, ohne Erfolg. Das Resultat bleibt - meine Urlösung ist die schnellste gemessene Lösung bei kl.und gr.Arrays. Gemessen wurde, getrennt f. jeden Vorgang, m. StopWatch.
Falkner 11.11.2013
Das meine ich ja: warum nimmst Du so eine winzige Blocksize? Das macht 1-2 Millionen Aufrufe auf Copy/MoveMemory. Hast Du mal (viel!) größere Blöcke probiert?
Matthias Hlawatsch 11.11.2013
Das sind eben die Blöcke, mit denen das geplante Tool arbeiten wird (in der Frage wurde das aber auch erwähnt). Was nicht daraus hervorgeht, mein Fehler(!), das Tool wird enorm vielen Copy-Aktionen ausgesetzt sein.
Falkner 11.11.2013
Zusatz: Die Daten kommen aus einer Datei. Optimale Einleseblöcke scheinen bei 1024*1024*192 (x32) zu liegen. Das ist zumindest ein Grund f. die "Winzigkeit".
Falkner 11.11.2013
@Matthias Hlawatsch, @nabuchodonossor, @ffordermaier: Da keine schnelleren Umsetzungen mehr bekannt wurden, gehe ich davon aus, dass meine ursprüngliche Lösung eine der schnellsten ist und werde diese m. Optimierung verwenden.

Ich bedanke mich bei allen, die hier mitgeholfen haben. Jeder Vorschlag war für mich nützlich. Um das Thema abzuschliessen, gebe ich den Haken einfach b. Matthias Hlawatsch.

Schönen Tag
Falkner 13.11.2013
1
Da ich zugegebenermassen das Probem nicht gesehen habe, hier nochmal die Annahmen, die zu meinem Vorschlag von RtlMoveMemory führen:

1) Es sollen Daten möglichst rasch aus einem Byte Array in ein anderes kopiert werden.
2) Es müssen nicht zwangsläufig alle Daten aus dem Quell Array gelesen werden.

Daher meine Idee für die Vorgehensweise: Das Zielarray in der gewünschten Grösse anlegen, dan mit der API Funktion beginnend an Quell-Adresse auf Ziel-Adresse kopieren. Quell-Adresse errechnet sich aus Adresse des 0. Elements des Quellarrays + Offset.

Zu Zeiten von VB6 habe ich mir damit eine StringBuilder ähnliche Klasse gebaut ... hat ziemlich gut funktioniert.
06.11.2013
nabuchodonossor 1,3k 5
Vielen Dank für deine Ausführlichkeit. Zum Problem: byte-Blöcke m. vorbestimmter Grösse sollen aus einem Array entnommen werden u. manipuliert werden. Mit dieser Vorgehensweise wird das ganze source-Array (block f. block) durchlaufen. Konvertierung ist nicht erforderlich, weil nur bytes eine Rolle spielen. Und das Problem ist die Geschwindigkeit; im Prog. müssen div. "Riesen-Arrays" derart durchlaufen werden - das kostet! Rein zeittechnisch werde ich erst z. WE deinen Ansatz testen können. Feedback wird es geben! Noch einmal vielen Dank.
Falkner 06.11.2013
Du kannst beliebig grosse Blöcke kopieren, also alles nur mit einem Aufruf von RtlMoveMemory.
nabuchodonossor 06.11.2013
Gerade habe ich movememory probiert:


[DllImport("Kernel32.dll", EntryPoint="RtlMoveMemory", SetLastError=false)]
static extern void MoveMemory(IntPtr dest, IntPtr src, int size);

internal static unsafe void Copy(byte[] src, byte[] dest, int count)
{
fixed(byte* pSrc = src, pDest = dest)
{
IntPtr psrc = new IntPtr((void *) pSrc), pdest = new IntPtr((void *) pDest);
MoveMemory(pdest, psrc, count);
}
}

Wie lege ich aber nun die Position fest (s. mein Methodenkopf oben)?
Falkner 10.11.2013
1
Mit herkömmlicher Pointerarithmetik, genauso wie Du es in Deiner ursprünglichen Methode auch gemacht hast. Also z.B. 'pSrc + srcOffset' um innerhalb des Source-Arrays auf die Postion srcOffset zu zeigen. Ebenso musst Du den Pointer für dest vorbereiten. Danach übergibst Du der MoveMemory Funktion zwei Pointer, die bereits auf das jeweils gewünschte Offset zeigen und den gewünschten count.
ffordermaier 10.11.2013
@ffordermaier: Das geht nicht, weil die betreffenden Pointer fixed sind. Bin hier aber noch sehr skeptisch, ob movememory tatsächlich schneller als meine Ursprungsmethode sein wird. Werde heute abend mal testen. Hab das jetzt so gemacht(s. nächster Kommentar):
Falkner 10.11.2013
internal static unsafe void Copy3(byte[] src, int srcOffset, byte[] dest, int destOffset, int count)
{
fixed(byte* pSrc = src, pDest = dest)
{
byte* ps = pSrc + srcOffset, pd = pDest + destOffset;
IntPtr psrc = new IntPtr((void *) ps), pdest = new IntPtr((void *) pd);

MoveMemory(pdest, psrc, count);
}
}
Falkner 10.11.2013
@All: Ich habe meiner Frage ein Update zugefügt.
Falkner 10.11.2013
@Falkner: mir ist gerade erst aufgefallen, dass es neben MoveMemory auch noch CopyMemory gibt. Letzteres ist schneller, erfordert aber dafür, dass Quell- und Zielbereich nicht überlappen. Wenn das bei Dir gegeben ist, dann probier das mal aus. (Und wenn es nicht gegeben ist, dürfte Dein eigener Code Probleme machen...)
Matthias Hlawatsch 10.11.2013
@Matthias Hlawatsch: Ich habe zwar Absicherungen in die Demo-Classes eingebaut, natürlich reicht das f. e. Software nicht - so war das nicht gemeint! Und auf ev. Überlappungen kann man doch reagieren! Ich sehe da keine Probleme. Ich pers. arbeite immer m. korrekten Quell- u. Zielbereichen. Vielen Dank f. CopyMemory das probiere ich die kommende Woche aus. Allerdings bin ich auch hier skeptisch, was das Ergebnis angeht. Zus. werde ich mal in alten C-Tutorials buddeln. Ev. findet sich da ein Ansatz.
Falkner 10.11.2013
@Matthias Hlawatsch: Meine Skepsis was begründet - CopyMemory hat sich als langsamste Lösung entpuppt. Ich hatte das angenommen, weil es wohl von memCopy zu stammen scheint. Meine Lösung folgt im nächsten Kommentar.
Falkner 11.11.2013
[DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
public static extern void CopyMemory(IntPtr dest, IntPtr src, uint count);


internal static unsafe void Copy(byte[] src, int srcOffset, byte[] dest, int destOffset, uint count)
{
fixed(byte* pSrc = src, pDest = dest)
{
byte* ps = pSrc + srcOffset, pd = pDest + destOffset;
IntPtr psrc = new IntPtr((void *) ps), pdest = new IntPtr((void *) pd);

CopyMemory(pdest, psrc, count);
}
}
Falkner 11.11.2013
Strange. CopyMemory sollte eigentlich schneller sein als MoveMemory, es fallen ja ein paar Checks weg.
Ich hab ein paar Gedanken noch in eine eigene Antwort geschrieben.
Matthias Hlawatsch 11.11.2013

Stelle deine .net-Frage jetzt!