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.
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();
}
}
}
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"?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With