Advanced Introduction to C++, Scientific Computing and Machine Learning




Claudius Gros, SS 2024

Institut für theoretische Physik
Goethe-University Frankfurt a.M.

C++ : Processes, Threads & Plotting

processes vs. threads



processes

threads

parallelization

threads basics

Copy Copy to clipboad
Downlaod Download
/* compile with 
 * g++ -std=c++11 -pthread test.cpp 
 */
 
#include <iostream>
#include <thread>
#include <cmath>
#define VARIABLE_NAME(x) #x
using namespace std;
 
const int globalN = 1000000;
 
/* calculate the golden number through a continued fraction expansion */
void goldenN(string threadName) 
{
cout << "subroutine  " << __FUNCTION__ << "()  launched by thread --" << threadName << "--\n\n"; 
int N = globalN;
double goldenNumber = 1.0;
for (int ii = 1; ii<N; ii++)
  {
  goldenNumber = 1.0+1.0/goldenNumber;
  if (ii%(N/10)==0)
    printf("goldenNumber: continued fraction (exact) %20.16f %20.16f\n",goldenNumber,(1.0+sqrt(5.0))/2.0);
  }
  // do stuff...
}
 
/* calculate exp(x) through a limit sequence */
void natural(double x, string threadName)
{
cout << "subroutine  " << __FUNCTION__ << "()  launched by thread --" << threadName << "--\n\n"; 
int N = globalN;
double exponential = 1.0;
for (int ii = 1; ii<N; ii++)
  {
  exponential = exponential*(1.0+x*1.0/N);
  if (ii%(N/10)==0)
    printf(" exponential: limit sequence (exact) %20.16f %20.16f\n",exponential,exp(x));
  }
    printf(" exponential: limit sequence (exact) %20.16f %20.16f\n",exponential,exp(x));
}
 
int main() 
{
  thread  first(goldenN,VARIABLE_NAME(first));       // spawn new thread that calls goldenN()
  thread second(natural,1.0,VARIABLE_NAME(second));  // spawn new thread that calls natural(1.0)
 
  printf("\n main(); goldenN() and natural() now execute concurrently %d times..\n\n",globalN);
 
  // synchronize threads:
  first.join();                // pauses until first finishes
  second.join();               // pauses until second finishes
 
  printf("goldenN() and natural() completed.\n");
 
  return 1;
}

avoiding thread interference

Copy Copy to clipboad
Downlaod Download
/* compile with 
 * g++ -std=c++11 -pthread test.cpp 
 * test by commenting out the mutex lock
 */
#include <iostream>       // cout
#include <thread>         // thread
#include <mutex>          // mutex
 
using namespace std;
 
mutex mtx;           // mutex for critical section
 
void printBlock(int n, char c) 
{
// critical section (exclusive access to cout signaled by locking mtx)
// order of lines printed may vary, but characters are never mixed
  mtx.lock();
  for (int i=0; i<n; ++i) 
    cout << c; 
  cout << '\n';
  mtx.unlock();
}
 
int main ()
{
  const int N =20;
  thread manyThreads[N];    // array of threads
/*
  mtx.lock();                // testing (1)
*/
  for (int i=0;i<N;i++)
    {
    manyThreads[i] = thread(printBlock,50,(char)(50+i));
    }
//
  for (int i=0;i<N;i++)
    manyThreads[i].join();
/*
  mtx.unlock();              // testing (1)
*/
  return 0;
}

threads and CPUs

Copy Copy to clipboad
Downlaod Download

/* compile with 
 * g++ -std=c++11 -pthread test.cpp 
 */
#include <iostream>
#include <thread>        // C++11 threading
#include <chrono>        // C++11 times
using namespace std;
 
void justSleeping(int myNumber) 
{
 thread::id this_id = this_thread::get_id();   // every thread has its own id
 int cpuNumber = sched_getcpu();
//
 cout << "thread " << myNumber << " with id --" << this_id 
      << "-- runs in " << __FUNCTION__ << "() on cpu # " 
      << cpuNumber << endl;
//
 this_thread::sleep_for(chrono::seconds(3));  // sleep_for() works with chrono
}
 
void doingStuff(int myNumber) 
{
 thread::id this_id = this_thread::get_id();
 int cpuNumber = sched_getcpu();
//
 cout << "thread " << myNumber << " with id --" << this_id 
      << "-- runs in " << __FUNCTION__ << "() on cpu # " 
      << cpuNumber << endl;
//
 double rr = 1.0;
 for (int ii = 1; ii<900000000; ii++)
   rr = 1.0+1.0/rr;
}
 
