【问题标题】:Performance in GUID comparisonGUID 比较中的性能
【发布时间】:2012-02-02 09:18:35
【问题描述】:

我读过this questionanother question。在这两个问题之一中,我还读到 Guid Structure 由以下四个字段组成:Int32、Int16、Int16 和 Byte[8],因此两个 Guid 之间的比较应该更快。

好吧,我只使用 Guid 结构来生成 UUID,然后我必须只比较那些以前生成的 UUID。因此,我想将快速生成的每个 UUID 转换为可比较的格式。

我使用以下代码运行了一些测试(我的灵感来自this question)。

        struct FastUuid
        {
            public long _part1;
            public long _part2;

            public FastUuid(byte[] value)
            {
                _part1 = BitConverter.ToInt64(value, 0);
                _part2 = BitConverter.ToInt64(value, 8);
            }
        }

        static void Main(string[] args)
        {
            TestUuidCompare(1000000000);
            Console.ReadLine();

        }

        public static void TestUuidCompare(uint numberOfChecks)
        {
            Guid uuid1 = Guid.NewGuid();
            Guid uuid2 = new Guid(uuid1.ToByteArray());

            byte[] a1 = uuid1.ToByteArray();
            byte[] a2 = uuid2.ToByteArray();

            string s1 = uuid1.ToString();
            string s2 = uuid2.ToString();

            BigInteger b1 = new BigInteger(uuid1.ToByteArray());
            BigInteger b2 = new BigInteger(uuid2.ToByteArray());

            long l1part1 = BitConverter.ToInt64(uuid1.ToByteArray(), 0); // Parts 1 and 2
            long l1part2 = BitConverter.ToInt64(uuid1.ToByteArray(), 8); // of uuid1.

            long l2part1 = BitConverter.ToInt64(uuid2.ToByteArray(), 0); // Parts 1 and 2
            long l2part2 = BitConverter.ToInt64(uuid2.ToByteArray(), 8); // of uuid2.

            long[] la1 = { l1part1, l1part2 }; // Parts 1 and 2 of uuid1.
            long[] la2 = { l2part1, l2part2 }; // Parts 1 and 2 of uuid2.

            int i1part1 = BitConverter.ToInt32(uuid1.ToByteArray(), 0);  // Parts 1, 2, 3
            int i1part2 = BitConverter.ToInt32(uuid1.ToByteArray(), 4);  // and 4
            int i1part3 = BitConverter.ToInt32(uuid1.ToByteArray(), 8);  // of
            int i1part4 = BitConverter.ToInt32(uuid1.ToByteArray(), 12); // uuid1.

            int i2part1 = BitConverter.ToInt32(uuid2.ToByteArray(), 0);  // Parts 1, 2, 3
            int i2part2 = BitConverter.ToInt32(uuid2.ToByteArray(), 4);  // and 4
            int i2part3 = BitConverter.ToInt32(uuid2.ToByteArray(), 8);  // of
            int i2part4 = BitConverter.ToInt32(uuid2.ToByteArray(), 12); // uuid2

            FastUuid fast1 = new FastUuid(uuid1.ToByteArray());
            FastUuid fast2 = new FastUuid(uuid2.ToByteArray());


            // UUID are equal (worse scenario)

            Stopwatch sw = new Stopwatch();
            bool result;

            sw.Reset(); sw.Start();
            for (int i = 0; i < numberOfChecks; i++)
            {
                result = (uuid1 == uuid2);
            }
            Console.WriteLine("- Guid.Equals: \t{0}", sw.Elapsed);

            sw.Reset(); sw.Start();
            for (int i = 0; i < numberOfChecks; i++)
            {
                result = Array.Equals(a1, a2);
            }
            Console.WriteLine("- Array.Equals(byte): \t{0}", sw.Elapsed);

            sw.Reset(); sw.Start();
            for (int i = 0; i < numberOfChecks; i++)
            {
                result = s1.Equals(s2);
            }
            Console.WriteLine("- String.Equals: \t{0}", sw.Elapsed);

            sw.Reset(); sw.Start();
            for (int i = 0; i < numberOfChecks; i++)
            {
                result = b1.Equals(b2);
            }
            Console.WriteLine("- BigInteger.Equals: \t{0}", sw.Elapsed);

            sw.Reset(); sw.Start();
            for (int i = 0; i < numberOfChecks; i++)
            {
                result = (l1part1 == l2part1 && l1part2 == l2part2);
            }
            Console.WriteLine("- Two long compare: \t{0}", sw.Elapsed);

            sw.Reset(); sw.Start();
            for (int i = 0; i < numberOfChecks; i++)
            {
                result = Array.Equals(la1, la2);
            }
            Console.WriteLine("- Array.Equals(long): \t{0}", sw.Elapsed);

            sw.Reset(); sw.Start();
            for (int i = 0; i < numberOfChecks; i++)
            {
                result = (i1part1 == i2part1 && i1part2 == i2part2 && i1part3 == i2part3 && i1part4 == i2part4);
            }
            Console.WriteLine("- Four int compare: \t{0}", sw.Elapsed);

            sw.Reset(); sw.Start();
            for (int i = 0; i < numberOfChecks; i++)
            {
                result = fast1.Equals(fast2);
            }
            Console.WriteLine("- FastUuid: \t{0}", sw.Elapsed);
        }

