2012-01-20 39 views
10

我优化了一个扩展方法来比较两个流是否相等(byte-for-byte) - 知道这是一个热门的方法,我试图尽可能优化它(流可以达到几兆字节的长度)。我基本上是想出了以下方法:If Condition True Block当条件为False

[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; 
      } 
     } 
    } 
} 

的问题是,convert1.UInt64[i] != convert2.UInt64[i]if块(返回false),甚至当值相等正在评估。我单独检查了每一项,然后检查了“不等于”的结果。 我在纯怀疑

Values are not equal

我还没有与指令指针乱 - 这是怎样的代码执行和钟表销是活。

任何想法如何发生?

+0

如果你添加{ – rerun

+1

看起来像一个参考比较(不同的对象,总是false)正在发生,而不是一个值比较 – Alex

+0

我很困惑,两个结构属性都有FieldOffset 0,你怎么知道你比较苹果和苹果? – mtijn

回答

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

调试器通常很难与此联合,它在尝试时无法显示阵列内容。但是核心问题无疑是for()最终表达式的+1。当索引数组超过其最后一个元素时,运算符不能捕获这个错误,重叠数组会导致Length属性具有伪造值。接下来发生的是未定义的行为,您正在读取不属于数组的一部分的字节。解决方法是使数组长度增加7个字节。

这种类型的代码并不完全是一种优化,在32位机器上读取和比较uint64代价很高,特别是当数组未正确对齐时。大约有50%的可能性。一个更好的捕鼠器是使用任何Windows计算机上的C运行时memcmp()函数,可得:

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

而且使用这样的:

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; 

做比较的这个PERF与普通的()循环比较字节。这里的最终节流是内存总线带宽。

+0

感谢汉斯。我想过要在“Max”电话中放置+1。你已经回答了我的问题,并且走得更远,并为我提供了更好的优化。当之无愧! –

1

这样的问题通常与理解优化如何工作有关。这行代码可能会被执行,因为这两个返回假子句组合成一组指令在较低级别。类似问题的其他原因是,如果您所在的体系结构允许在调试器中点击某些指令的条件执行,但结果永远不会提交给架构级别的寄存器。

确认代码首先在调试模式下工作。然后,当您确信结果与发布模式相同时,请查看底层指令以找出手头的编译器优化。

+0

该代码目前正在调试 - 即使如此,我也会假设JITter不会以一种会破坏它的方式优化它。我将检查装配,看看封面下面发生了什么。 –

相关问题