oneapi-src / oneTBB

oneAPI Threading Building Blocks (oneTBB)
https://oneapi-src.github.io/oneTBB/
Apache License 2.0
5.67k stars 1.02k forks source link

Provide C or/and envvar interface to global_control #102

Open anton-malakhov opened 5 years ago

anton-malakhov commented 5 years ago

Problem

As TBB is getting adopted wider as a composable parallelism solution outside of traditional C++ world, the need is growing in controlling its global settings for applications, which are not written in C++. For example, MKL (which has TBB threading layer) is a library with C interface and as such is used in C applications and libraries. In turn, MKL is used in many other languages and libraries, for example, Numpy and Julia. They don't necessary know or want to know anything about how threading is implemented and what is the user's choice of the threading layer at run-time while users&applications don't want to complicate their simple application scripts with a C++ code that interacts with TBB's global_control class. They all need a simpler way to interact with TBB's global settings. For C-based projects like Numpy and Julia-lang, there should be a way to control TBB's behavior in their sub-components using a C API. For scripting languages, the function names and usage model should be friendly to use with dynamic loading of C symbols like it is for TBB_runtime_interface_version(). For Python, the tbb4py module provides a way (mostly) for a user or Python scripts to control TBB's global behavior. However, many projects do not have direct dependence on TBB, e.g. Numpy optionally depends on MKL, which in turn optionally depends on TBB, thus it doesn't want to introduce tbb4py as their hard-coded requirement. So, their ideal solution would be introduction of environment variables to control TBB defaults directly the same way as sklearn/joblib/loky suggests to do it for other threaded libraries: https://github.com/numpy/numpy/issues/11826#issuecomment-437291454. Environment variables allow to avoid complications with detection and dynamic loading of TBB symbols by passing global settings 'just in case' if TBB will be loaded later. Semantics of both global_control and envvars is global anyway and thus it looks quite natural to initialize global_control's with default values from environment variables. Suggestion to introduce environment variables in applications/libraries, which use TBB directly, just introduces more problems than it solves. For example, MKL has these variables but it might not be the only component, which uses/controls TBB in an application, the other popular library, which uses TBB directly and co-exists along with Numpy is Numba. Thus having no single point of controlling TBB settings leads to conflicts between independent components, which implement such control themselves.

Proposals

  1. Implement pure C interface for all the global_control settings but possibly limited to single scope in order to avoid complications with life time and scope where settings are effective. So that it will be friendly with dynamic loading and manipulating settings in a single function, w/o the need to release the scope later.
  2. Complementary/independent from p1 suggestion. Implement a set of environment variables which provide initial default values for global_control settings: TBB_NUM_THREADS and TBB_STACK_SIZE
akukanov commented 5 years ago

The case for C-friendly and dlopen-friendly API for global settings seems valid to me. If such a contribution is made, it will be considered :) The only high-level request I have for it is to make the API RAII-like, e.g.

tbb_control_handle TBB_set_global_control(tbb_global_parameter p, size_t value);
size_t TBB_get_global_control_active_value(tbb_global_parameter p);
void TBB_remove_global_control(tbb_control_handle h);
akukanov commented 5 years ago

As for environment variables, the main issues with using those for libraries- and the main reasons why we did not add TBB_NUM_THREADS in the first place and generally strive to avoid envirable creep in TBB - are:

These are not just artificial issues, but things learned hard way in the past with e.g. our OpenMP runtime. Any solution for external control over the number of threads used by TBB needs to be done in a way that addresses the first two:

The best idea I could come up with - and I still do not like it - is to extend tbb::global_control with a parameter to specify the name of environment variable which value should be used to set max_allowed_parallelism. In other words, it's just another way for a program to specify max_allowed_parallelism. And I do not like it primarily because it could be attractive for library developers, causing the problems you have described.

Added after posting: note that the above idea is essentially just a syntax sugar for what an application or high-level component is already able to do: read an environment variable of choice and use its value to set tbb::global_control(max_allowed_parallelism).

Thus having no single point of controlling TBB settings leads to conflicts between independent components, which implement such control themselves.

In fact, such a single point of controlling the number of threads does exist - it's the process affinity mask which TBB respects. An application or a high-level component might even temporarily change this mask, initialize TBB, and restore the one that it wants to use for the whole process.

arunparkugan commented 2 months ago

@akukanov is this issue still relevant?

akukanov commented 1 month ago

The described issue has not been addressed, to the best of my knowledge. However, for the first part of the proposal – C API for global settings – I do not know if there is enough interest, and the second part – adding environment variables – would be a somewhat risky change in TBB behavior, which needs strong justification and careful consideration.

I am fine with closing the issue; it can always be re-opened or a new one created if needed.