为什么C#中的CRC32实现如此之慢?

时间:2009-06-03 16:15:13

标签: c# .net crc32

我正在使用以下函数计算VS2008,.NET 3.5项目中文件的CRC32:

public UInt32 ComputeHash(System.IO.Stream stream)
{
    unchecked
    {
        const int BUFFER_SIZE = 1024;

        UInt32 crc32Result = 0xFFFFFFFF;
        byte[] buffer = new byte[BUFFER_SIZE];
        int count = stream.Read(buffer, 0, BUFFER_SIZE);

        while (count > 0)
        {
            for (int i = 0; i < count; i++)
            {
                crc32Result = ((crc32Result) >> 8) ^ _crc32Table[(buffer[i]) ^ (crc32Result) & _LOOKUP_TABLE_MAX_INDEX];
            }
            count = stream.Read(buffer, 0, BUFFER_SIZE);
        }

        return ~crc32Result;
    }
}

为了简洁起见,我遗漏了构建查找表(_crc32Table)的函数。该表是一个UInt32数组,是在实例化类时构建的,包含256个值(256也是_LOOKUP_TABLE_MAX_INDEX + 1的值)。

我已经运行了一些基准测试,将它与MD5CryptoServiceProvider和SHA1CryptoServiceProvider ComputeHash函数进行比较,它们的速度要快得多。 MD5功能的速度提高了两倍,SHA1哈希的速度提高了约35%。我被告知CRC32很快,但那不是我所看到的。

我的假设是否错误?这是预期的还是这个算法存在缺陷?

4 个答案:

答案 0 :(得分:6)

您正在将代码与内置函数进行比较,并询问它们为何更快。您需要做的是找到内置函数的源代码。他们是如何工作的?看看有什么不同。

Betcha内置函数调用本机库并且不必在托管内存框架内运行就作弊。

答案 1 :(得分:3)

分析可能有助于确定IO调用(读取)与CRC计算所花费的时间。代码通常是IO绑定的。但是,作为在C#中实现了相当快的CRC功能的人,我可以给出一些指针,说明为什么它比MD5慢。

  • 你一次读取一个字节的内存。实现slicing-by-four,这样你就可以一次读取四个字节,或者可以按八个字节进行切片,这样你就可以一次读取8个字节(但如果代码实际运行在64-,则仅 位模式 - 您应该回退到32位模式下的四个切片,您应该使用if(sizeof(IntPtr)&lt; 8)或类似的方式进行测试。
  • 每个循环迭代处理一个字节,从而为每个字节支付循环开销。实现切片N或者考虑循环展开。 (两者都可能是不必要的。)
  • 每个字节会产生两个数组边界检查。您可以使用“不安全”代码来避免边界检查。对于不安全的代码,您还应确保对齐读取指针,尽管由于您只访问.NET数组,因此您可能认为它们已经与机器字大小对齐。请注意,不安全的代码是不安全的,所以要小心!
  • MD5被设计为一种非常快速的算法,并且没有上面列出的问题。它一次读取多个字节并并行处理它们,并以非托管代码实现。
  • 这是次要的,但你的循环结构不是最佳的。因为你知道count!= 0,所以do / while循环预先递减计数(即--count)并且比较为零比比较两个变量的for循环更好。使用您的代码,这将节省一些指令,并且可能每个字节读取一次内存。

如果实现切片为N,则将所有查找表打包到一个大表中,以便可以通过相同的指针访问它们。您也可以使用相同的表进行4个切片和8个切片。另请注意,典型的N切片实现假设特定的机器字节序,因此您可能需要一个单独的版本来支持big-endian机器,您可以使用BitConverter.IsLittleEndian检查它。

答案 2 :(得分:0)

也许:您在计算CRC吞吐量时是否计算查找表的计算?通常,查找表计算一次并缓存。如果不缓存它,则每次计算CRC时都会计算它。此外,如果您只测量一个CRC,那么您可能已将表计算成本包括在CRC计算成本中。最好测量每个哈希的多次迭代。


附录:当我测量时,当使用/ debug +和/ optimize-编译应用程序时,我看到了一个2.6倍的因子,比较你的CRC32和MD5哈希值。使用/ debug-和/ optimize +,我看到了1.6倍的因子。当我更改编译标志时,绝对MD5性能没有变化。没有调试,CRC仍然较慢,但它更接近。

答案 3 :(得分:0)

我不太熟悉在执行此代码时自动执行的优化,但如果分析不适合您,则有几个选项。

我建议尝试使用不安全的代码,并使用指针算法进行缓冲区[i]和_crc32Table查找,以防它尚未优化。

我可以看到您遇到性能问题的另一个地方是Stream.Read调用。您是否尝试过不同的BUFFER_SIZE值?

如果没有自动优化,使用较大的字节缓冲区并可能执行某些手动loop unrolling也可以帮助您。