Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Boost math (ibeta_inv function) not thread safe?

I have compiled part of boost - the ibeta_inv function - into a .Net 64 bit assembly and it worked great until I started calling it from multiple threads. Then it occationally return wrong results.

I complied it using this code (C++/CLI):

// Boost.h

#pragma once

#include <boost/math/special_functions/beta.hpp>

using namespace boost::math;

namespace Boost {

    public ref class BoostMath
    {
    public:
        double static InverseIncompleteBeta( double a, double b, double x )
        {
            return ibeta_inv(a,b,x);
        }
    };
}

Has anyone tried this before?

I have NOT tried this outside .Net, so I don't know if this is the cause, but I really don't see why, since it works great single threaded.

Usage (C#):

private void calcBoost(List<Val> vals)
{
    //gives WRONG results (sometimes):
    vals.AsParallel().ForAll(v => v.BoostResult = BoostMath.InverseIncompleteBeta(v.A, v.B, v.X));
    //gives CORRECT results:
    vals.ForEach(v => v.BoostResult = BoostMath.InverseIncompleteBeta(v.A, v.B, v.X));
}

UPDATE: As can be seen in my comments below - I'm not sure at all anymore that this is a Boost problem. Maybe it is some weird PLinq to C++/CLI bug??? I'm blaffed and will return with more facts later.

like image 776
Dan Byström Avatar asked Mar 27 '12 11:03

Dan Byström


2 Answers

It is vital that the Val class is threadsafe.

A simple way to ensure this would be to make it immutable, but I see you also have BoostResult that needs to be written. So that will need to be volatile, or have some form of locking.

public sealed class Val
{
    // Immutable fields are inheriently threadsafe 
    public readonly double A;
    public readonly double B;
    public readonly double X;

    // volatile is an easy way to make a single field thread safe
    // box and unbox to allow double as volatile
    private volatile object boostResult = 0.0;

    public Val(double A, double B, double X)
    {
        this.A = A;
        this.B = B;
        this.X = X;
    }

    public double BoostResult
    {
        get
        {
            return (double)boostResult;
        }
        set
        {
            boostResult = value;
        }
    }
}

Lock version: (see this question to determine which is best suited for your application)

public sealed class Val
{
    public readonly double A;
    public readonly double B;
    public readonly double X;

    private readonly object lockObject = new object();
    private double boostResult;

    public Val(double A, double B, double X)
    {
        this.A = A;
        this.B = B;
        this.X = X;
    }

    public double BoostResult
    {
        get
        {
            lock (lockObject)
            {
                return boostResult;
            }
        }
        set
        {
            lock (lockObject)
            {
                boostResult = value;
            }
        }
    }
}

And if you think 6 million locks will be slow, just try this:

using System;

namespace ConsoleApplication17
{
    class Program
    {
        static void Main(string[] args)
        {
            { //without locks
                var startTime = DateTime.Now;
                int i2=0;
                for (int i = 0; i < 6000000; i++)
                {
                    i2++;
                }
                Console.WriteLine(i2);
                Console.WriteLine(DateTime.Now.Subtract(startTime)); //0.01 seconds on my machine
            }
            { //with locks
                var startTime = DateTime.Now;
                var obj = new Object();
                int i2=0;
                for (int i = 0; i < 6000000; i++)
                {
                    lock (obj)
                    {
                        i2++;
                    }
                }
                Console.WriteLine(i2);
                Console.WriteLine(DateTime.Now.Subtract(startTime)); //0.14 seconds on my machine, and this isn't even in parallel.
            }
            Console.ReadLine();
        }
    }
}
like image 79
weston Avatar answered Nov 02 '22 05:11

weston


I happen to have encapsulated part of boost in a C++/CLI 64bit project and use it from C# exactly as you do.

So I threw in your C++ class in my own Boost wrapper and added this code to the C# project:

    private class Val
    {
        public double A;
        public double B;
        public double X;
        public double ParallellResult;
    }

    private static void findParallelError()
    {
        var r = new Random();

        while (true)
        {
            var vals = new List<Val>();
            for (var i = 0; i < 1000*1000; i++)
            {
                var val = new Val();
                val.A = r.NextDouble()*100;
                val.B = val.A + r.NextDouble()*1000;
                val.X = r.NextDouble();
                vals.Add(val);
            }

            // parallel calculation
            vals.AsParallel().ForAll(v => v.ParallellResult = Boost.BoostMath.InverseIncompleteBeta(v.A, v.B, v.X));

            /sequential verification
            var error = vals.Exists(v => v.ParallellResult != Boost.BoostMath.InverseIncompleteBeta(v.A, v.B, v.X));
            if (error)
                return;
        }
    }

And it just executes "forever". The parallel results are equal to the sequential results all the time. No thread unsafety here...

May I suggest that you download a fresh copy of Boost and include it in a completely new project and try this out?

I also notice that you called your result "BoostResult"... and mention in the comments something about "our current implementaion". Exactly what are you comparing your result agains? What is your definition of "correct"?

like image 20
Zoon Shine Avatar answered Nov 02 '22 04:11

Zoon Shine