在C#中使用SSE 4.2 crc32算法?这是否可行?

4
我需要在大量文件上计算crc32,包括巨大的文件(几GB)。我尝试了网上找到的几个算法,例如Damieng这个,它们可以工作,但速度很慢(超过1分钟)。
我发现这个基准测试中有各种crc32算法,并发现sse 4.2有硬件加速的crc32方法。
我没有找到任何使用SSE crc32代码的c#示例。
1 - 是否可能?
2 - 如何检测当前cpu是否启用了SSE4.2?(以切换crc32方法)
(如果可能,请提供代码示例)

2
你对代码进行了性能分析吗?我严重怀疑你的问题不是由于缓慢的CRC算法引起的,更有可能是读取数GB数据时的I/O开销。 - James
@James 确实,要么你可以利用带有指针优化的 unsafe 代码区域来获得一些好处 - 尽管这只是猜测而已 :-) - Adam Houldsworth
2
计算几GB文件的CRC32需要读取整个文件...并按顺序处理它。尝试测量仅读取整个文件(并添加字节或32位块,以避免系统优化读取)需要多长时间,将其与CRC32计算进行比较。 - Paŭlo Ebermann
密码学标签连同CRC32真的很可怕,我真的希望这是一个错误(如果是这样,请移除标签),否则你有更多的问题,而不仅仅是慢计算。 - Bruno Rohée
@Bruno 你说得对,但crc32算法完全适用于.net System.Security.Cryptography模式。这就是我添加标签的原因。无论如何,如果有人感到烦恼,我可以将其删除。我用它来识别文件。 - Eric Bole-Feysot
2个回答

5

现在我们在.NET Core 3.0中有可用的System.Runtime.Intrinsics.X86命名空间,这让我们受益匪浅。下面是使用SSE 4.2实现CRC32-C算法的完整代码:

using System;
using System.Runtime.Intrinsics.X86;
using System.Security.Cryptography;

/// <summary>
/// The hardware implementation of the CRC32-C polynomial 
/// implemented on Intel CPUs supporting SSE4.2.
/// </summary>
public class Crc32HardwareAlgorithm : HashAlgorithm
{
    /// <summary>
    /// the current CRC value, bit-flipped
    /// </summary>
    private uint _crc;

    /// <summary>
    /// We can further optimize the algorithm when X64 is available.
    /// </summary>
    private bool _x64Available;

    /// <summary>
    /// Default constructor
    /// </summary>
    public Crc32HardwareAlgorithm()
    {
        if (!Sse42.IsSupported)
        {
            throw new NotSupportedException("SSE4.2 is not supported");
        }

        _x64Available = Sse42.X64.IsSupported;

        // The size, in bits, of the computed hash code.
        this.HashSizeValue = 32;
        this.Reset();
    }

    /// <summary>When overridden in a derived class, routes data written to the object into the hash algorithm for computing the hash.</summary>
    /// <param name="array">The input to compute the hash code for.</param>
    /// <param name="ibStart">The offset into the byte array from which to begin using data.</param>
    /// <param name="cbSize">The number of bytes in the byte array to use as data.</param>
    protected override void HashCore(byte[] array, int ibStart, int cbSize)
    {
        if (_x64Available)
        {
            while (cbSize >= 8)
            {
                _crc = (uint)Sse42.X64.Crc32(_crc, BitConverter.ToUInt64(array, ibStart));
                ibStart += 8;
                cbSize -= 8;
            }
        }

        while (cbSize > 0)
        {
            _crc = Sse42.Crc32(_crc, array[ibStart]);
            ibStart++;
            cbSize--;
        }
    }

    /// <summary>When overridden in a derived class, finalizes the hash computation after the last data is processed by the cryptographic stream object.</summary>
    /// <returns>The computed hash code.</returns>
    protected override byte[] HashFinal()
    {
        uint outputCrcValue = ~_crc;

        return BitConverter.GetBytes(outputCrcValue);
    }

    /// <summary>Initializes an implementation of the <see cref="T:System.Security.Cryptography.HashAlgorithm"></see> class.</summary>
    public override void Initialize()
    {
        this.Reset();
    }

    private void Reset()
    {
        _crc = uint.MaxValue;
    }
}

这个只是一次读取一个字节的数组吗?x86 CRC32指令可用于1、2、4或8字节的源大小。在C++中,使用_mm_crc32_u64。这比一次一个字节地使用要快8倍;无论输入大小如何,延迟都固定为3个周期。(32位模式一次只能处理最多4个字节) - Peter Cordes
1
你说得对。我刚刚更新了答案,加入了X64版本。现在速度稍微快了一些。 - MoonStom
只是“稍微”快一点?你是在测试小输入吗?还是你的基准测试被预热时间所占主导?或者由于某些原因,它不能有效地编译?对于中等或更大的缓冲区(例如1kiB应该足以隐藏启动/清理开销),它应该快8倍。 - Peter Cordes
32位比64位慢得多的原因是,如果没有x64可用,则运行“while(cbSize> 0)”部分,这意味着整个CRC每次计算1个字节。为了解决此问题,请在“while(cbSize> 0)”之前添加以下内容(抱歉,无法进行多行):“while(cbSize> = 4) { _crc = Sse42.Crc32(_crc,BitConverter.ToUInt32(array,ibStart)); ibStart + = 4; cbSize -= 4; }” - XJDHDR
话虽如此,在我的测试中,我发现即使使用x64 Intrinsics方法,速度也只比使用纯C#的slice-by-16算法快约25%。我的修复仅将x86模式提高到比软件算法慢约50%。真正产生巨大差异的是调用这些C++ DLL中的方法,它们比x64 Intrinsics方法快约7.5倍:https://crc32c.machinezoo.com/#cpp - XJDHDR
显示剩余5条评论

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接