Interlocked.Read / Interlocked.Exchange намного медленнее в Mono, чем .NET? - PullRequest
3 голосов
/ 07 февраля 2012

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

Короче говоря:
Interlocked.Read / Interlocked.Exchange, кажется, работает намного медленнее при работе в среде Mono, чем при работе в среде .NET. Мне любопытно узнать почему.

В длинну:
Я хотел получить потокобезопасный double для 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 Framework: .NET Interlocked test results

И этот результат с использованием фреймворка Mono: Mono Interlocked test results

Я выполнил оба теста несколько раз на одной машине (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 Framework последовательно возвращает ~ 2,5 секунд с Exchange и Read. Моно каркас возвращает ~ 5,1 секунд.

1 Ответ

2 голосов
/ 09 февраля 2012

Делать выводы о производительности не так просто. В первом примере длинное <-> двойное преобразование может быть важным фактором. Сменив все двойные на длинные (и удалив преобразования), я теперь на 32-битном Mono в 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.

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

...