使用volatile写入类成员vs结构成员的奇怪行为

时间:2013-06-18 15:19:35

标签: c# .net performance struct volatile

我正在开发一个需要long类型且具有易失性语义的组件 由于.NET中没有volatile long,我创建了一个简单的包装器类型,它使用Volatile类来处理读/写访问。
我不确定是否应该使用类或结构,所以我决定对它们进行测试,然后我遇到了一个非常奇怪的行为。

这是测试代码:

internal class Program
{
    private class VolatileLongClass
    {
        private long value;

        public long Value
        {
            get { return Volatile.Read(ref value); }
            set { Volatile.Write(ref this.value, value); }
        }
    }

    private struct VolatileLongStruct
    {
        private long value;

        public long Value
        {
            get { return Volatile.Read(ref value); }
            set { Volatile.Write(ref this.value, value); }
        }
    }

    private static void Main()
    {
        const int iterations = 10;
        var totalTime = 0L;
        for (var i = 0; i < iterations; i++)
        {
            var watch = Stopwatch.StartNew();

            var volatileLong = new VolatileLongClass(); //<-- change to VolatileLongStruct
            for (var j = 0L; j < 10 * 1000 * 1000; j++)
                volatileLong.Value = j;

            var msElapsed = watch.ElapsedMilliseconds;
            Console.Out.WriteLine("Ms Elapsed = {0}", msElapsed);
            totalTime += msElapsed;
        }
        Console.Out.WriteLine("Avg = {0:N2}", (double) totalTime / iterations);
    }
}

我为VolatileLongStruct获得的输出:

Ms Elapsed = 109
Ms Elapsed = 109
Ms Elapsed = 109
Ms Elapsed = 109
Ms Elapsed = 109
Ms Elapsed = 109
Ms Elapsed = 109
Ms Elapsed = 109
Ms Elapsed = 109
Ms Elapsed = 109
Avg = 109.00

struct的上述输出是一致的。但是,VolatileLongClass的输出是:

Ms Elapsed = 17558   <-- ***
Ms Elapsed = 105
Ms Elapsed = 105
Ms Elapsed = 105
Ms Elapsed = 105
Ms Elapsed = 105
Ms Elapsed = 17541   <-- ***
Ms Elapsed = 105
Ms Elapsed = 105
Ms Elapsed = 105
Avg = 3,593.90

正如您所看到的,某些迭代存在很大的时间差异。花费异常时间的精确迭代略有不同,但至少有一次迭代存在一致的问题。

有人可以解释为什么一个易变的写入(有时)在类成员上(有时)长于结构成员吗?

顺便说一下,上面的结果是用.Net 4.5和Release build

生成的

1 个答案:

答案 0 :(得分:1)

时差很可能是循环中var volatileLong = new VolatileLongClass();的结果;该语句使编译器 - 一次 - 分配空间来保存VolatileLongClass引用,然后在每次通过循环时创建一个新对象并将引用存储到该位置。相比之下,语句var volatileLong = new VolatileLongStruct();导致编译器 - 一次 - 分配空间来保存VolatileLongStruct实例,然后在每次传递循环时通过将所有实例清零来预先存在实例数据(使用正常而非易失性写入)。

请注意,如果代码要求结构字段具有特定的多线程语义,那么这些字段通常应该公开,结构应该被视为与管道磁带粘在一起的一组变量[例如public struct IntPair {public int V1,V2;} IntPair myPair;应被视为创建两个单独的变量myPair.V1myPair.V2]。实际上,由于结构,所以变量的组合与管道磁带粘在一起,并且由于大小不是1,2或4个字节的结构所呈现的任何其他抽象必然是“漏洞”,特别是关于多线程行为,结构更好地表现为它本身,而不是伪装成它不是。