int main() 
{
 unsigned int nCPU = 1 * std::thread::hardware_concurrency();
 printf("%d concurrent threads are supported.\n",nCPU);
 thread manyThreads[nCPU];       // array of threads
// -------------------------
// starting time measurement (chrono:: for static functions in chrono)
// -------------------------
 chrono::time_point<chrono::system_clock> end, start = chrono::system_clock::now();
// -------------------------
 for (int i=0;i<nCPU;i++)
   {
//   manyThreads[i] = thread(justSleeping,i);
   manyThreads[i] = thread(doingStuff,i);
   }
//
 for (int i=0;i<nCPU;i++)
   manyThreads[i].join();
//
// ---------------------
// end time measurement 
// ---------------------
 end = chrono::system_clock::now();
 chrono::duration<double> elapsed_seconds = end-start;       
 time_t end_time = chrono::system_clock::to_time_t(end);     // duration -- seconds
 cout << "finished computation at " << ctime(&end_time)
      << "elapsed time: " << elapsed_seconds.count() << "s\n";
// ---------------------
}


fork() versus thread()

Copy Copy to clipboad
Downlaod Download
#include <stdio.h>
#include <unistd.h>      // fork() (linux), sleep()
#include <sys/types.h>   // getpid() (not univeral)
using namespace std;
 
int main()
{
 const long bigNumber = 4000000000;
 double   rr = 1.0;   // variables valid for both processes
//
 printf("# before creating a fork\n");
// the original process has an internal PID given by the system,
// close to the one seen by 'top', the PID of the child is zero
 pid_t pid_of_first_fork = fork();        
//
 pid_t pid_of_second_fork = fork();        
 printf("# after creating the second fork : %6d %6d | %6d\n",
    (int)pid_of_first_fork,(int)pid_of_second_fork,getpid());
//
 for (long ii = 1; ii<bigNumber; ii++)
   {
   rr = 1.0+1.0/rr;
   if (ii*2==bigNumber)
     if ((int)pid_of_first_fork==0)
       if ((int)pid_of_second_fork==0)
         printf("\n");
   }
//
 printf("# finishing now                  : %6d %6d\n",(int)pid_of_first_fork,(int)pid_of_second_fork);
//
 if ((int)pid_of_first_fork>0)       // parent process finshes last (just for prompt)
   if ((int)pid_of_second_fork>0)
     sleep(4);
return 1;
}

mixing forks with threading

Copy Copy to clipboad
Downlaod Download


/* compile with 
 * g++ -std=c++11 -pthread test.cpp 
 */
#include <iostream>
#include <iomanip>       // IO manipulations
#include <thread>        // C++11 threading
#include <chrono>        // C++11 times
#include <unistd.h>      // fork() (linux), sleep()
#include <sys/types.h>   // getpid() (not univeral)
using namespace std;
 
void justSleeping(int myNumber) 
{
 thread::id this_id = this_thread::get_id();   // every thread has its own id
 int cpuNumber = sched_getcpu();
//
 cout << "thread " << myNumber << " with id --" << this_id 
      << "-- runs  " << __FUNCTION__ << "()  on cpu # " 
      << cpuNumber << endl;
//
 this_thread::sleep_for(chrono::seconds(3));  // sleep_for() works with chrono
}
 
void doing__stuff(int myNumber) 
{
 thread::id this_id = this_thread::get_id();
 int cpuNumber = sched_getcpu();
//
 cout << "thread " << myNumber << " with id --" << this_id 
      << "-- runs  " << __FUNCTION__ << "()  on cpu # " 
      << cpuNumber << endl;
//
 double rr = 1.0;
 for (int ii = 1; ii<900000000; ii++)
   rr = 1.0+1.0/rr;
}
 
int main() 
{
 unsigned int nCPU = 1 * std::thread::hardware_concurrency();
 printf("%d concurrent threads are supported.\n\n", nCPU);
//
 thread manyThreads[nCPU];                        // array of threads
 chrono::time_point<chrono::system_clock> end,    // finishing time
         start = chrono::system_clock::now();     // staring time
// -------------------------
// forking before threading
// -------------------------
  pid_t pid_of_fork = fork(); 
  printf("# after creating the fork : %6d | %6d\n\n", (int)pid_of_fork, getpid());
// -------------------------
// threading
// -------------------------
 for (int i=0;i<nCPU;i++)
   {
   if (pid_of_fork==0)           // inside child fork?
     manyThreads[i] = thread(justSleeping,i);
   else
     manyThreads[i] = thread(doing__stuff,i);
   this_thread::sleep_for(chrono::milliseconds(200)); 
   }
//
   if (pid_of_fork==0)           // a delay of ten seconds
     sleep(10);
// ---------------------
// fork after threading
// ---------------------
 pid_t pid_of_next_fork;
 if (pid_of_fork==0)         
   pid_of_next_fork = fork(); 
 thread::id thread_id;
 thread::id my_id = this_thread::get_id();   // id of current thread
//
 for (int i=0;i<nCPU;i++)
   {
   thread_id = manyThreads[i].get_id();    // thread --> thread id
//
   cout << setw(6)  << getpid()         << "  " 
        << setw(6)  << pid_of_fork      << "  " 
        << setw(6)  << pid_of_next_fork << "  " 
        << i <<  "  " << manyThreads[i].joinable()
        <<  "  " << thread_id 
        <<  "  " << my_id
        << endl;
//
   this_thread::sleep_for(chrono::milliseconds(200)); 
   manyThreads[i].join();
   }
//
// ---------------------
// end time measurement 
// ---------------------
 end = chrono::system_clock::now();
 chrono::duration<double> elapsed_seconds = end-start;       
 time_t end_time = chrono::system_clock::to_time_t(end);     // duration -- seconds
//
 cout << "fork " << getpid() << " used "<< elapsed_seconds.count()
      << " seconds; timestamp :: " << ctime(&end_time) << "\n";
// ---------------------
}

