数字比较作为 uint 是否比 c# 中的普通 int 更快

Is numbers comparisons as uint faster than normal int in c#

我正在查看一些 .Net 核心库是如何实现的,引起我注意的许多事情之一是 Dictionary<TKey, TValue> class 一些数字比较完成了对 (uint) 尽管在我天真的眼中这并没有影响逻辑。

例如

do { // some magic } while (collisionCount <= (uint)entries.Length);

collisionCount0 处初始化并始终递增 (collisionCount++),因此 entries 是一个数组,其长度也不会为负数 see source code

相对于

if ((uint)i >= (uint)entries.Length) { // some code }

source code line

其中 i 在某些情况下执行以下操作时可能会变成负值,see debug img

i = entry.next; 

因此将其用作正数会改变程序流程(由于二进制补码)

查看 class 代码的摘录:

// Some code and black magic

uint hashCode = (uint)key.GetHashCode();
int i = GetBucket(hashCode);
Entry[]? entries = _entries;
uint collisionCount = 0;
if (typeof(TKey).IsValueType)
{
    i--;
    do
    {
        if ((uint)i >= (uint)entries.Length) // <--- Workflow impact
        {
            goto ReturnNotFound;
        }

        entry = ref entries[i];
        if (entry.hashCode == hashCode && EqualityComparer<TKey>.Default.Equals(entry.key, key))
        {
            goto ReturnFound;
        }

        i = entry.next;

        collisionCount++;
    } while (collisionCount <= (uint)entries.Length);
}

// More cool stuffs

是否有任何性能提升或这是什么原因?

链接的词典源包含此评论;

// Should be a while loop https://github.com/dotnet/runtime/issues/9422
// Test in if to drop range check for following array access
if ((uint)i >= (uint)entries.Length)
{
    goto ReturnNotFound;
}
 
entry = ref entries[i];

此处的uint比较并不快,但有助于加快数组访问速度。链接的 github 问题讨论了 运行time 编译器的限制,以及该循环结构如何允许进一步优化。由于此 uint 比较已明确执行,因此编译器可以证明 0 <= i < entries.Length。这允许编译器省略数组边界测试并抛出 IndexOutOfRangeException,否则将需要

换句话说,在编写这段代码时,执行了性能分析。编译器不够聪明,无法尽可能快地生成更简单、更易读的代码,运行。因此,对编译器的局限性有深刻理解的人调整了代码以使其更好。