Liebe MyCshapr Community,
Ich bin beim Performance testen auf folgende Schwachstelle in meinem Programmcode gekommen
unsafe
{
int stride = m_videoWidth * 3;
//RtlMoveMemory(data.Scan0, pBuffer, stride);
byte* dst = (byte*)data.Scan0.ToPointer();
byte* src = (byte*)pBuffer.ToPointer();
for (int i = 0; i < this.m_videoHeight; i++)
{
byte* str = src + (this.m_videoHeight - i - 1) * stride;
for (int j = 0; j < stride; j++)
{
*(dst++) = *(str++);
}
}
}
Hat wer von euch eine Idee, wie ich diesen Codeblock beschleunigen könnte?
Im Speziellen verursacht "*(dst++) = *(str++)" den Performanceverlust (Laut Visual Studio bis zu 33% Programmweit O_O)
Vielen Dank
lg
Philipp
Hi PhilHol,
du drehst also das Bild um, richtig?
Nutze RtlMoveMemory zeilenweise!
Hier noch ein Link zu einer Diskussion über Block-Copy von IntPtr-Datenbereichen: stackoverflow: How can I copy unmanaged data in C# and how fast is it?
beste Grüße
zommi
Ha du bist gut .. Darauf hätte ich auch selber kommen können aufKopfklatsch
Von 33% auf 14,8% runter .. Danke dir =)
Sieht jetzt so aus:
unsafe
{
int stride = m_videoWidth * 3;
//RtlMoveMemory(data.Scan0, pBuffer, stride);
byte* dst = (byte*)data.Scan0.ToPointer();
byte* src = (byte*)pBuffer.ToPointer();
for (int i = 0; i < this.m_videoHeight; i++)
{
byte* str = src + (this.m_videoHeight - i - 1) * stride;
RtlMoveMemory((IntPtr)dst, (IntPtr)str, stride);
dst += stride;
}
}
Danke nochmals
lg
Phil
Hi PhilHol,
noch ein paar Bemerkungen:1.Gestalte die Parameter beim DllImport von RtlMoveMemory direkt als IntPtr, dann sparst du dir das Casten. 1.Die Zeilen eines Bilder müssen nicht unbedingt direkt hintereinander im Speicher liegen. Stride gibt den Byte-Unterschied zwischen zwei Zeilen-Anfängen an. Es ist daher auch der Schritt, den man gehen muss, um von einer Zeile zur nächsten zu kommen. (Manchmal heißt Stride auch Pitch oder WidthStep)
Dies ist jedoch nicht die Länge einer Pixel-Zeile in Bytes. Diese ergibt sich direkt aus "Width * BytesPerPixel"
Da Bilder meistens kompakt im Speicher gehalten werden, aber gleichzeitige ein effizienter Zugriff auf die einzelnen Zeilen(-anfänge) erfolgen soll, liegen die Zeilen schon hintereinander, aber nicht nahtlos! Nach Konvention unterliegen die Zeilen einem 32-Bit Alignment.
Deshalb wird jede Bildzeile um "sinnlose" Bytes ergänzt, damit die Byte-Anzahl durch 4 teilbar ist.
Dein erstes Bild scheint ein normales .NET-Bild zu sein, was sich an diese Konvention hält. Ob es dein zweites macht, kann ich nicht sehen/beurteilen. Jedenfalls könnten beide Bilder unterschiedliche Strides haben! Und auch wenn es keinen großen Unterschied macht, würde ich der Klarheit halber eben nur die eigentlichen Pixel-Daten kopieren und nicht dass Padding auch noch mit.
Mein Code würde daher wohl wie folgt aussehen (auch mit einer anderen Bezeichner-Konvention)
int inputStride = data.Stride;
int outputStride = ... ;
int bytesPerLine = VideoWidth * BytesPerPixel;
for (int y = 0; y < VideoHeight; y++)
{
int reverseY = (VideoHeight - 1) - y;
IntPtr inputLine = data.Scan0 + (reverseY * inputStride);
IntPtr outputLine = pBuffer + (y * outputStride);
RtlMoveMemory(outputLine, inputLine, bytesPerLine);
}
So entfällt auch das unsafe.
Falls du kein .NET 4.0 verwendest, müsstest du die Pointer-Arithmetik wohl anders machen, da es davor noch kein +Operator bei IntPtr gab. Da hilft aber: "intPtr + x" = "new IntPtr(intPtr.ToInt64() + x)"
beste Grüße
zommi
Puh,
Das ist ja mal eine gute Optimierung.
@1. OK das ist klar. Ehrlich gesagt war mit nicht bewusst, dass das direkte Casten große Probleme machen kann
@2. Wow .. OK SO genau wusste ich's dann auch nicht.
Aber es ging mit deinem Code gut (außer dass input + output vertauscht war)
Die Strides sind gleich groß, also konnte ich den Wert von data.Stride auch für den Output übernehmen.
Dein Code ist nochmal um ein Stück schneller als bisher ..
Das schlimmste ist jetzt das hochzählen von "y" in der for Schleife .. Wahnsinn ^^ .. Danke!
Da du dich scheinbar echt gut auskennst noch eine Frage, die du zufällig wissen könntest:
Im Prinzip lese ich ein Video aus und stelle aus zwei verschiedenen AVI-Streams die jeweiligen Bilder in einer Picturebox dar. Ab und zu bleibt das Bild einfach hängen beim schnellen durch-scrollen und ich brauch ca. 5Sekunden bis zu 1 Minute damit das Programm wieder reagiert ... Kann es da zu einem Memoryproblem kommen ?
Der gesamte Quelltext der Funktion sieht jetzt so aus, vllt kannst du das aus dem Quelltext heraus erkennen..
Vielen Dank auf jeden Fall !!! 😃
int ISampleGrabberCB.BufferCB(double sampleTime, IntPtr pBuffer, int bufferLen)
{
Bitmap bitmap = _bitmaps[(_currentBitmap + 1)%NUM_BITMAPS];
if (bitmap == null || bitmap.Height != VideoHeight || bitmap.Width != VideoWidth)
{
_bitmaps[(_currentBitmap + 1)%NUM_BITMAPS] = new Bitmap(VideoWidth, VideoHeight,
PixelFormat.Format24bppRgb);
bitmap = _bitmaps[(_currentBitmap + 1)%NUM_BITMAPS];
}
if (bufferLen == bitmap.Width * bitmap.Height * 3)
{
var r = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
BitmapData data = bitmap.LockBits(r, ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);
int inputStride = data.Stride;
int outputStride = data.Stride;
int bytesPerLine = VideoWidth*3;
for (int y = 0; y < VideoHeight; y++)
{
int reverseY = (VideoHeight - 1) - y;
IntPtr inputLine = pBuffer + (reverseY*inputStride);
IntPtr outputLine = data.Scan0 + (y*outputStride);
RtlMoveMemory(outputLine, inputLine, bytesPerLine);
}
bitmap.UnlockBits(data);
_currentBitmap++;
Changed(this, null);
}
return 0;
}
lg
Phil
Hi PhilHol,
mhhh... das könnte mit Memory-Problemen zu tun haben. Wenn du zu schnell die Bilder, Videos, etc wechselt, legst du vielleicht zu viele neue Bitmaps an.
Hast du mal die Speicherauslastung beobachtet? Steigt die an und fällt dann wieder?
Jedenfalls solltest du alte Bitmaps immer disposen! (Bitmap.Dispose())
Aber noch besser ist es eben überhaupt nicht so viele zu erzeugen.
Was soll das oben mit dem NUM_BITMAPS großen _bitmaps Array? Ist das eine Art Bitmap-Cache? Wie oft geht er denn dort in den if-Block hinein? Vielleicht das einfach mal mitzählen. Wenn du 2 AVIs in 2 Pictureboxen darstellst, warum sind es dann nicht einfach 2 Bitmaps?
beste Grüße
zommi
Hallo PhilHol,
im Übrigen solltest du besser keine Pictureboxen verwenden - siehe [Artikel] Einführung: Zeichnen Optimieren / Schnelles zeichnen bzw. Schnelle GDI(+) Grafik - wie? [Parallax Scrolling]
Hallo,
Ich habe nun herausgefunden, warum es manchmal hängt:
Das Bitmap, welches ich zum zeichnen verwende, wird über eine managed DirectX Methode aus einem AVI-Film gelesen (war nicht anders möglich) und dann über diese Schnittstelle ausgelesen.
Jetzt kann es aber manchmal sein, dass beim schnellen Scrollen das Image gerade gezeichnet wird, jedoch die API noch beim auslesen des Bitmaps aus dem AVI Film ist. Sprich sie greifen auf den gleichen Speicher zu.
Du hast recht mit deiner Vermutung, dass das _bitmap Array ein Bitmap Buffer ist (hat damals vor paar Jahre ein Kollege programmiert, den hab ich erst jetzt fragen können), damit man diesem Problem Herr wird. 100% funktionieren tut's aber nicht.
Meine weitere Frage zu dem Thema:
Gibt es eine Möglichkeit, den Image-Speicher bei schnellem Scrollen synchron zu halten?
Vielen Dank für die Inputs soweit
@Picturebox. Mittlerweile bin ich soweit, dass ich die Picturebox vererbe und in der vererbten Klasse das Bild direkt mit pe.Graphics.DrawImage() zeichne .. Ist das schneller? Oder gibt's da noch schnellere Möglichkeiten (native ? )
lg
Philipp
Hallo PhilHol,
DrawImage sollte unter GDI das schnellste sein, wie man ein Bild auf den Schirm kriegt. Allerdings würde ich dafür keine PictureBox nehmen. PictureBoxen sind aus meiner Sicht ausschließlich dazu da, das an PictureBox.Image zugewiesene Bild anzuzeigen. Wenn selbst gezeichnet werden soll, verwende ich immer Panel oder direkt das Form. Und es muss auch nicht gleich immer Vererbung sein. Eigentlich reicht ein EventHandler für das Paint-Event.
Siehe [Artikel] Zeichnen in Windows-Programmen
herbivore