Multi-Threading and QuantLib

Update 23.11.2015: The latest version is now part of the official QuantLib Release 1.7.

Update 22.09.2015: Please find the latest version of the patch for QuantLib 1.6.2 here.

Update 28.02.2015: Please find the latest version of the patch for QuantLib 1.5 here.

Update 11.05.2014: Please find the latest version of the patch for QuantLib 1.4 here.

QuantLib is per se not thread-safe. The standard way to utilise more than one core is to spawn several independent processes. Riccardo’s thread-safe singleton patch allows to use QuantLib within multi-threading applications as long as objects aren’t explicitly shared between different threads. In fact this patch turns the singleton pattern into a thread local singleton pattern. One possible use case of this patch is to run the test-suite with a multi-threading test runner to speed it up, e.g. on a i7@3.2Ghz with four cores plus four HT cores the multi-threading test-suite runs in approx. two minutes whereas the single threaded version takes around eight minutes.

Using QuantLib in Java/Scala/C# or F# applications via the SWIG layer violates the multi-threading requirement because the garbage collector runs in a different thread and therefore QuantLib objects are shared among different threads. This creates problems with QuantLib’s implementation of the observer pattern and is discussed in detail here.

An improved implementation of the observer pattern based on boost::signals2 together with Riccardo’s thread-safe singleton patch and the multi-threading test runner can obtained from github. Under Linux/MacOS use

./configure --enable-tss --enable-thread-safe-observer-pattern

to enable the thread-safe singleton and the thread-safe observer pattern. Under Windows the corresponding preprocessor directives

#define QL_ENABLE_TSS
#define QL_ENABLE_THREAD_SAFE_OBSERVER_PATTERN

are already set in the file userconfig.hpp.  In order to enable the boost shared_ptr hook change the preprocessor variable BOOST_SP_ENABLE_DEBUG_HOOKS towards BOOST_SP_ENABLE_DEBUG_HOOKS_2 in the file

boost/smart_ptr/detail/sp_counted_impl.hpp

Background: the original preprocessor variable BOOST_SP_ENABLE_DEBUG_HOOKS changes the memory layout of the class boost::shared_ptr which might lead to problems with other pre-compiled libraries which also use boost::shared_ptr.

The reward for this work is a stable SWIG interface for Java/Scala/C#/F# and a thread local singleton implementation, which allows to use QuantLib within multi-threading applications as long as SWIG/QuantLib objects aren’t explicitly shared among different threads.

Advertisements

12 thoughts on “Multi-Threading and QuantLib

  1. Thanks a lot for the post. I’m currently using QL 1.3 and SWIG with C# on Windows. What are the files I need to add/replace in QL 1.3 to enable this implementation (or any good way to find them in the github tree)?

  2. Hi, I’m trying to build on VC10 , x64, checked out R01030x-branch from quantlib project, changed observable files and replaced BOOST_SP_ENABLE_DEBUG_HOOKS with BOOST_SP_ENABLE_DEBUG_HOOKS_2. Everything builds, but as soon as I run test-suite I get an error

    Unhandled exception at 0x76d2e4b4 in QuantLib-test-suite-vc100-x64-mt-gd.exe: 0xC0000005: Access violation writing location 0x0000000000000024.

    Under debugger I can see that exception happens as soon as it hits constructor

    scoped_lock(boost::signals2::mutex& mutex)
    : mutex_(mutex) { mutex_.lock(); }

    ====>

    void lock()
    {
    EnterCriticalSection(&cs_);
    }

    Any suggestions of what I’m missing.

      • OK, I finally nailed down what goes wrong. Below is the sample program to recreate the bug. All you need to do is to declare global static Handle .

        #include

        using namespace QuantLib;

        /*
        this is static so constructor is called before main starts,
        In observable.cpp there is anothere static variable boost::signals2::mutex mutex ;
        when constructor of testQuote is called it tries to lock the mutex in observable but
        the mutex in observable is not initialized yet so that causes segv
        */
        Handle testQuote;
        int main(int, char* []) {

        printf(“Program started!!!”);
        }
        .
        I tried to do some workaround but than figured out that in observable.cpp std::set<void*, std::greater > globalSet; is not initialized either so even if we avoid locking uninitialized mutex we fail when we try to add object to the globalset. the whole logic needs some rework. Or simply avoid declaring static handle(pretty harsh restriction IMHO)

        Issue with the quantlib test is that it has in some test cases global statics Handles are used. Like one in inflationvolatility.cpp
        Handle nominalEUR;
        Handle nominalGBP;

  3. My solution is to wrap mutex and globalet infto functions. Seems to be working fine. Testes from SWIG C# as well as testsuite.

    Changes are local to observable.cpp

    namespace {

    typedef char* pointer;
    std::set<void*, std::greater >* pglobalSet=NULL;
    boost::signals2::mutex* pmutex=NULL;

    //There is no need to do any synchronization since function gets called during binary loading time i.e no multithreading
    std::set<void*, std::greater >& globalSet()
    {
    if (pglobalSet==NULL)
    pglobalSet= new std::set<void*, std::greater >();
    return *pglobalSet;
    }

    //There is no need to do any synchronization since function gets called during binary loading time i.e no multithreading
    boost::signals2::mutex& mutex()
    {
    if (pmutex==NULL)
    pmutex= new boost::signals2::mutex();
    return *pmutex;
    }

    class ScopedLockecInitializer
    {
    public:
    ScopedLockecInitializer()
    {
    globalSet();
    mutex();
    }
    };

    //This will have side effect if there are no static observers within compilation unit
    //Side effect is that both globalSet and mutex are initialized
    ScopedLockecInitializer scopedLockecInitializer_;

    class scoped_lock {
    public:
    scoped_lock(boost::signals2::mutex& mutex)
    : mutex_(mutex) { mutex_.lock(); }
    ~scoped_lock() { mutex_.unlock(); }
    private:
    boost::signals2::mutex& mutex_;
    };

    void addObserverToSet(void* obs) {

    scoped_lock lock(mutex());
    globalSet().insert(obs);
    }

    bool isObserver(void* obs) {

    scoped_lock lock(mutex());
    return globalSet().count(obs) > 0;
    }

    void setObserverThisPointer(void* obs) {

    scoped_lock lock(mutex());

    const std::set<void*, std::greater >::iterator iter
    = globalSet().lower_bound(obs);

    if (iter != globalSet().end()) {
    pointer p = reinterpret_cast(*iter);

    const std::size_t extra=2*(sizeof(std::size_t)+sizeof(Observer**));
    const std::size_t n = *reinterpret_cast(p-extra);

    if (obs < p + n) {
    *reinterpret_cast(p – 2*sizeof(Observer**))
    = reinterpret_cast(obs);
    }
    }
    }

    std::size_t deleteObserverFromSet(void* obs) {

    scoped_lock lock(mutex());
    return globalSet().erase(obs);
    }
    }

  4. Have you uploaded this patch to a new zip yet? As I have tried your GITHUB site as well as the links here. Thanks. If so what is the link please?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s