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

Posted

技术标签:

【中文标题】Mono 上的 Interlocked.Read / Interlocked.Exchange 比 .NET 慢得多?【英文标题】:Interlocked.Read / Interlocked.Exchange much slower on Mono than .NET? 【发布时间】:2012-02-07 15:57:52 【问题描述】:

抱歉这个问题太长了,但是有 Jon Skeet 的参考资料,所以对某些人来说可能是值得的。

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

长篇大论: 我想要一个适用于 32 位平台的线程安全 double,所以我制作了这个结构:

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 对this question 的回答,所以我制作了这个结构:

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 框架得到了这个结果:

使用 Mono 框架的结果如下:

我在同一台机器 (Windows XP) 上多次运行这两项测试,结果是一致的。我很想知道为什么 Interlocked.Read/Interlocked.Exchange 似乎在 Mono 框架上执行得这么慢。

更新:

我写了以下更简单的测试:

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 框架在 ExchangeRead 上始终返回 ~2.5 秒。 Mono 框架返回 ~5.1 秒。

【问题讨论】:

我会从“这个测试并不是真的有用”开始:在 Windows 上运行 .Net 4.0 64bit 并在 Linux 上运行 Mono 来比较当前版本的性能。 你实际上不知道是不是Interlocked方法更慢,也可能是接口调度。我建议在得出任何结论之前尽可能多地去除概括(即,只需将 Interlocked 方法放在循环中)。 @RolfBjarneKvinge -- 我用更简单的测试和结果更新了我的问题。 只是为了确保:您使用的是发布版本,并且您没有附加调试器? 您的新测试基本上确认您的原始测试速度较慢,主要是由于其他因素而不是 Interlocked。它证实了每个实例的真正差异几乎没有。 【参考方案1】:

得出性能结论并不容易。在第一个示例中,longdouble 转换可能是重要因素。通过将所有双精度数更改为长整数(并删除转换),这些是我在 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 编译器中做得更好。但这只是一个猜测。如果您想比较现实生活场景中的联锁性能,您有(至少)两种选择:

    在现实生活场景中比较它们。 比较 JIT 编译器发出的机器代码并查看 Interlocked 的确切实现。

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

【讨论】:

以上是关于Mono 上的 Interlocked.Read / Interlocked.Exchange 比 .NET 慢得多?的主要内容,如果未能解决你的问题,请参考以下文章

如何执行双重原子读取?

Raspberry Pi 上的 Mono [关闭]

MONO 4.6.2 服务器上的高 CPU 使用率

DateTime 列上的 Mono InvalidOperationException (SQL Server)

Linux / Mono上的VS编码UI测试

无法在 Mono 3 上的 xsp 上运行 asp.net 4.5 应用程序