wiki:AppMultiThread

Version 27 (modified by davea, 14 years ago) (diff)

--

Multi-thread apps

The average number of cores per PC will increase over the next few years, possibly at a faster rate than the average amount of available RAM.

Depending on your application and project, it may be desirable to develop a multi-thread application. Possible reasons to do this:

  • Your application's memory footprint is large enough that, on some PCs, there's not enough RAM to run a separate copy of the app on each CPU.
  • You want to reduce the turnaround time of your jobs (either because of human factors, or to reduce server occupancy).

You may be able to use OpenMP, or languages like Titanium or Cilk, or libraries of multi-threaded numerical "kernels", to develop a multi-threaded app.

Thread priorities

You should set the priority of new threads to that of the main thread.

#ifdef MSVC
#define getThreadPriority() GetThreadPriority(GetCurrentThread())
#define setThreadPriority(num) SetThreadPriority(GetCurrentThread(),
num)
#else
#include <sys/resource.h>
#define getThreadPriority() getpriority(PRIO_PROCESS, 0)
#define setThreadPriority(num) nice(num)
#endif

getThreadPriority() is called before forking (by OpenMP in AQUA's case), and setThreadPriority() is then called by each worker thread.

Deploying a multi-threaded app version

BOINC uses the application planning mechanism to coordinate the scheduling of multi-threaded applications.

Suppose you've developed a multi-threaded program, and that it achieves a linear speedup on up to 64 processors, and no additional speedup beyond that. To deploy it:

  • Choose a "planning class" name for the program, say "par64" (see below).
  • Create an app version, specifying its plan class as "par64".
  • Link the following function into your scheduler (customized as needed):
    bool app_plan(SCHEDULER_REQUEST& sreq, const char* plan_class, HOST_USAGE& hu) {
        if (!strcmp(plan_class, "par64")) {
            // the following is for an app that can use anywhere
            // from 1 to 64 threads, can control this exactly,
            // and whose speedup is .95N
            // (on a uniprocessor, we'll use a sequential app if one is available)
            //
            int ncpus, nthreads;
            bool bounded;
    
            get_ncpus(sreq, ncpus, bounded);
            nthreads = ncpus;
            if (nthreads > 64) nthreads = 64;
            hu.avg_ncpus = nthreads;
            hu.max_ncpus = nthreads;
            sprintf(hu.cmdline, "--nthreads %d", nthreads);
            hu.flops = 0.95*sreq.host.p_fpops*nthreads;
            if (config.debug_version_select) {
                log_messages.printf(MSG_NORMAL,
                    "[version] Multi-thread app estimate %.2f GFLOPS\n",
                    hu.flops/1e9
                );
            }
            return true;
        }
    ...
    }
    

The BOINC client will schedule applications based on the average CPU usage returned by this function.