From c8d1d41f22d2d7407499a661dc0c55a5850f6078 Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Fri, 18 Aug 2023 21:45:38 -0400 Subject: [PATCH 1/2] Set OMP_PROC_BIND=false before calling omp_get_max_threads --- CHANGELOG.rst | 2 ++ galsim/utilities.py | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2d7ac497a16..4f3cf6c6f66 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -30,6 +30,8 @@ New Features Performance Improvements ------------------------ +- Work around an OMP bug that disables multiprocessing on some systems when omp_get_max_threads + is called. (#1241) Bug Fixes diff --git a/galsim/utilities.py b/galsim/utilities.py index de96c0a5b18..69a2568c216 100644 --- a/galsim/utilities.py +++ b/galsim/utilities.py @@ -1842,6 +1842,11 @@ def set_omp_threads(num_threads, logger=None): # Tell OpenMP to use this many threads if logger: logger.debug('Telling OpenMP to use %d threads',num_threads) + + # Cf. comment in get_omp_threads. Do it here too. + var = "OMP_PROC_BIND" + if var not in os.environ: + os.environ[var] = "false" num_threads = _galsim.SetOMPThreads(num_threads) # Report back appropriately. @@ -1860,6 +1865,18 @@ def get_omp_threads(): :returns: num_threads """ + # Some OMP implemenations have a bug where if omp_get_max_threads() is called + # (which is what this function does), it sets something called thread affinity. + # The upshot of that is that multiprocessing (i.e. not even omp threading) is confined + # to a single thread. Yeah, it's idiotic, but that seems to be the case. + # The only solution found by Eli, who looked into it prett hard, is to set the env + # variable OMP_PROC_BIND to "false". This seems to stop the bad behavior. + # So we do it here always before calling GetOMPThreads. + # If this breaks someone valid use of this variable, let us know and we can try to + # come up with another solution, but without this lots of multiprocessing breaks. + var = "OMP_PROC_BIND" + if var not in os.environ: + os.environ[var] = "false" return _galsim.GetOMPThreads() class single_threaded: From deaf6696be51d100d5771c0acd093289a34f918a Mon Sep 17 00:00:00 2001 From: Mike Jarvis Date: Fri, 18 Aug 2023 23:42:22 -0400 Subject: [PATCH 2/2] coverage --- galsim/utilities.py | 6 +++--- tests/test_sensor.py | 11 +++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/galsim/utilities.py b/galsim/utilities.py index 69a2568c216..de10da32471 100644 --- a/galsim/utilities.py +++ b/galsim/utilities.py @@ -1867,9 +1867,9 @@ def get_omp_threads(): """ # Some OMP implemenations have a bug where if omp_get_max_threads() is called # (which is what this function does), it sets something called thread affinity. - # The upshot of that is that multiprocessing (i.e. not even omp threading) is confined - # to a single thread. Yeah, it's idiotic, but that seems to be the case. - # The only solution found by Eli, who looked into it prett hard, is to set the env + # The upshot of that is that multiprocessing (i.e. not even just omp threading) is confined + # to a single hardware thread. Yeah, it's idiotic, but that seems to be the case. + # The only solution found by Eli, who looked into it pretty hard, is to set the env # variable OMP_PROC_BIND to "false". This seems to stop the bad behavior. # So we do it here always before calling GetOMPThreads. # If this breaks someone valid use of this variable, let us know and we can try to diff --git a/tests/test_sensor.py b/tests/test_sensor.py index e62c295538c..cba6e0a887d 100644 --- a/tests/test_sensor.py +++ b/tests/test_sensor.py @@ -1208,6 +1208,17 @@ def test_omp(): assert "OpenMP reports that it will use 1 threads" in cl.output assert "Unable to use multiple threads" in cl.output + # This is really just for coverage. Check that OMP_PROC_BIND gets set properly. + with mock.patch('os.environ', {}): + assert os.environ.get('OMP_PROC_BIND') is None + galsim.get_omp_threads() + assert os.environ.get('OMP_PROC_BIND') == 'false' + + with mock.patch('os.environ', {}): + assert os.environ.get('OMP_PROC_BIND') is None + galsim.set_omp_threads(4) + assert os.environ.get('OMP_PROC_BIND') == 'false' + @timer def test_big_then_small():