https://github.com/crillab/pfactory
Raw File
Tip revision: 146679997c1ab450af33942e741df39317b6c2a9 authored by Lonca Emmanuel on 21 February 2020, 10:00:23 UTC
removed trailing reference to experimental/random
Tip revision: 1466799
README.md
# pFactory - A generic library for designing parallel solvers

[![Build Status](https://api.travis-ci.org/crillab/pfactory.svg?branch=master)](https://travis-ci.org/crillab/pfactory)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=crillab_pFactory&metric=alert_status)](https://sonarcloud.io/dashboard?id=crillab_pFactory)
[![License](https://img.shields.io/badge/license-GPL--3.0-orange)](https://github.com/crillab/pfactory/blob/master/LICENSE)

<div style="text-align: justify">
pFactory is a parallel library designed to support and facilitate the implementation of parallel solvers in C++. It provides robust implementations of parallel algorithms and allows seamlessly sharing mechanisms, divide-and-conquer or portfolio methods. pFactory is not related to a specific problem and can very easily be incorporated in order to solve any kind of combinatorial problem (SAT, CSP, MAXSAT...).
</div>

<p align="center">
<img src="pfactory.png" alt="drawing" width="800" />
</p>

<div style="text-align: justify">
We aim to produce a tool that is accessible while remaining efficient and without being intrusive. To address this challenge, pFactory now proposes three principal objects: groups, communicators, and controllers (as depicted in the figure). These objects have been designed to be modular and thus, can be operated to design and personalize a broad spectrum of parallel strategies. A group expresses a collection of tasks to be performed by a set of threads (represented by the circles in the figure). Moreover, the user can choose to start a group concurrently, which makes possible the implementation of portfolio based solvers. A controller handles a set of groups and deals with some functionalities (s.t. start, stop, . . . ) on these groups. For example, to wait simultaneously for the end of all tasks, or to activate the concurrent mode on a set of group. A communicator allows sharing information easily between threads. We provide different kinds of communicators, helping to design different sharing mechanism topologies. The object called Communicator<T> uses templates. Thus it is possible to share any kind of informations (vector, int...). A dedicated and powerful algorithms to exchange such informations is provided. More informations are given in the following paper:
</div>
<br>
pFactory: A generic library for designing parallel solvers, the 16th International Conference on Applied Computing (AC 2019).
Gilles Audemard, Gael Glorian, Jean-Marie Lagniez, Valentin Montmirail and Nicolas Szczepanski


## Installation instructions

### Dependencies
This libary needs:
 - a C++11 compiler
 - the pthread library

### Installation 
 

After unzipping the release archive, go into the source directory and enter:
```console
./configure
```

```console
make
```

The created library ```libpFactory.a``` is in the directory ```lib/```

Note to developers: editing pFactory may require the [autotools](https://www.gnu.org/software/automake/manual/html_node/Autotools-Introduction.html). After downloading the source code, execute `bootstrap` to initialize the project.

## User Manual

The main method of pFactory is the constructor ```pFactory::Group(unsigned int nbThreads)``` which create a Group object. 
An instance of the class group represents:
  - a set of threads ```std::thread```
  - a set of tasks ```std::function<int()>```

Tasks are added thanks to the method ```Group::add(std::function<int()> function)``` 
using C++11 lambdas and are launched by the method ```Group::start()```. 
Of course, we can have more tasks than threads and in this case, a queue of work is 
created and all tasks are executed. To finish, the method ```Group::wait()```  waits 
that all tasks are completed (only one if the concurrent mode is activated in the method ```start```) 
and join all threads. 

The library contains an efficient sharing mechanism.
Once the group object created, you can link a communicator that is in charge of the communication 
between threads. To this end: 
- Create a communicator (in this example, the communicator can share integers between threads): 
``` pFactory::Communicator<int>* integerCommunicator(&group);```. 
    Variable ```group``` is an  instance of ```Factory::Group``` object defined above. 
- Send int to other threads using the method ```send(int)```:
-   Receive integers from other threads. Two methods achieve this task:
    - using the method ```void recvAll(std::vector<int> &data)```. 
    In this case, the vector data receives all data.
    - using the method ```std::pair<bool, int> recv();```. In this case, one can receive
    data one by one. The first element of the pair becomes true if there is 
    no more data to receive. 




## Examples

### Example 1: Hello World

```cpp
#include "pFactory.h"

// In this example, we create a group of thread saying hello world
int main(){
  // A group of nbCores threads 
  pFactory::Group group(pFactory::getNbCores());
  // Add as many tasks as threads in the group
  for(unsigned int i = 0; i < pFactory::getNbCores();i++){
    group.add([&](){
          // pFactory::cout() provides a special critical section for displaying information
	      pFactory::cout() << group.getTask() << " says Hello World" << std::endl;
	      return 0;
      });
  }
  // Start the computation of all tasks
  group.start();
  // Wait until all threads are performed all tasks 
  group.wait();
}

```


### Example 2 : Communications

```cpp
#include "pFactory.h"

// In this example, each thread share an integer to the others.

int main() {
    // A group of nbCores threads 
    pFactory::Group group(pFactory::getNbCores());
    pFactory::Communicator<int> integerCommunicator(group);
    
    for(unsigned int i = 0; i < pFactory::getNbCores(); i++) {
        // Add as many tasks as threads in the group
        group.add([&]() {
            // group.getTask() return the task in progress 
            pFactory::cout() << group.getTask() << " sends: " << group.getTask().getId() << std::endl;
            
            integerCommunicator.send(group.getTask().getId());
            
            // A group has a barrier to wait all tasks at the same moment of the execution 
            // Here, this barrier is used to wait that all tasks are sent their data 
            group.barrier.wait();

            /* With recvAll function */
            std::stringstream msg;
            std::vector<int> data;
            integerCommunicator.recvAll(data);
            msg << group.getTask() << " receives:";
            for(unsigned int j = 0; j < data.size(); ++j)
                msg << data[j] << ' ';
            pFactory::cout() << msg.str() << std::endl;
            return 0;
        });
    }
    // Start the computation of all tasks
    group.start();
    // Wait until all threads are performed all tasks 
    group.wait();
}
```

### Example 3

```cpp
#include "pFactory.h"

// In this example, we create a group of threads with a lot of tasks.
// It is a model for a dynamic divide and conquer (DC) strategy.
// Firstly create tasks that represent subproblems (divide phase) and next calculate all theses tasks (conquer phase) 
// In addition, some others tasks can be added during the conquer phase (thus called dynamic)

int algorithm(pFactory::Group& group, bool dynamic){
  // To simulate the task calculation
  for(unsigned int j = 0; j < 100;j++){ 
    if (group.isStopped()){
      group.getTask().setDescription("stopped during its computation");
      return (int)group.getTask().getId();
    } // To stop this task during its calculation if the group have to be stopped
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
  }

  // Dynamic divide phase
  if (group.getTask().getId() < nbTasks)
    group.add([&](){return algorithm(group, true);});
  
  // The return code of the task that has finished 
  
  group.getTask().setDescription(dynamic == true? "dynamic expected end": "static expected end");
  pFactory::cout() << group.getTask() << std::endl;
  return (int)group.getTask().getId();
}

int main(){
  // A group of nbCores threads 
  pFactory::Group group(pFactory::getNbCores());

  // First divide phase : add firstly 20 tasks  
  for(unsigned int i = 0; i < 20;i++)
    // A task is represented by a C++11 lambda function 
    group.add([&](){return algorithm(group, false);});
  
  //By default, a group takes the latest tasks added (is set to group.popBack()) 
  group.popFront(); //To prioritize the first tasks added (in the order of group.add() methods)

  pFactory::Controller controller(group);
  controller.start();// Conquer phase : start the computation of all tasks
  controller.wait();// Wait until all threads are performed all tasks 

  for(auto &task: group.getTasks()) std::cout << task << std::endl;  
}
```

### Example 4

You can also [download](http://www.cril.univ-artois.fr/~audemard/pfactory-glucose.tgz) an implementation of the [SAT solver glucose](https://www.labri.fr/perso/lsimon/glucose/) in parallel mode (aka named syrup)
using the library pFactory. Such implementation integrates clauses sharing mechanism.


## Authors

Main author
 - Nicolas Szczepanski - szczepanski.nicolas@gmail.com


Other contributors
 - Gilles Audemard - audemard@cril.fr
 - Jean-Marie Lagniez - jmlagniez@gmail.com

## Contact
Do not hesitate to contact pfactory@cril.fr if you encounter any problems with pFactory.
back to top