2012-01-20 12 views
10

Zoptymalizowałem metodę rozszerzenia, aby porównać dwa strumienie dla równości (bajt po bajcie) - wiedząc, że jest to gorąca metoda, którą starałem się zoptymalizować w miarę możliwości (strumienie może osiągnąć wielomagabajtowe długości). I w istocie wpadł następującej metody:Jeśli komunikat Prawdziwy blok został wykonany, gdy warunek jest fałszywy

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

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

/// <summary> 
/// Compares two streams for byte-by-byte equality. 
/// </summary> 
/// <param name="target">The target stream.</param> 
/// <param name="compareTo">The stream to compare the target to.</param> 
/// <returns>A value indicating whether the two streams are identical.</returns> 
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; 

       // Shrink the number of comparisons. 
       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; 
      } 
     } 
    } 
} 

Problemem jest to, że blok convert1.UInt64[i] != convert2.UInt64[i]if (powrót false) jest oceniany nawet wtedy, gdy wartości są równe. Sprawdziłem każdy indywidualnie, a następnie sprawdziłem wynik "nie jest równy". Jestem w czystej niedowierzaniem:

Values are not equal

nie mam pomieszane ze wskaźnikiem instrukcji - w ten sposób kod wykonywany i kołek zegarek jest na żywo.

Jakieś pomysły, jak to może się stać?

+0

czy działa, jeśli dodasz {} – rerun

+1

wygląda jak porównanie odniesienia (różne obiekty, zawsze fałszywe) ma miejsce zamiast porównywania wartości – Alex

+0

Jestem zdezorientowany, obie właściwości struktury mają FieldOffset 0, skąd wiesz, że "Porównujesz jabłka i jabłka? – mtijn

Odpowiedz

11
for (var i = 0; i < (len/8) + 1; i++) 

Ogólnie rzecz biorąc debugger ma problemy z połączeniem, nie może wyświetlać zawartości tablicy, gdy ją wypróbuję. Ale głównym problemem jest bez wątpienia +1 w wyrażeniu końcowym for(). To indeksuje tablicę poza jej ostatnim elementem, gdy jest ona podzielna przez 8. Środowisko wykonawcze nie może uchwycić tego błędu, nakładanie się tablic powoduje, że właściwość Length ma fałszywą wartość. Co dzieje się dalej to niezdefiniowane zachowanie, czytasz bajty, które nie są częścią tablicy. Rozwiązaniem jest wydłużenie tablicy o 7 bajtów.

Ten rodzaj kodu nie jest dokładnie optymalizacją, czytanie i porównywanie uint64 na 32-bitowej maszynie jest drogie, szczególnie gdy tablica nie jest poprawnie wyrównana. Około 50% szans na to. Lepszym pułapka jest użycie funkcji C Runtime memcmp(), dostępne na każdym komputerze z systemem Windows:

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

i używać go tak:

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; 

Czy porównywać perf tego z równiną dla(), która porównuje bajty. Ostatnią przepustnicą jest tutaj przepustowość magistrali pamięci.

+0

Dzięki Hans. Myślałem o umieszczeniu +1 w wywołaniu "Max".Odpowiedziałeś na moje pytanie, przeszedłeś poza nie i dostarczyłeś jeszcze lepszej optymalizacji. Dobrze zasłużony kleszcz! –

1

Takie problemy to często problemy ze zrozumieniem działania optymalizacji. Ta linia kodu mogłaby zostać bardzo dobrze wykonana, ponieważ obie zwrotne klauzule błędne są łączone w jeden zestaw instrukcji na niższym poziomie. Inną przyczyną takich problemów jest sytuacja, w której architektura, na której się znajdujesz, pozwala na wykonanie warunkowe, w którym pewne polecenia są trafiane w debugerze, ale wyniki nigdy nie są zatwierdzane do rejestrów na poziomie architektury.

Sprawdź, czy kod działa najpierw w trybie debugowania. Następnie, gdy jesteś przekonany, że wynik jest taki sam jak w trybie zwolnienia, spójrz na podstawowe instrukcje, aby dowiedzieć się optymalizacji kompilatora pod ręką.

+0

Kod jest obecnie debugowany - mimo to zakładam, że JITter nie zoptymalizuje tego w sposób, który mógłby go złamać. Zbadam demontaż i zobaczę, co się dzieje pod kołdrą. –

Powiązane problemy