EmptyEnumerable <T>. Initialization Assignment and Multithreading

This is more a design issue, which I guess, than a real mistake or rant. Interestingly, people think of the following behavior:

In .NET, when you want to effectively represent an empty IEnumerable, you can use Enumerable.Empty<MyType>()this to cache an empty enumerated instance. This is a good and free micro-optimization, I think it could help if I relied heavily.

However, what the implementation looks like:

public static IEnumerable<TResult> Empty<TResult>() {
    return EmptyEnumerable<TResult>.Instance;
}


internal class EmptyEnumerable<TElement>
{
    static volatile TElement[] instance;

    public static IEnumerable<TElement> Instance {
        get {
            if (instance == null) instance = new TElement[0];
            return instance;
        }
    }
}

I would expect the assignment to happen inside the lock, after another zero check, but that is not what happens.

, (.. , , ​​ , ) ?

?

+4
3

, volatile . return instance; , .

, , . .

? , , volatile, . , . . .


, volatile . . - :

var instanceRead1 = instance;
var returnValue;
if (instanceRead1 == null) {
    returnValue = new TElement[0];
    instance = returnValue;
}

var instanceRead2 = instance;
if (instanceRead2 == returnValue) return instanceRead2;
else return null;

instanceRead2 , . ​​, . CPU - . , . , .

+5

. , , , , , . . , . , , . , "" . (, ), , , .

, () , , , . , , , - . -; , .

0

, , ( ):

  • volatile instance .
  • static object syncRoot.
  • .
  • typeof(T) (, ).

( 1 ):

  • 21,7
  • 28,8
  • 20,3
  • 29,3

As you can see, the approach is the lockworst. A static type initializer would be best, which would also make the code cleaner. The actual reason is probably not really because of the lock, but rather because of the size of the recipient and things like pasting code and additional options for the compiler to optimize the code.

The speed of creating 1 million (not a billion this time) empty arrays for the same computer is 26 ms.

The code:

using System;

namespace ConsoleSandbox
{
    class T1<T>
    {
        static volatile T[] _instance;
        public static T[] Instance
        {
            get
            {
                if (_instance == null) _instance = new T[0];
                return _instance;
            }
        }
    }

    class T2<T>
    {
        static T[] _instance;
        static object _syncRoot = new object();
        public static T[] Instance
        {
            get
            {
                if (_instance == null) 
                    lock (_syncRoot)
                        if (_instance == null)
                            _instance = new T[0];
                return _instance;
            }
        }
    }

    class T3<T>
    {
        static T[] _instance = new T[0];
        public static T[] Instance
        {
            get
            {
                return _instance;
            }
        }
    }

    class T4<T>
    {
        static T[] _instance;
        public static T[] Instance
        {
            get
            {
                if (_instance == null)
                    lock (typeof(T4<T>))
                        if (_instance == null)
                            _instance = new T[0];
                return _instance;
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            int[][] res = new int[2][];
            var sw = new System.Diagnostics.Stopwatch();
            sw.Start();

            for (var i = 0; i < 1000000000; i++)
                res[i % 2] = T1<int>.Instance;

            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
            sw.Restart();

            for (var i = 0; i < 1000000000; i++)
                res[i % 2] = T2<int>.Instance;

            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
            sw.Restart();

            for (var i = 0; i < 1000000000; i++)
                res[i % 2] = T3<int>.Instance;

            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
            sw.Restart();

            for (var i = 0; i < 1000000000; i++)
                res[i % 2] = T4<int>.Instance;

            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
            sw.Restart();

            for (var i = 0; i < 1000000; i++)
                res[i % 2] = new int[0];

            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);

            Console.WriteLine(res[0]);
            Console.WriteLine(res[1]);
        }
    }
}
0
source

All Articles