threading of individual loops

Copy Copy to clipboad
Downlaod Download
/* compile and run with  (look at real time used)
 * g++ -fopenmp test.cpp
 * time ./a.out 90000000
 */
#include <iostream>      // standard IO
#include <stdio.h>       // for printf
#include <cstdlib>       // for atof
#include <math.h>        // for the math
#include <time.h>        // ('clock()' measures time in nanoseconds)
using namespace std;
 
/* **************************************** */
/* *****      time in seconds       ******* */
/* **************************************** */
 
double timeInSeconds()
{
const double oneSecond = 1.0 / CLOCKS_PER_SEC;
static long int lastTime = clock();
long int actual_time = clock();
long int elapsed_time = actual_time - lastTime;
lastTime = actual_time;
return elapsed_time * oneSecond;     // time passed since last call
}    
 
/* **************************************** */
/* *****            main            ******* */
/* **************************************** */
     
int main(int argLength, char* argValues[])  // parsing command line arguments
{
 if (argLength==1)
   {
   cout << "please run with an integer  argument (loop size)\n";
   return 0;
   }
 long int loopSize = (long)(atof(argValues[1]));  
 double *results = new double[loopSize];
 
 int tenPerCent = int(loopSize/10.0);
//
 timeInSeconds();                           // initialized
//
#pragma omp parallel for                    // test with and without
 for (size_t i=0;i<loopSize;i++)
   {
   double rr = i*1.0/(1.0+i);
   results[i] =  cos(cos(rr));
   if ((i%tenPerCent)==0)
     printf("  %3.1f of loop completed\n", i*1.0/loopSize);
   }
  printf("time used: %8.4f (seconds)\n", timeInSeconds());
//
 return 1;
}

system calls

Copy Copy to clipboad
Downlaod Download
#include <stdio.h>
#include <iostream>      /* strings              */
#include <unistd.h>      /* fork (linux), sleep  */
#include <sys/types.h>   /* getpid()             */
#include <stdlib.h>      /* system, srand, rand  */
#include <time.h>        /* for the time         */
using namespace std;
 
int main(int argLength, char* argValues[])   // parsing command line arguments
{
 srand( (unsigned)time( NULL ) );  // initializing the random number
 const double oneSecond = 1.0 / CLOCKS_PER_SEC;
 long starting_time = clock();
 int myPID = getpid();
 printf("\n");
 printf("# I am the process %d\n",myPID);
//
// --- renice the actual process
//
 int myNice =(rand()%20);          // renice [0:19], only higer values (lower prriority) allowed
 char reniceString[66];            // array of char for the renice string
 sprintf(reniceString, "renice %d -p %d",myNice,myPID); 
 printf("%s\n",reniceString);
 system(reniceString);             // sytem call : renice
//
// --- do stuff
//
 const long bigNumber = 1000000000;
 double   rr = 1.0;   // variables valid for both processes
 for (long ii = 1; ii<bigNumber; ii++)
   rr = 1.0+1.0/rr;
//
// --- finish and continue
//
 printf("finishing; priority, time : %2d %10.2f seconds\n",myNice,(clock()-starting_time)*oneSecond);
 system(argValues[0]);  // system call : start next program; system("./a.out")
 return 1;
}

plotting from within C++ using gnuplot

Copy Copy to clipboad
Downlaod Download
/* using gnuplot directly from C++
 * by writing to gnuplot via a pipe (stream) created by popen()
 * the data is stored inline in gnuplot as data blocks
 */
#include <iostream>
#include <sstream>
#include <stdio.h>
#include <cstdlib>
#include <math.h>
using namespace std;
 
