比较安全与不安全的byte[]比较方法

4

我在各种地方看到过关于使用固定块进行不安全迭代数组有多快的内容。我在.NET 4和4.5中尝试了一下,结果大致相同。
安全比较总是更快,有时候略微快一些,有时候几乎是一半的时间,特别是在.NET 4中。

我做错了什么吗?

class Program
{
    public unsafe static int UnsafeCompareTo2(byte[] self, byte[] other)
    {
        if (self.Length < other.Length) { return -1; }

        if (self.Length > other.Length) { return +1; }

        GCHandle selfHandle =
            GCHandle.Alloc(self, GCHandleType.Pinned);

        GCHandle otherHandle =
            GCHandle.Alloc(other, GCHandleType.Pinned);

        byte* selfPtr = (byte*)
            selfHandle.AddrOfPinnedObject().ToPointer();

        byte* otherPtr = (byte*)
            otherHandle.AddrOfPinnedObject().ToPointer();

        int length = self.Length;

        int comparison = 0;

        for (int index = 0; index < length; index++)
        {
            comparison =
                (*selfPtr++).CompareTo((*otherPtr++));

            if (comparison != 0) { break; }
        }
        selfHandle.Free();

        return comparison;
    }

    public static int CompareTo(byte[] self, byte[] other)
    {
        if (self.Length < other.Length) { return -1; }

        if (self.Length > other.Length) { return +1; }

        int comparison = 0;

        for (int i = 0; i < self.Length && i < other.Length; i++)
        {
            if ((comparison = self[i].CompareTo(other[i])) != 0)
            { return comparison; }
        }
        return comparison;
    }

    public unsafe static int UnsafeCompareTo(byte[] self, byte[] other)
    {
        if (self.Length < other.Length) { return -1; }

        if (self.Length > other.Length) { return +1; }

        int n = self.Length;

        fixed (byte* selfPtr = self, otherPtr = other)
        {
            byte* ptr1 = selfPtr;
            byte* ptr2 = otherPtr;

            while (n-- > 0)
            {
                int comparison;

                if ((comparison = (*ptr1++).CompareTo(*ptr2++)) != 0)
                {
                    return comparison;
                }
            }   
        }
        return 0;
    }

    static void Main(string[] args)
    {
        byte[] b1 = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
        byte[] b2 = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 21 };
        Stopwatch watch = new Stopwatch();

        watch.Start();
        int result;
        for(long i = 0; i < Math.Pow(10, 2); i++)
            result = CompareTo(b1, b2);
        watch.Stop();
        Console.WriteLine("safe = {0}", watch.Elapsed);
        watch.Restart();
        for (long i = 0; i < Math.Pow(10, 2); i++)
            result = UnsafeCompareTo(b1, b2);
        watch.Stop();
        Console.WriteLine("unsafe1 = {0}", watch.Elapsed);
        watch.Restart();
        for (long i = 0; i < Math.Pow(10, 2); i++)
            result = UnsafeCompareTo2(b1, b2);
        watch.Stop();
        Console.WriteLine("unsafe2 = {0}", watch.Elapsed);
        Console.ReadLine();
    }
}

1
可能是比较操作的装箱/拆箱,我得试一下。有趣 +1 - Eli Algranti
有趣的事情,如果在if语句中去掉长度验证和第二个不安全比较,有时候第二个比较会获胜... 哈哈 - marcelo-ferraz
我尝试了更多的迭代,10^5次,然后发现不安全的方式更快。 - marcelo-ferraz
是的,我以为这会是一个线性进展,但在大约100000次迭代中,第一个不安全的比较执行时间的一半,即使它有if语句。 - marcelo-ferraz
大多数时间,一半的毫秒...我尝试了10^3次迭代,保险柜更快,但是10^5次迭代显示出了所有人都谈论的性能提升... - marcelo-ferraz
请翻译下列与编程有关的内容从英语到中文。仅返回翻译后的文本:但请尝试并指出任何错误。 - marcelo-ferraz
1个回答

1

对我来说,区别往往被开销和随机噪声淹没。我发现它在更多的迭代和更长的字节数组中更加突出。我制作了一个变体,通过更经常地避免方法调用开销,使其稍微更快:

public unsafe static int UnsafeCompareTo(byte[] self, byte[] other)
{
    if (self.Length < other.Length) { return -1; }

    if (self.Length > other.Length) { return +1; }

    int n = self.Length;

    fixed (byte* selfPtr = self, otherPtr = other)
    {
        byte* ptr1 = selfPtr;
        byte* ptr2 = otherPtr;

        byte b1;
        byte b2;
        while (n-- > 0)
        {
            b1 = (*ptr1++);
            b2 = (*ptr2++);
            if (b1 == b2)
                continue;
            return b1.CompareTo(b2);
        }
    }
    return 0;
}

我还注意到你的代码中有一个 bug(实际上不会使它变慢),在下面这一行:

GCHandle otherHandle = GCHandle.Alloc(self, GCHandleType.Pinned);

它应该使用其他内容,并在使用后释放它。

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