Interlocked.Read / Interlocked.Exchange намного медленнее на Mono, чем на .NET?

Извините за длинный вопрос, но есть ссылка на Джона Скита, так что для некоторых это может оказаться полезным.

Вкратце:
Interlocked.Read / Interlocked.Exchange работает намного медленнее при работе в среде Mono, чем при работе в среде .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();
    }
}

Затем я прочитал ответ Джона Скита на этот вопрос, поэтому я сделал эту структуру:

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

Я запускал оба теста несколько раз на одном и том же компьютере (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 постоянно возвращает ~2,5 секунды как с Exchange, так и с Read. Платформа Mono возвращает ~5,1 секунд.


person ken    schedule 07.02.2012    source источник
comment
Я бы начал с того, что этот тест не очень полезен: запустите .Net 4.0 64bit в Windows и Mono в Linux, чтобы сравнить, как работают текущие версии.   -  person skolima    schedule 07.02.2012
comment
На самом деле вы не знаете, медленнее ли это методы Interlocked, это также может быть диспетчеризация интерфейса. Я предлагаю убрать как можно больше обобщений, прежде чем делать какие-либо выводы (т.е. просто поместить методы Interlocked в цикл).   -  person Rolf Bjarne Kvinge    schedule 08.02.2012
comment
@RolfBjarneKvinge - я обновил свой вопрос, добавив более простой тест и результаты.   -  person ken    schedule 09.02.2012
comment
Просто чтобы убедиться: вы используете релизную сборку, и к вам не подключен отладчик?   -  person CodesInChaos    schedule 09.02.2012
comment
Ваши новые тесты в основном подтверждают, что ваши первоначальные тесты были медленнее в основном из-за других вещей, чем Interlocked. Это подтверждает, что реальная разница на экземпляр практически невелика.   -  person Andrew Barber    schedule 09.02.2012


Ответы (1)


Делать выводы о производительности не так просто. В первом примере важным фактором может быть преобразование long‹->double. Изменив все двойные числа на длинные (и удалив конверсии), я получил такое время на 32-битном моно в Windows:

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.

Также обратите внимание, что единственная гарантия, которую дает приведенная выше реализация, заключается в том, что вы не увидите разрывов. Например, это не дает вам (обычно необходимой) гарантии того, что если два потока увеличивают значение, сумма будет правильной (т.е. она будет учитывать все приращения).

person konrad.kruczynski    schedule 08.02.2012