Skip to content

Commit

Permalink
Add a separate function to control the default number of threads. (li…
Browse files Browse the repository at this point in the history
…bigl#1684)

The default number of threads for parallel for loops can be controlled
explicitly by the user. Either by calling igl::default_num_thread with a
user-defined thread count before entering any other libigl function, or
by setting the environment variable IGL_NUM_THREADS.
  • Loading branch information
cheng-chi authored Feb 16, 2021
1 parent 6f02331 commit 38a1235
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 27 deletions.
54 changes: 54 additions & 0 deletions include/igl/default_num_threads.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// This file is part of libigl, a simple c++ geometry processing library.
//
// Copyright (C) 2021 Jérémie Dumas <[email protected]>
//
// This Source Code Form is subject to the terms of the Mozilla Public License
// v. 2.0. If a copy of the MPL was not distributed with this file, You can
// obtain one at http://mozilla.org/MPL/2.0/.

#include "default_num_threads.h"

#include <cstdlib>
#include <thread>

IGL_INLINE unsigned int igl::default_num_threads(unsigned int user_num_threads) {
// Thread-safe initialization using Meyers' singleton
class MySingleton {
public:
static MySingleton &instance(unsigned int force_num_threads) {
static MySingleton instance(force_num_threads);
return instance;
}

unsigned int get_num_threads() const { return m_num_threads; }

private:
MySingleton(unsigned int force_num_threads) {
// User-defined default
if (force_num_threads) {
m_num_threads = force_num_threads;
return;
}
// Set from env var
if (char *env_str = getenv("IGL_NUM_THREADS")) {
const int env_num_thread = atoi(env_str);
if (env_num_thread > 0) {
m_num_threads = static_cast<unsigned int>(env_num_thread);
return;
}
}
// Guess from hardware
const unsigned int hw_num_threads = std::thread::hardware_concurrency();
if (hw_num_threads) {
m_num_threads = hw_num_threads;
return;
}
// Fallback when std::thread::hardware_concurrency doesn't work
m_num_threads = 8u;
}

unsigned int m_num_threads = 0;
};

return MySingleton::instance(user_num_threads).get_num_threads();
}
38 changes: 38 additions & 0 deletions include/igl/default_num_threads.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// This file is part of libigl, a simple c++ geometry processing library.
//
// Copyright (C) 2021 Jérémie Dumas <[email protected]>
//
// This Source Code Form is subject to the terms of the Mozilla Public License
// v. 2.0. If a copy of the MPL was not distributed with this file, You can
// obtain one at http://mozilla.org/MPL/2.0/.
#ifndef IGL_DEFAULT_NUM_THREADS_H
#define IGL_DEFAULT_NUM_THREADS_H
#include "igl_inline.h"

namespace igl
{

///
/// Returns the default number of threads used in libigl. The value returned by the first call to
/// this function is cached. The following strategy is used to determine the default number of
/// threads:
/// 1. User-provided argument force_num_threads if != 0.
/// 2. Environment variable IGL_NUM_THREADS if > 0.
/// 3. Hardware concurrency if != 0.
/// 4. A fallback value of 8 is used otherwise.
///
/// @note It is safe to call this method from multiple threads.
///
/// @param[in] force_num_threads User-provided default value.
///
/// @return Default number of threads.
///
IGL_INLINE unsigned int default_num_threads(unsigned int force_num_threads = 0);

} // namespace igl

#ifndef IGL_STATIC_LIBRARY
#include "default_num_threads.cpp"
#endif

#endif
54 changes: 27 additions & 27 deletions include/igl/parallel_for.h
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// This file is part of libigl, a simple c++ geometry processing library.
//
//
// Copyright (C) 2016 Alec Jacobson <[email protected]>
//
// This Source Code Form is subject to the terms of the Mozilla Public License
// v. 2.0. If a copy of the MPL was not distributed with this file, You can
//
// This Source Code Form is subject to the terms of the Mozilla Public License
// v. 2.0. If a copy of the MPL was not distributed with this file, You can
// obtain one at http://mozilla.org/MPL/2.0/.
#ifndef IGL_PARALLEL_FOR_H
#define IGL_PARALLEL_FOR_H
Expand All @@ -19,7 +19,7 @@ namespace igl
// for loop. If the inner block of a for-loop can be rewritten/encapsulated in
// a single (anonymous/lambda) function call `func` so that the serial code
// looks like:
//
//
// for(int i = 0;i<loop_size;i++)
// {
// func(i);
Expand All @@ -38,13 +38,13 @@ namespace igl
// Returns true iff thread pool was invoked
template<typename Index, typename FunctionType >
inline bool parallel_for(
const Index loop_size,
const Index loop_size,
const FunctionType & func,
const size_t min_parallel=0);
// PARALLEL_FOR Functional implementation of an open-mp style, parallel for
// loop with accumulation. For example, serial code separated into n chunks
// (each to be parallelized with a thread) might look like:
//
//
// Eigen::VectorXd S;
// const auto & prep_func = [&S](int n){ S = Eigen:VectorXd::Zero(n); };
// const auto & func = [&X,&S](int i, int t){ S(t) += X(i); };
Expand All @@ -59,13 +59,13 @@ namespace igl
// {
// accum_func(t);
// }
//
//
// Inputs:
// loop_size number of iterations. I.e. for(int i = 0;i<loop_size;i++) ...
// prep_func function handle taking n >= number of threads as only
// argument
// argument
// func function handle taking iteration index i and thread id t as only
// arguments to compute inner block of for loop I.e.
// arguments to compute inner block of for loop I.e.
// for(int i ...){ func(i,t); }
// accum_func function handle taking thread index as only argument, to be
// called after all calls of func, e.g., for serial accumulation across
Expand All @@ -74,13 +74,13 @@ namespace igl
// thread pooling should be attempted {0}
// Returns true iff thread pool was invoked
template<
typename Index,
typename PrepFunctionType,
typename FunctionType,
typename AccumFunctionType
typename Index,
typename PrepFunctionType,
typename FunctionType,
typename AccumFunctionType
>
inline bool parallel_for(
const Index loop_size,
const Index loop_size,
const PrepFunctionType & prep_func,
const FunctionType & func,
const AccumFunctionType & accum_func,
Expand All @@ -89,6 +89,8 @@ namespace igl

// Implementation

#include "default_num_threads.h"

#include <cmath>
#include <cassert>
#include <thread>
Expand All @@ -97,7 +99,7 @@ namespace igl

template<typename Index, typename FunctionType >
inline bool igl::parallel_for(
const Index loop_size,
const Index loop_size,
const FunctionType & func,
const size_t min_parallel)
{
Expand All @@ -110,12 +112,12 @@ inline bool igl::parallel_for(
}

template<
typename Index,
typename Index,
typename PreFunctionType,
typename FunctionType,
typename FunctionType,
typename AccumFunctionType>
inline bool igl::parallel_for(
const Index loop_size,
const Index loop_size,
const PreFunctionType & prep_func,
const FunctionType & func,
const AccumFunctionType & accum_func,
Expand All @@ -125,14 +127,12 @@ inline bool igl::parallel_for(
if(loop_size==0) return false;
// Estimate number of threads in the pool
// http://ideone.com/Z7zldb
const static size_t sthc = std::thread::hardware_concurrency();
const size_t nthreads =
#ifdef IGL_PARALLEL_FOR_FORCE_SERIAL
0;
const size_t nthreads = 1;
#else
loop_size<min_parallel?0:(sthc==0?8:sthc);
const size_t nthreads = igl::default_num_threads();
#endif
if(nthreads==0)
if(loop_size<min_parallel || nthreads<=1)
{
// serial
prep_func(1);
Expand All @@ -142,10 +142,10 @@ inline bool igl::parallel_for(
}else
{
// Size of a slice for the range functions
Index slice =
Index slice =
std::max(
(Index)std::round((loop_size+1)/static_cast<double>(nthreads)),(Index)1);

// [Helper] Inner loop
const auto & range = [&func](const Index k1, const Index k2, const size_t t)
{
Expand Down Expand Up @@ -181,7 +181,7 @@ inline bool igl::parallel_for(
return true;
}
}

//#ifndef IGL_STATIC_LIBRARY
//#include "parallel_for.cpp"
//#endif
Expand Down

0 comments on commit 38a1235

Please sign in to comment.