有以下结果。

  • Guid.Equals:18.911 秒
  • Array.Equals(byte): 12.003 s
  • String.Equals:26.159 秒
  • BigInteger.Equals:22.652 秒
  • 两个长比较:6.530 s
  • Array.Equals(long): 11.930 s
  • 四个整数比较:6.795 秒
  • FastUuid:1m 26.636 s

为什么 FastUuid 比较最慢? 既然 UUID 应该是 Dictionary 的关键,有没有办法得到两个 long 之间的性能比较,同时用一个小的(16 字节)对象/结构来实现 UUID?

编辑: 实际上,我执行的测试是衡量两个 UUID 之间的比较性能,因此它们对评估访问 Dictionary 的性能几乎没有意义,因为当我调用 ContainsKey method 时,它会计算 UUID 的哈希值。 我应该评估计算 Guid、String、FastUuid 等的哈希值的性能吗? ContainsKey 方法是如何工作的?

【问题讨论】:

  • 致对 OP 问题投反对票的人,为什么?建设性的反馈总是受欢迎的!
  • 你的测试很有问题。您正在测试具有 1000000000 个相等 guid 的极不可能的情况。 Guid.Equals() 针对更可能的情况进行了优化,即它们不会相等。
  • 我设置了 1000000000 次检查以获得更好的比较。

标签: c# .net


【解决方案1】:

结构体的 Equals 默认实现使用反射进行比较,比较慢。 (在 MSDN 上的 ValueType.Equals 上查看更多信息。)

您应该覆盖 Equals 并提供您自己的实现:

public override bool Equals(object other)
{
    var fastOther = other as FastUuid?;
    return fastOther.HasValue &&
        fastOther.Value._part1 == _part1 &&
        fastOther.Value._part2 == _part2;
}

但是,这并不能完全解决问题。由于这是一个结构,您还应该实现 IEquatable&lt;FastUuid&gt; 以避免将其他项目装箱/拆箱:

public bool Equals(FastUuid other)
{
    return
        other._part1 == _part1 &&
        other._part2 == _part2;
}

等于就是:

public override bool Equals(object other)
{
    var fastOther = other as FastUuid?;
    return fastOther.HasValue && Equals(fastOther.Value);
}

【讨论】:

  • 使用这个方案,经过的时间增加:3m 52 s
  • 对不起,我还应该展示如何避免装箱其他结构。我已经更新了示例。
  • 至少应该比 Array.Equals 版本快。你是 64 位还是 32 位?
  • 对于我给出的第二个版本,我得到的性能与 Array.Equals 在 32 位中的性能差不多。
  • 请注意,在 .Net 4.5 中不再适用:“如果当前实例和 obj 的字段都不是引用类型,则 Equals 方法对两者进行逐字节比较内存中的对象。否则,它使用反射来比较obj和这个实例的对应字段。 - msdn.microsoft.com/en-us/library/2dts52z7.aspx
【解决方案2】:

结构使用运行时反射来确定需要比较的内容。使用您自己的比较方法覆盖 Equals 以获得更快的结果。

http://msdn.microsoft.com/en-us/library/2dts52z7.aspx

更新 - 覆盖结构的 equals 最终会产生装箱开销(将结构转换为对象并返回结构),但如果您定义一个直接采用 FastUuid 结构的 Equals,它会运行得更快:

        public bool Equals(FastUuid value)
        {
            return this._part1 == value._part1
                && this._part2 == value._part2;
        }

这比我的机器上的 Guid 实现略快(4.2 秒对 5.8 秒,但仍比 1.7 秒的长比较慢)。

ps。请小心仅使用发布版本运行测试,因为调试版本会严重影响时间。

【讨论】:

  • 好!在发布模式下,这些时间会减少: - 两个长比较:2.291 秒 - 四个 int 比较:2.245 秒 - FastUuid:10.018 秒
猜你喜欢
  • 1970-01-01
  • 2021-10-19
  • 2014-10-11
  • 1970-01-01
  • 2018-02-21
  • 2015-07-31
  • 2014-08-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多