C# 为什么C语言中的CRC32实现如此缓慢?

C# 为什么C语言中的CRC32实现如此缓慢?,c#,.net,crc32,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

我正在使用以下函数计算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;
    }
}
public UInt32计算哈希(System.IO.Stream)
{
未经检查
{
const int BUFFER_SIZE=1024;
UInt32 crc32Result=0xFFFFFFFF;
字节[]缓冲区=新字节[缓冲区大小];
int count=stream.Read(缓冲区,0,缓冲区大小);
而(计数>0)
{
for(int i=0;i>8)^ crc32Table[(缓冲区[i])^(crc32Result)&查找表最大索引];
}
count=stream.Read(缓冲区,0,缓冲区大小);
}
返回~crc32Result;
}
}
为了简洁起见,我省略了构建查找表的函数(_crc32Table)。该表是UInt32的一个数组,在实例化类时生成,包含256个值(256也是_LOOKUP_table_MAX_INDEX+1的值)

我运行了一些基准测试,将其与MD5CryptoServiceProvider和SHA1CryptoServiceProvider ComputeHash函数进行比较,它们的速度要快得多。MD5函数的速度是前者的两倍多,SHA1散列的速度大约快35%。我被告知CRC32速度很快,但这不是我看到的


我的假设错了吗?这是意料之中的还是该算法中存在缺陷?

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



附录:当我测量时,我看到了一个2.6倍的系数,将你的CRC32与MD5散列进行比较,当应用程序使用/debug+和/optimize-编译时。使用/debug-和/optimize+,我看到了1.6倍的系数。当我更改编译标志时,MD5的绝对性能没有变化。在没有调试的情况下,CRC的速度仍然较慢,但距离更近。

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


Betcha内置函数调用本机库,并通过不必在托管内存框架内运行来作弊

我不太熟悉在执行此代码时自动执行的优化,但是如果评测不适合您,您可以选择几个选项

我可以建议尝试使用不安全的代码,并使用指针算法进行缓冲区[I]和表查找,以防尚未对其进行优化

我唯一能看到您遇到性能问题的地方是Stream.Read调用。您是否尝试过不同的缓冲区大小值


如果没有自动优化,使用较大的字节缓冲区并可能执行一些手动操作也可以帮助您解决问题。

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

  • 您正在一次读取一个字节的内存。实现时,您可以一次读取四个字节,或者可以按八个字节进行切片,以便一次读取八个字节(但仅当代码实际在64位模式下运行时-您应该返回到32位模式下按四个字节进行切片,您应该使用if(sizeof(IntPtr)<8)或类似方法进行测试)
  • 每个循环迭代处理一个字节,因此为每个字节支付循环开销。实现SLIXEN BN或考虑循环展开。(可能没有必要同时执行这两项操作。)
  • 每个字节将进行两次数组边界检查。您可以使用“不安全”代码来避免边界检查。对于不安全的代码,您还应该确保对齐读取指针,尽管由于您只访问.NET数组,您可能会认为它们已经与机器字大小对齐。注意,不安全的代码是不安全的,所以要小心
  • MD5被设计成一个非常快速的算法,没有上面列出的问题。它一次读取多个字节并并行处理它们,并在非托管代码中实现
  • 这是次要的,但您的循环构造不是最佳的。既然你认识伯爵0时,预递减count(即--count)并将其比较为零的do/while循环优于比较两个变量的for循环。对于您的代码,这将节省一些指令,并且可能会节省每个字节的内存读取

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

正如我在问题中所说,“表是UInt32的数组,在实例化类时生成……”。该表在散列开始之前就存在了。当然,但是是否为每个散列重新计算该表?它是静态表还是实例表。您是否计算哈希的多次迭代并平均响应时间?或者只有一个。如果它是一个实例变量,并且您为每个计算的Crc32创建了一个新的Crc32类实例,那么您每次都要支付初始成本。如果您只测量一个散列,那么您需要支付初始成本。不?我明白你的意思。直到类实例化之后才开始计时,因此表创建时间不是一个因素。即使它包含在时间计算中