Interlocked.Read / Interlocked.Exchange 在 Mono 上比 .NET 慢得多吗?

3

抱歉问题比较长,但其中涉及到Jon Skeet的参考信息,对于某些人来说可能是值得的。

简而言之:
在Mono框架中运行时,Interlocked.Read / Interlocked.Exchange 似乎比在.NET框架中运行时执行速度慢得多。我想知道是为什么。

详细解释:
我想要一个适用于32位平台的线程安全双精度浮点数,所以我创建了这个结构体:

public interface IThreadSafeDouble
{
    double Value { get; set; }
}

public struct LockedThreadSafeDouble : IThreadSafeDouble
{
    private readonly object Locker;
    private double _Value;

    public double Value
    {
        get { lock (Locker) return _Value; }
        set { lock (Locker) _Value = value; }
    }

    public LockedThreadSafeDouble(object init)
        : this()
    {
        Locker = new object();
    }
}

当我阅读了Jon Skeet对于这个问题的回答后,我创建了以下这个结构体:

public struct InterlockedThreadSafeDouble : IThreadSafeDouble
{
    private long _Value;

    public double Value
    {
        get { return BitConverter.Int64BitsToDouble(Interlocked.Read(ref _Value)); }
        set { Interlocked.Exchange(ref _Value, BitConverter.DoubleToInt64Bits(value)); }
    }
}

然后我编写了这个测试:

    private static TimeSpan ThreadSafeDoubleTest2(IThreadSafeDouble dbl)
    {
        var incrementTarg = 10000000;
        var sw = new Stopwatch();
        sw.Start();
        for (var i = 0; i < incrementTarg; i++, dbl.Value++);
        sw.Stop();
        return sw.Elapsed;
    }

    private static void ThreadSafeTest()
    {
        var interlockedDbl = new InterlockedThreadSafeDouble();
        var interlockedTim = ThreadSafeDoubleTest2(interlockedDbl);

        var lockedDbl = new LockedThreadSafeDouble(true);
        var lockedTim = ThreadSafeDoubleTest2(lockedDbl);

        System.Console.WriteLine("Interlocked Time: " + interlockedTim);
        System.Console.WriteLine("Locked Time:      " + lockedTim);
    }       

    public static void Main(string[] args)
    {
        for (var i = 0; i < 5; i++)
        {
            System.Console.WriteLine("Test #" + (i + 1));
            ThreadSafeTest();
        }
        System.Console.WriteLine("Done testing.");
        System.Console.ReadLine();
    }

使用.NET框架,我得到了以下结果: .NET Interlocked test results 而在Mono框架下,我得到了以下结果: Mono Interlocked test results 我在同一台机器(Windows XP)上多次运行了这两个测试,并得到了一致的结果。我很好奇为什么在Mono框架下,Interlocked.Read/Interlocked.Exchange看起来要慢得多。
更新: 我编写了以下更简单的测试:
long val = 1;
var sw = new Stopwatch();
sw.Start();
for (var i = 0; i < 100000000; i++) {
    Interlocked.Exchange(ref val, 2);
    // Interlocked.Read(ref val);
}
sw.Stop();
System.Console.WriteLine("Time: " + sw.Elapsed);

使用.NET框架,无论是Exchange还是Read操作,平均响应时间都在约2.5秒左右。而使用Mono框架,则平均响应时间为约5.1秒


1
我会从“这个测试并不是真正有用”的角度开始:在Windows上运行.Net 4.0 64位版本,在Linux上运行Mono,比较当前版本的性能表现。 - skolima
1
你其实不知道是 Interlocked 方法更慢,还是接口分发更慢。我建议在得出任何结论之前尽可能剥离通用性(即将 Interlocked 方法放在循环内)。 - Rolf Bjarne Kvinge
@RolfBjarneKvinge -- 我更新了我的问题,附上了更简单的测试和结果。 - ken
请确认一下:您正在使用发布版本,并且没有连接调试器? - CodesInChaos
你的新测试基本上证实了你最初的测试之所以较慢,主要是由于其他原因而不是Interlocked。它证实了每个实例的真正差异几乎为零。 - Andrew Barber
1个回答

2

得出性能结论并不容易。在第一个例子中,long<->double转换可能是一个重要因素。通过将所有的doubles改为longs(并删除转换),这些是我在Windows上32位Mono上的时间:

Test #1
Interlocked Time: 00:00:01.2548628
Locked Time:      00:00:01.7281594
Test #2
Interlocked Time: 00:00:01.2466018
Locked Time:      00:00:01.7219013
Test #3
Interlocked Time: 00:00:01.2590181
Locked Time:      00:00:01.7443508
Test #4
Interlocked Time: 00:00:01.2575325
Locked Time:      00:00:01.7309012
Test #5
Interlocked Time: 00:00:01.2593490
Locked Time:      00:00:01.7528010
Done testing.

因此,Interlocked实现并不是最大的因素。

但是你有第二个例子没有转换。为什么会这样?我认为答案是循环展开,在.NET JIT编译器中更好地完成。但那只是一个猜测。如果您想在实际情况下比较互锁性能,您有(至少)两个选项:

  1. 在实际情况下进行比较。
  2. 比较JIT编译器发出的机器代码,并查看Interlocked的确切实现。

还要注意,以上实现所提供的唯一保证是您不会观察到撕裂。例如,它不会给您(通常需要的)保证,即如果两个线程正在递增值,则总和将是正确的(即它将考虑所有递增)。


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