Testing some singleton implementations

Andrej Mitrovic andrej.mitrovich at gmail.com
Fri Jan 31 00:25:03 PST 2014


There was a nice blog-post about implementing low-lock singletons in D, here:
http://davesdprogramming.wordpress.com/2013/05/06/low-lock-singletons/

One suggestion on Reddit was by dawgfoto (I think this is Martin
Nowak?), to use atomic primitives instead:
http://www.reddit.com/r/programming/comments/1droaa/lowlock_singletons_in_d_the_singleton_pattern/c9tmz07

I wanted to benchmark these different approaches. I was expecting
Martin's implementation to be the fastest one, but on my machine
(Athlon II X4 620 - 2.61GHz) the implementation in the blog post turns
out to be the fastest one. I'm wondering whether my test case is
flawed in some way. Btw, I think we should put an implementation of
this into Phobos.

The timings on my machine:

Test time for LockSingleton: 542 msecs.
Test time for SyncSingleton: 20 msecs.
Test time for AtomicSingleton: 755 msecs.

Here's the code:

http://codepad.org/TMb0xxYw

And pasted below for convenience:

-----
module singleton;

import std.concurrency;
import core.atomic;
import core.thread;

class LockSingleton
{
    static LockSingleton get()
    {
        __gshared LockSingleton _instance;

        synchronized
        {
            if (_instance is null)
                _instance = new LockSingleton;
        }

        return _instance;
    }

private:
    this() { }
}

class SyncSingleton
{
    static SyncSingleton get()
    {
        static bool _instantiated;  // tls
        __gshared SyncSingleton _instance;

        if (!_instantiated)
        {
            synchronized
            {
                if (_instance is null)
                    _instance = new SyncSingleton;

                _instantiated = true;
            }
        }

        return _instance;
    }

private:
    this() { }
}

class AtomicSingleton
{
    static AtomicSingleton get()
    {
        shared bool _instantiated;
        __gshared AtomicSingleton _instance;

        // only enter synchronized block if not instantiated
        if (!atomicLoad!(MemoryOrder.acq)(_instantiated))
        {
            synchronized
            {
                if (_instance is null)
                    _instance = new AtomicSingleton;

                atomicStore!(MemoryOrder.rel)(_instantiated, true);
            }
        }

        return _instance;
    }
}

version (unittest)
{
    ulong _thread_call_count;  // TLS
}

unittest
{
    import std.datetime;
    import std.stdio;
    import std.string;
    import std.typetuple;

    foreach (TestClass; TypeTuple!(LockSingleton, SyncSingleton,
AtomicSingleton))
    {
        // mixin to avoid multiple definition errors
        mixin(q{

        static void test_%1$s()
        {
            foreach (i; 0 .. 1024_000)
            {
                // just trying to avoid the compiler from doing
dead-code optimization
                _thread_call_count += (TestClass.get() !is null);
            }
        }

        auto sw = StopWatch(AutoStart.yes);

        enum threadCount = 4;
        foreach (i; 0 .. threadCount)
            spawn(&test_%1$s);
        thread_joinAll();

        }.format(TestClass.stringof));

        sw.stop();
        writefln("Test time for %s: %s msecs.", TestClass.stringof,
sw.peek.msecs);
    }
}

void main() { }
-----


More information about the Digitalmars-d mailing list