Skip to content

Commit

Permalink
Bettered docs and minor fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
lformaggia committed May 9, 2024
1 parent be7a878 commit 02276a3
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 53 deletions.
2 changes: 1 addition & 1 deletion Examples/src/Parallel/OpenMP/SimpsonHybrid/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"n":1000000,
"a":0.0,
"b":1000.0,
"num_threads":4
"num_threads":2
}
105 changes: 56 additions & 49 deletions Examples/src/Parallel/OpenMP/SimpsonHybrid/main_simpsonHybrid.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,90 +4,97 @@
* Created on: Aug 11, 2022
* Author: forma
*/
#include <iostream>
#include <fstream>
#include <vector>
#include <tuple>
#include <cmath> // for sin
#include "json.hpp"
#include "chrono.hpp"
#include "json.hpp"
#include "mpi.h"
#include <cmath> // for sin
#include <fstream>
#include <iostream>
#include <tuple>
#include <vector>

#include "../SimpsonHybrid/SimpsonRule.hpp"
int main ()
int
main()
{
int my_rank,comm_sz;
int my_rank, comm_sz;
Timings::Chrono clock;
// This tuple is here to pack some data
std::tuple<double,double,double,unsigned int, unsigned int> databuf;
std::tuple<double, double, double, unsigned int, unsigned int> databuf;
// I use a function wrapper to wrap the function
std::function<double (double const &)> f=[](double const & x){return std::sin(x)*cos(2*x);};
MPI_Init(nullptr,nullptr);
MPI_Comm mpi_comm=MPI_COMM_WORLD;
MPI_Comm_rank(mpi_comm,&my_rank);
MPI_Comm_size(mpi_comm,&comm_sz);
std::function<double(double const &)> f = [](double const &x) {
return std::sin(x) * cos(20 * x);
};
MPI_Init(nullptr, nullptr);
MPI_Comm mpi_comm = MPI_COMM_WORLD;
MPI_Comm_rank(mpi_comm, &my_rank);
MPI_Comm_size(mpi_comm, &comm_sz);
// it will store the number of elements in each process
std::vector<unsigned int> n_local_v(comm_sz);
// it will store the starting point of the subintervl treated by each proc.
std::vector<double> a_local_v(comm_sz);
// root process does some preprocessing
if(my_rank==0)
if(my_rank == 0)
{
// Opening the file with data.
// Read from a file
std::ifstream ifile("data.json");//open file
using json=nlohmann::json;// to make life easier
json j1;// create a json object
ifile >>j1;//load the file in the json object
ifile.close();// close file
std::ifstream ifile("data.json"); // open file
using json = nlohmann::json; // to make life easier
json j1; // create a json object
ifile >> j1; // load the file in the json object
ifile.close(); // close file
// Extract data. you need to use get<type> to select the type
// Alternatively, you may use the utility value
auto a = j1["a"].get<double>();// get data from json object
auto a = j1["a"].get<double>(); // get data from json object
auto b = j1["b"].get<double>();
auto n = j1["n"].get<unsigned int>();
auto num_threads = j1.value("num_threads", 1u);
std::cout<<"Computing Simpson composite rule between "<<a<<" and "<<b<<" with "<<n<<" intervals\n";
std::cout<<"Using "<<comm_sz<<" MPI processes and " <<num_threads<<" threads\n";
std::cout << "Computing Simpson composite rule between " << a << " and "
<< b << " with " << n << " intervals\n";
std::cout << "Using " << comm_sz << " MPI processes and " << num_threads
<< " threads\n";

double h=(b-a)/n;
databuf=std::tie(a,b,h,n,num_threads); // To pack some data (only to show hot it works)
double h = (b - a) / n;
databuf =
std::tie(a, b, h, n,
num_threads); // To pack some data (only to show hot it works)
// split the subintervals
auto n_local_base=n/comm_sz;
int rest =n%comm_sz; //maybe n is not a multiple of comm_sz
a_local_v[0]=a;
for (auto i=0;i<comm_sz;++i)
auto n_local_base = n / comm_sz;
int rest = n % comm_sz; // maybe n is not a multiple of comm_sz
a_local_v[0] = a;
for(auto i = 0; i < comm_sz; ++i)
{
n_local_v[i]= i<rest? n_local_base+1:n_local_base;
n_local_v[i] = i < rest ? n_local_base + 1 : n_local_base;
}
for (auto i=1;i<comm_sz;++i)
for(auto i = 1; i < comm_sz; ++i)
{
a_local_v[i]=a_local_v[i-1]+h*n_local_v[i-1];
a_local_v[i] = a_local_v[i - 1] + h * n_local_v[i - 1];
}
clock.start(); // start timings
}
// Broadcast, we use a trick to send some data in one message and avoid the use of MPI_Pack
MPI_Bcast(&databuf, sizeof(databuf),MPI_BYTE,0,mpi_comm);
auto [a,b,h,n, num_threads]=databuf; // get global data (in fact we need only h)
// Broadcast, we use a trick to send some data in one message and avoid the
// use of MPI_Pack
MPI_Bcast(&databuf, sizeof(databuf), MPI_BYTE, 0, mpi_comm);
auto [a, b, h, n, num_threads] =
databuf; // get global data (in fact we need only h)
unsigned int n_local;
double a_local;
double a_local;
// Scatter the local parameters to all processes
MPI_Scatter(n_local_v.data(),1,MPI_UNSIGNED,&n_local,1,MPI_UNSIGNED,0,mpi_comm);
MPI_Scatter(a_local_v.data(),1,MPI_DOUBLE,&a_local,1,MPI_DOUBLE,0,mpi_comm);
auto b_local = a_local+h*n_local;
std::cout<<"Process "<<my_rank<<" a_local, b_local, n_local="<<a_local<<" "<<b_local<<" "<<n_local
<<std::endl;
double local_int = SimpsonRule_mt(f,a_local,b_local,n_local,num_threads);
MPI_Scatter(n_local_v.data(), 1, MPI_UNSIGNED, &n_local, 1, MPI_UNSIGNED, 0,
mpi_comm);
MPI_Scatter(a_local_v.data(), 1, MPI_DOUBLE, &a_local, 1, MPI_DOUBLE, 0,
mpi_comm);
auto b_local = a_local + h * n_local;
std::cout << "Process " << my_rank << " a_local, b_local, n_local=" << a_local
<< " " << b_local << " " << n_local << std::endl;
double local_int = SimpsonRule_mt(f, a_local, b_local, n_local, num_threads);
double integral{0.};
MPI_Reduce(&local_int, &integral, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
if(my_rank==0)
if(my_rank == 0)
{
std::cout<<"Integral value "<<std::setprecision(16)<<integral<<" Computed in "<<clock.wallTimeNow()<<" microsec.\n";
std::cout << "Integral value " << std::setprecision(16) << integral
<< " Computed in " << clock.wallTimeNow() << " microsec.\n";
}
MPI_Finalize();
return 0;

}




43 changes: 40 additions & 3 deletions Examples/src/SharedLibrary/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,51 @@
#A very simple example of the use of shared libraries#
# A very simple example of the use of shared libraries #


The command `exec.sh` compiles and runs the example
The command `exec.sh` compiles and runs the example.

This is just an example that creates two "relases" of a shared library an and sets up the technique used for versioning in Linux.
By changing some links you may use the old or new release of the library, without recompiling the main program. That's what happens when you install a new release of a library in your PC.

The complexity of the handling of versions and releases is of course necessary in production code. If the library is used only locally you can avoid it.
We recall that the shared library is a collection of object files that are linked together in a single file. The library is loaded in memory when the program is run, and the objects are extracted from the library and linked to the program. We can distingush three type of names of a shared library:

- the **link** name (e.g. `libmylib.so`), that is used when you compile the program;
- the **soname** (e.g. `libmylib.so.1`), that is used when you run the program and conatins the information on the version of the library;
- the **real** name (e.g. `libmylib.so.1.1.0`), that is the actual file that contains the library, and it normally contain also information on the release number.

Usually, files with link and soname are just symbolik links to the real file.

The complexity of the handling of versions and releases is of course necessary in professional code. If the library is used only locally you can avoid the complexity of using the three names, and in this case link,soname and real name coincide (e.g. `libmylib.so`).

In `DynamicLoading` we show another characteristics of shared libraries: the fact that you can dynamically load them, and extract objects contained in the library run time.

## Note ##
If you use versioning, you need to store in the library the information on the version of the library. This is done with the `-Wl,-soname` option of the linker. The option is used as follows:
```bash
g++ -shared -Wl,-soname,libmylib.so.1 -o libmylib.so.1.1.0 mylib.o
```

It indicates that `libmylib.so.1` is the soname of the library, while the real name of the library is `libmylib.so.1.1.0`. The soname is used by the dynamic linker to find the library when the program is run. The real name is used to store the library in the file system.

The option `-Wl,-soname` is used to pass the option `-soname` to the linker. The option `-soname` is used as follows:
```bash
-Wl,-soname=NAME
```
where `NAME` is the soname of the library.
or
```bash
-Wl,-soname,NAME
```
When you link your executable with a shared library, you normally use the link name, which is normally just a symbolic link to the soname, which is itself a link to the real name.
If the linked library has the soname information, the loader (also called dynamic linker) will use the soname to find the library, and not the link name. This is useful when you have to update the library, and you want to use the new version without recompiling the program.

Here is the synopsis of the option `-soname` of the linker:
```
-soname=name
When creating an ELF shared object, set the internal DT_SONAME field to the specified name. When an
executable is linked with a shared object which has a DT_SONAME field, then when the executable is run
the dynamic linker will attempt to load the shared object specified by the DT_SONAME field rather than
using the file name given to the linker.
```

#What do I learn here?#
- Some basic concepts of shared libraries.

0 comments on commit 02276a3

Please sign in to comment.