10 Stimmen

Wenn die Aussage wahr ist, wird der Block ausgeführt, wenn die Bedingung falsch ist

Ich habe eine Erweiterungsmethode optimiert, um zwei Streams auf Gleichheit (Byte für Byte) zu vergleichen - da dies eine wichtige Methode ist, habe ich versucht, sie so weit wie möglich zu optimieren (die Streams können eine Größe von mehreren Megabytes haben). Ich bin im Grunde auf den folgenden Ansatz gekommen:

[StructLayout(LayoutKind.Explicit)]
struct Converter
{
    [FieldOffset(0)]
    public Byte[] Byte;

    [FieldOffset(0)]
    public UInt64[] UInt64;
}

/// 
/// Vergleicht zwei Streams Byte für Byte auf Gleichheit.
/// 
/// Der Zielstream.
/// Der Stream, mit dem das Ziel verglichen werden soll.
/// Ein Wert, der angibt, ob die beiden Streams identisch sind.
public static bool CompareBytes(this Stream target, Stream compareTo)
{
    if (target == null && compareTo == null)
        return true;
    if (target == null || compareTo == null)
        return false;
    if (target.Length != compareTo.Length)
        return false;
    if (object.ReferenceEquals(target, compareTo))
        return true;
    if (!target.CanRead || !target.CanSeek)
        throw new ArgumentOutOfRangeException("target");
    if (!compareTo.CanRead || !compareTo.CanSeek)
        throw new ArgumentOutOfRangeException("target");
    lock (target)
    {
        lock (compareTo)
        {
            var origa = target.Position;
            var origb = compareTo.Position;
            try
            {
                target.Position = compareTo.Position = 0;

                // Reduziere die Anzahl der Vergleiche.
                var arr1 = new byte[4096];
                var convert1 = new Converter() { Byte = arr1 };
                var arr2 = new byte[4096];
                var convert2 = new Converter() { Byte = arr2 };

                int len;
                while ((len = target.Read(arr1, 0, 4096)) != 0)
                {
                    if (compareTo.Read(arr2, 0, 4096) != len)
                        return false;
                    for (var i = 0; i < (len / 8) + 1; i++)
                        if (convert1.UInt64[i] != convert2.UInt64[i])
                            return false;
                }

                return true;
            }
            finally
            {
                target.Position = origa;
                compareTo.Position = origb;
            }
        }
    }
}

Das Problem ist, dass der convert1.UInt64[i] != convert2.UInt64[i] if-Block (Rückgabe von false) auch dann bewertet wird, wenn die Werte gleich sind. Ich habe sie einzeln überprüft und dann das Ergebnis des 'Nichtgleich'-Zustands überprüft. Ich kann es kaum glauben:

Werte sind nicht gleich

Ich habe nicht mit dem Instruktionszeiger herumgespielt - so wurde der Code ausgeführt und der Watch-Pin ist aktiv.

Ideen dazu, wie dies passieren könnte?

11voto

Hans Passant Punkte 894572
  for (var i = 0; i < (len / 8) + 1; i++)

Der Debugger hat im Allgemeinen Schwierigkeiten mit diesem Union, er kann den Array-Inhalt nicht anzeigen, wenn ich es versuche. Aber das Hauptproblem ist zweifellos die +1 im Endausdruck des for(). Das indexiert den Array über sein letztes Element hinaus, wenn len durch 8 teilbar ist. Die Laufzeit kann diesen Fehler nicht erkennen, das Überlappen der Arrays führt dazu, dass die Eigenschaft Length einen falschen Wert hat. Was als nächstes passiert, ist ein undefiniertes Verhalten, Sie lesen Bytes, die nicht zum Array gehören. Ein Workaround ist, das Array um 7 Bytes zu verlängern.

Diese Art von Code ist nicht unbedingt eine Optimierung, das Lesen und Vergleichen von uint64 auf einer 32-Bit-Maschine ist teuer, insbesondere wenn das Array nicht korrekt ausgerichtet ist. Etwa 50% Wahrscheinlichkeit dafür. Eine bessere Lösung ist die Verwendung der C-Laufzeitfunktion memcmp(), die auf jeder Windows-Maschine verfügbar ist:

    [DllImport("msvcrt.dll")]
    private static extern int memcmp(byte[] arr1, byte[] arr2, int cnt);

Und verwenden Sie es so:

    int len;
    while ((len = target.Read(arr1, 0, 4096)) != 0) {
        if (compareTo.Read(arr2, 0, 4096) != len) return false;
        if (memcmp(arr1, arr2, len) != 0) return false;
    }
    return true;

Vergleichen Sie die Leistung davon mit einer einfachen for() Schleife, die Bytes vergleicht. Der ultimative Engpass hier ist die Bandbreite des Speicherbusses.

1voto

Mark Smith Punkte 1057

Probleme wie diese sind in der Regel Probleme mit dem Verständnis, wie Optimierungen funktionieren. Diese Codezeile könnte sehr gut ausgeführt werden, weil beide Rückgabefalse-Klauseln auf niedrigerer Ebene zu einem Satz Anweisungen kombiniert sind. Andere Ursachen für Probleme wie diese sind, wenn die Architektur, auf der Sie sich befinden, bedingte Ausführung zulässt, bei der bestimmte Anweisungen im Debugger ausgeführt werden, aber die Ergebnisse auf Architekturebene nie in Register übernommen werden.

Stellen Sie zunächst sicher, dass der Code im Debugmodus funktioniert. Wenn Sie davon überzeugt sind, dass das Ergebnis im Release-Modus gleich ist, betrachten Sie die zugrunde liegenden Anweisungen, um die Compiler-Optimierung zu verstehen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X