// === 
// === (x,y) string for inline data input for gnuplot 
// ===       the data block created has the name 'dataName'
// === 
string xyValuesGnu(double xValues[],double yValues[],int nData, string dataName)
{
 stringstream ss;
 ss << dataName << " << " << "EOD\n";        // defining data block
 for (int i=0;i<nData;i++)
   ss << xValues[i] << " " << yValues[i] << endl;
 ss << "EOD\n";                           // terminating data block
//
 return ss.str();
}
 
// === 
// === create some nice data
// === 
 
void createData(double xValues[],double yValues[],int nData,double width,double omega,double r0)
{
 double angle, radius;
 for (int i=0;i<nData;i++)
   {
   angle  = i*M_PI*2.0/nData;
   radius = width*sin(omega*angle) + r0;
   xValues[i] = radius*cos(angle);
   yValues[i] = radius*sin(angle);
   }
}
 
int main()
{
 const int nData = 400;
 string dataToPlot, dataName;
 double x[nData];
 double y[nData];
 
 FILE *pipeGnu = popen("gnuplot -persist", "w");   // streaming to gnuplot
 
// first curve
 dataName = "$data_1";
 createData(x,y,nData,3.0,9.0,1.0);                // create some data
 dataToPlot = xyValuesGnu(x,y,nData,dataName);     // data to string
 fprintf(pipeGnu, "%s\n",dataToPlot.c_str());                     // input data block
 fprintf(pipeGnu, "plot \"%s\" w lines lw 3\n",dataName.c_str()); // plot the data block
 fflush(pipeGnu);
 
// second curve
 dataName = "$data_2";
 createData(x,y,nData,2.0,11.0,3.0);                // create some data
 dataToPlot = xyValuesGnu(x,y,nData,dataName);      // data to string
 fprintf(pipeGnu, "%s\n",dataToPlot.c_str());                       // input data block
 fprintf(pipeGnu, "replot \"%s\" w lines lw 3\n",dataName.c_str()); // plot the data block
 fflush(pipeGnu);
 
 fclose(pipeGnu);      // closing the pipe to gnuplot
 return 1;
}

dynamic plotting using gnuplot

Copy Copy to clipboad
Downlaod Download
/* animation by using gnuplot directly from C++
 * by writing to gnuplot via a pipe (stream) created by popen()
 * the data is stored inline in gnuplot as data blocks
 */
#include <iostream>
#include <sstream>
#include <stdio.h>
#include <cstdlib>
#include <math.h>
#include <unistd.h>
using namespace std;
 
// ===
// === (x,y) string for inline data input for gnuplot
// ===       the data block created has the name 'dataName'
// ===
string xyValuesGnu(double xValues[],double yValues[],int nData, string dataName)
{
 stringstream ss;
 ss << dataName << " << " << "EOD\n";       // defining data block
 for (int i=0;i<nData;i++)
   ss << xValues[i] << " " << yValues[i] << endl;
 ss << "EOD\n";                             // terminating data block
//
 return ss.str();
}
 
// === 
// === create some nice data
// === 
 
void createData(double xValues[],double yValues[],int nData,double r0,double a0, double r1)
{
 double angle, radius;
 double x0 = r0*cos(a0);
 double y0 = r0*sin(a0);
 for (int i=0;i<nData;i++)
   {
   angle  = i*M_PI*2.0/(nData-1.0);
   xValues[i] = x0 + r1*cos(angle);
   yValues[i] = y0 + r1*sin(angle);
   }
}
 
int main()
{
 const int nData = 100;
 const int nAnim = 500;
 string dataToPlot;
 string dataName = "$dataAnim";
 double x[nData];
 double y[nData];
 
 FILE *pipeGnu = popen("gnuplot", "w");            // streaming to gnuplot
 
// starting
 if (1==2)                                         // option for animated gif
   {
   fprintf(pipeGnu, "reset\n");            
   fprintf(pipeGnu, "set term gif animate\n");
   fprintf(pipeGnu, "set output \"animate.gif\"\n");
   }
//
 fprintf(pipeGnu, "set xrange [-5:5]\n"); 
 fprintf(pipeGnu, "set yrange [-5:5]\n"); 
//
for (int iAnim=0;iAnim<nAnim;iAnim++)
  {
  double changingAngle = iAnim*10*M_PI/nAnim;
  createData(x,y,nData,2.5,changingAngle,2);
  dataToPlot = xyValuesGnu(x,y,nData,dataName);    // data to string
  fprintf(pipeGnu, "%s\n",dataToPlot.c_str());     // change data block
//
  if (iAnim==0)
    fprintf(pipeGnu, "plot \"%s\" w lines lw 10\n",dataName.c_str()); // plot the data block
  else
    fprintf(pipeGnu, "replot\n");                                     // replot 
//
  fflush(pipeGnu);
  usleep(20000);                                   // sleep for microseconds
  }
 
fclose(pipeGnu);      // closing the pipe to gnuplot
return 1;
}