[pypy-commit] pypy optimize-locks: Meh. It's subtle code...
arigo
noreply at buildbot.pypy.org
Sun Feb 8 20:33:32 CET 2015
Author: Armin Rigo <arigo at tunes.org>
Branch: optimize-locks
Changeset: r75770:a0ffa88963d1
Date: 2015-02-08 20:33 +0100
http://bitbucket.org/pypy/pypy/changeset/a0ffa88963d1/
Log: Meh. It's subtle code...
diff --git a/rpython/rlib/rthread.py b/rpython/rlib/rthread.py
--- a/rpython/rlib/rthread.py
+++ b/rpython/rlib/rthread.py
@@ -3,7 +3,7 @@
from rpython.translator import cdir
import py, sys
from rpython.rlib import jit, rgc
-from rpython.rlib.debug import ll_assert
+from rpython.rlib.debug import ll_assert, fatalerror, debug_print
from rpython.rlib.objectmodel import we_are_translated, specialize
from rpython.rlib.objectmodel import CDefinedIntSymbolic
from rpython.rtyper.lltypesystem.lloperation import llop
@@ -62,7 +62,8 @@
releasegil=True) # release the GIL
# another set of functions, this time in versions that don't cause the
-# GIL to be released. To use to handle the GIL lock itself.
+# GIL to be released. This was used to handle the GIL lock itself,
+# but nowadays it's done differently, so it's really only used in tests.
c_thread_acquirelock_NOAUTO = llexternal('RPyThreadAcquireLock',
[TLOCKP, rffi.INT], rffi.INT,
_nowrapper=True)
@@ -128,27 +129,104 @@
_immutable_fields_ = ["_lock"]
def __init__(self, ll_lock):
+ # "_num_acquires" is the number of times we have started an
+ # "acquire" on this lock, minus the number of times we have
+ # done a "release", multiplied by two and with one added iff
+ # the low-level ll_lock is actually acquired. As long as
+ # there is no contention, _num_acquires will only take the
+ # values 0 or 2, and the ll_lock will not be used (and the GIL
+ # will not be released because of operations on this lock).
self._lock = ll_lock
+ self._num_acquires = 0
+
+ def _acquire_ll_lock(self, microseconds, intr_flag):
+ n = self._num_acquires
+ #debug_print("_really_acquire, n =", n)
+ if n == 2:
+ # if n is precisely 2, we must do a double acquire: one
+ # for really acquiring the lock, and another for blocking
+ # until it is released by someone else. The first one
+ # should never block and we *must* not release the GIL
+ # around it. Only afterwards will the lock be in a
+ # consistent state again: _num_acquires odd and _lock
+ # acquired.
+ flag = rffi.cast(rffi.INT, 1)
+ res = c_thread_acquirelock_NOAUTO(self._lock, flag)
+ res = rffi.cast(lltype.Signed, res)
+ if res != 1:
+ fatalerror("lock.acquire() failed unexpectedly")
+ n = 3
+ #
+ self._num_acquires = n + 2
+ res = c_thread_acquirelock_timed(self._lock, microseconds, intr_flag)
+ res = rffi.cast(lltype.Signed, res)
+ return res
+
+ @staticmethod
+ def _really_acquire(self):
+ res = self._acquire_ll_lock(-1, 0)
+ if res != 1:
+ fatalerror("lock.acquire() failed unexpectedly")
+ #
+ n = self._num_acquires - 2
+ assert n >= 0
+ self._num_acquires = n
+
+ @staticmethod
+ def _really_release(self):
+ #debug_print("_really_release, n =", self._num_acquires)
+ c_thread_releaselock(self._lock)
def acquire(self, flag):
- res = c_thread_acquirelock(self._lock, int(flag))
- res = rffi.cast(lltype.Signed, res)
- return bool(res)
+ if flag:
+ #debug_print("acquiring...")
+ # acquire(True): if _num_acquires >= 2, then there is contention
+ jit.conditional_call(self._num_acquires >= 2,
+ Lock._really_acquire, self)
+ #
+ # At this point, either _num_acquires was zero and we increment
+ # it here; or it was not zero, so we called _really_acquire(),
+ # which incremented it but decremented it again before
+ # returning --- so that we can increment it here *again*. The
+ # point is that if the JIT compiles a small Python block of
+ # code which contains the release() soon afterwards, the
+ # following increment will be matched with the decrement and
+ # optimized away (as usual relying on the GIL).
+ self._num_acquires += 2
+ #debug_print("acquired")
+ return True
+ else:
+ # acquire(False): if n == 0, then we succeed; otherwise, we fail
+ if self._num_acquires == 0:
+ self._num_acquires = 2
+ return True
+ else:
+ return False
def acquire_timed(self, timeout):
"""Timeout is in microseconds. Returns 0 in case of failure,
1 in case it works, 2 if interrupted by a signal."""
- res = c_thread_acquirelock_timed(self._lock, timeout, 1)
- res = rffi.cast(lltype.Signed, res)
+ n = self._num_acquires
+ # can't use jit.conditional_call() easily here, because of the
+ # return value. Anyway in PyPy this code is not seen by the JIT.
+ if n == 0:
+ self._num_acquires = n + 2
+ res = 1 # no contention, worked
+ else:
+ res = self._acquire_ll_lock(timeout, 1)
return res
def release(self):
# Sanity check: the lock must be locked
- if self.acquire(False):
- c_thread_releaselock(self._lock)
+ n = self._num_acquires
+ if n < 2:
raise error("bad lock")
- else:
- c_thread_releaselock(self._lock)
+ n = n - 2
+ self._num_acquires = n
+ # the only case where we don't really want to release the ll_lock
+ # is when this release() makes _num_acquires go from 2 to 0.
+ jit.conditional_call(n != 0, Lock._really_release, self)
+ #debug_print("released")
def __del__(self):
if free_ll_lock is None: # happens when tests are shutting down
More information about the pypy-commit
mailing list