I'm trying to write to file a certain value, calculated with some parallelized operations using Eigen, in particular with the flag -fopenmp
.
I've written a code that works perfectly with -g -o
, but I'm having trouble making this work with parallelization: I've tried with #pragma omp critical
however, while running, the program halts at the line ofstream outputFile(path, ios::app);
indicating that, despite using #pragma
, file writing remains unsafe.
Here there's an example of what I tried:
class Loss
{
public:
double loss_value;
string choosen_loss;
string path;
Loss(string loss_function, string filepath)
{
choosen_loss = loss_function;
path = filepath;
};
void calculator(variant<double, VectorXd> NN_outputs, variant<double, VectorXd> targets, int data_size)
{
#pragma omp critical //for safe writing;
if (choosen_loss == "MSE")
{
choice = MSE;
loss_value += choice(NN_outputs, targets) / (double)data_size;
}
else if (choosen_loss == "BCE")
{
choice = BCE;
loss_value += choice(NN_outputs, targets) / (double)data_size;
}
else if (choosen_loss == "MEE")
{
choice = MEE;
loss_value += choice(NN_outputs, targets) / (double)data_size;
}
else
{
cout << "\nUnvailable choice as loss function. " << endl;
}
counter++;
if (counter == data_size)
{
ofstream outputFile(path, ios::app);
if (outputFile.is_open())
{
outputFile << loss_value << endl;
outputFile.close();
}
else
{
cerr << "Errore: impossibile aprire il file " << path << endl;
}
counter = 0;
loss_value = 0;
}
};
};
Does anyone know how to solve this?
I would start by cleaning up the code, moving thing things can be done in the ctor into the ctor, so they only get done once, something on this order:
T choice;
std::ofstream output_file;
double loss_value {0.0};
Loss(string loss_function, string filepath)
: output_file(filepath)
{
if (loss_function == "MSE") {
choice = MSE;
} else if (loss_function == "BCE") {
choice = BCE;
} else if (loss_function == "MEE") {
choice = MEE;
} else {
throw std::logic_error("unavailable choice as loss function");
}
if (!output_file.is_open()) {
throw std::runtime_error("Errore: impossibile aprire il file " + filepath);
}
};
This lets us slim down calculator
to mostly doing the calculation:
void calculator(variant<double, VectorXd> NN_outputs, variant<double, VectorXd> targets, int data_size)
{
loss_value += choice(NN_outputs, targets / (double)data_size);
++counter;
if (counter == data_size)
{
output_file << loss_value << '\n';
output_file.close();
counter = 0;
loss_value = 0;
}
};
If at all possible, however, I'd just have the parallel functions return the calculated loss_value
, and have a parallel loop like this:
#pragam omp parallel for reduction(+:loss_value)
for (int counter = 0; counter < data_size; counter++) {
loss_value += choice(NN_outputs, targets) /(double)data_size;
}
output_file << loss_value << '\n';
This gets all the string comparisons out of the inner loop. We just do the calculations themselves in parallel. Writing to the file is outside the loop (and the parallel section) completely, so we don't need to use a critical section for thread safety. Oh, and the reduction(+:loss_value)
means each thread will start with its own copy of loss_value
, and after the threads all finish, it'll add those individual values together into the real loss_value
. This avoids any contention between threads in updating loss_value
.