PySCIPOpt icon indicating copy to clipboard operation
PySCIPOpt copied to clipboard

Release the GIL on CPU intensive methods

Open AntoinePrv opened this issue 5 years ago • 8 comments

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

AntoinePrv avatar Sep 15 '20 14:09 AntoinePrv

But scip itself is sequential. Maybe this would be helpful for concurrent scip, or for the parallelism that @stephenjmaher implemented.

fserra avatar Sep 16 '20 06:09 fserra

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

AntoinePrv avatar Sep 16 '20 14:09 AntoinePrv

ah ok, I see. Well, I don't know anything about this. I cannot help, sorry

fserra avatar Sep 16 '20 15:09 fserra

I can send a PR in the future. Just wanted to open an issue to keep track of it in the meantime.

AntoinePrv avatar Sep 16 '20 15:09 AntoinePrv

@CGraczyk, in case you are still interested in this feature, #505 is not a (full) fix for this.

AntoinePrv avatar Jul 01 '21 11:07 AntoinePrv

I see, then i will reopen the issue, if you think this is nice to have feel free to open a new pull request.

CGraczyk avatar Jul 01 '21 14:07 CGraczyk

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 avatar Oct 12 '21 09:10 axelvonkamp

@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).

AntoinePrv avatar Oct 12 '21 14:10 AntoinePrv

Is this still an issue, @AntoinePrv?

Joao-Dionisio avatar Jul 10 '23 13:07 Joao-Dionisio

Unsure but I have not be working with PySCIPOpt for a long time now. So possible future discussion can happen in a new issue.

AntoinePrv avatar Jul 18 '23 09:07 AntoinePrv