Release the GIL on CPU intensive methods
The Python Global Interpreter Lock (GIL) only lets one thread execute Python code at the time.
So when using threading with pure Python, the code is actually concurrent, but not parallel.
When running non Python code, such as the C code of libscip, the GIL can be realsed, allowing threading to have some level of parallelism.
In Cython, this can be done by wrapping C function calls with:
with nogil:
SCIPsolve(self._scip)
However, one needs to make sure that no Python code is executed when releasing the GIL. This can be hard for two reasons:
- Cython generates some Python code which can be hard to distinguish from C code;
- The GIL needs to be reacquired in all Python callbacks such as BranchRule
But scip itself is sequential. Maybe this would be helpful for concurrent scip, or for the parallelism that @stephenjmaher implemented.
This is not about SCIP parallelism because Python would not care about C threads that do not run Python code.
This is about parallelism of user code, such as solving multiple Scip models in parallel (perhaps need PARA_SCIP=ON), or more generally doing any other non-SCIP computation while SCIP is solving
ah ok, I see. Well, I don't know anything about this. I cannot help, sorry
I can send a PR in the future. Just wanted to open an issue to keep track of it in the meantime.
@CGraczyk, in case you are still interested in this feature, #505 is not a (full) fix for this.
I see, then i will reopen the issue, if you think this is nice to have feel free to open a new pull request.
I have a related problem because I am looking for a method to solve LPs in parallel using the Python threading module. Using the current pyscipopt module the LPs are effectively being solved one at a time due to the GIL. However, with a few modifications I have been able to run the solve function (src/pyscipopt/lp.pxi) in parallel using multiple threads. First, I have declared two functions in src/pyscipopt/scip.pxd with the nogil keyword:
SCIP_RETCODE SCIPlpiSolveDual(SCIP_LPI* lpi) nogil
SCIP_RETCODE SCIPlpiSolvePrimal(SCIP_LPI* lpi) nogil
Then I have changed the solve function like this:
def solve(self, dual=True):
"""Solves the current LP.
Keyword arguments:
dual -- use the dual or primal Simplex method (default: dual)
"""
cdef SCIP_LPI* lpi = self.lpi
cdef SCIP_RETCODE rc
if dual:
with nogil:
rc = SCIPlpiSolveDual(lpi)
else:
with nogil:
rc = SCIPlpiSolvePrimal(lpi)
PY_SCIP_CALL(rc)
cdef SCIP_Real objval
PY_SCIP_CALL(SCIPlpiGetObjval(self.lpi, &objval))
return objval
As far a I can see the whole SCIP_LPI interface could be rephrased in this manner so that the GIL can be released as soon as the SCIPlpiXXX functions are being entered. So I am wondering, since I don't really know much about the SCIP interna, is my solution really safe to use or am I missing something? If it is safe, what is the likelihood that SCIP_LPI will be rephrased so that the GIL is released as often as possible in official pyscipopt distribution?
@axelvonkamp, as you noticed, releasing the GIL in PySCIPOpt is not extremely hard, simply adding a number of nogil around (the GIL is already being re-aquired during Python callbacks).
For it to get into PySCIPOpt, it's mostly a matter of doing and testing it properly, but I personally don't have much time for it.
As far as SCIP parallelism is involved, I'm not sure when it safe, I believe compiling SCIP with -D PARASCIP=ON (it's already the case if you get SCIP/PySCIPOpt from conda) should be enough if you use separate Models in each threads (as opposed to multiple threads modifying a single Model).
Is this still an issue, @AntoinePrv?
Unsure but I have not be working with PySCIPOpt for a long time now. So possible future discussion can happen in a new issue.