mirror of
https://github.com/openjdk/jdk.git
synced 2026-02-14 04:15:04 +00:00
8330051: Small ObjectMonitor spinning code cleanups
Reviewed-by: dcubed, eosterlund, fbredberg
This commit is contained in:
parent
3e185c70fe
commit
ee7b2e9b3b
@ -108,19 +108,6 @@
|
||||
|
||||
#endif // ndef DTRACE_ENABLED
|
||||
|
||||
// Tunables ...
|
||||
// The knob* variables are effectively final. Once set they should
|
||||
// never be modified hence. Consider using __read_mostly with GCC.
|
||||
|
||||
int ObjectMonitor::Knob_SpinLimit = 5000; // derived by an external tool -
|
||||
|
||||
static int Knob_Bonus = 100; // spin success bonus
|
||||
static int Knob_BonusB = 100; // spin success bonus
|
||||
static int Knob_Penalty = 200; // spin failure penalty
|
||||
static int Knob_Poverty = 1000;
|
||||
static int Knob_FixedSpin = 0;
|
||||
static int Knob_PreSpin = 10; // 20-100 likely better
|
||||
|
||||
DEBUG_ONLY(static volatile bool InitDone = false;)
|
||||
|
||||
OopStorage* ObjectMonitor::_oop_storage = nullptr;
|
||||
@ -406,7 +393,7 @@ bool ObjectMonitor::enter(JavaThread* current) {
|
||||
// transitions. The following spin is strictly optional ...
|
||||
// Note that if we acquire the monitor from an initial spin
|
||||
// we forgo posting JVMTI events and firing DTRACE probes.
|
||||
if (TrySpin(current) > 0) {
|
||||
if (TrySpin(current)) {
|
||||
assert(owner_raw() == current, "must be current: owner=" INTPTR_FORMAT, p2i(owner_raw()));
|
||||
assert(_recursions == 0, "must be 0: recursions=" INTX_FORMAT, _recursions);
|
||||
assert(object()->mark() == markWord::encode(this),
|
||||
@ -535,18 +522,18 @@ bool ObjectMonitor::enter(JavaThread* current) {
|
||||
// Caveat: TryLock() is not necessarily serializing if it returns failure.
|
||||
// Callers must compensate as needed.
|
||||
|
||||
int ObjectMonitor::TryLock(JavaThread* current) {
|
||||
ObjectMonitor::TryLockResult ObjectMonitor::TryLock(JavaThread* current) {
|
||||
void* own = owner_raw();
|
||||
if (own != nullptr) return 0;
|
||||
if (own != nullptr) return TryLockResult::HasOwner;
|
||||
if (try_set_owner_from(nullptr, current) == nullptr) {
|
||||
assert(_recursions == 0, "invariant");
|
||||
return 1;
|
||||
return TryLockResult::Success;
|
||||
}
|
||||
// The lock had been free momentarily, but we lost the race to the lock.
|
||||
// Interference -- the CAS failed.
|
||||
// We can either return -1 or retry.
|
||||
// Retry doesn't make as much sense because the lock was just acquired.
|
||||
return -1;
|
||||
return TryLockResult::Interference;
|
||||
}
|
||||
|
||||
// Deflate the specified ObjectMonitor if not in-use. Returns true if it
|
||||
@ -723,7 +710,7 @@ void ObjectMonitor::EnterI(JavaThread* current) {
|
||||
assert(current->thread_state() == _thread_blocked, "invariant");
|
||||
|
||||
// Try the lock - TATAS
|
||||
if (TryLock (current) > 0) {
|
||||
if (TryLock(current) == TryLockResult::Success) {
|
||||
assert(_succ != current, "invariant");
|
||||
assert(owner_raw() == current, "invariant");
|
||||
assert(_Responsible != current, "invariant");
|
||||
@ -757,7 +744,7 @@ void ObjectMonitor::EnterI(JavaThread* current) {
|
||||
// to the owner. This has subtle but beneficial affinity
|
||||
// effects.
|
||||
|
||||
if (TrySpin(current) > 0) {
|
||||
if (TrySpin(current)) {
|
||||
assert(owner_raw() == current, "invariant");
|
||||
assert(_succ != current, "invariant");
|
||||
assert(_Responsible != current, "invariant");
|
||||
@ -794,7 +781,7 @@ void ObjectMonitor::EnterI(JavaThread* current) {
|
||||
|
||||
// Interference - the CAS failed because _cxq changed. Just retry.
|
||||
// As an optional optimization we retry the lock.
|
||||
if (TryLock (current) > 0) {
|
||||
if (TryLock(current) == TryLockResult::Success) {
|
||||
assert(_succ != current, "invariant");
|
||||
assert(owner_raw() == current, "invariant");
|
||||
assert(_Responsible != current, "invariant");
|
||||
@ -847,7 +834,9 @@ void ObjectMonitor::EnterI(JavaThread* current) {
|
||||
|
||||
for (;;) {
|
||||
|
||||
if (TryLock(current) > 0) break;
|
||||
if (TryLock(current) == TryLockResult::Success) {
|
||||
break;
|
||||
}
|
||||
assert(owner_raw() != current, "invariant");
|
||||
|
||||
// park self
|
||||
@ -862,7 +851,9 @@ void ObjectMonitor::EnterI(JavaThread* current) {
|
||||
current->_ParkEvent->park();
|
||||
}
|
||||
|
||||
if (TryLock(current) > 0) break;
|
||||
if (TryLock(current) == TryLockResult::Success) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (try_set_owner_from(DEFLATER_MARKER, current) == DEFLATER_MARKER) {
|
||||
// Cancelled the in-progress async deflation by changing owner from
|
||||
@ -895,7 +886,9 @@ void ObjectMonitor::EnterI(JavaThread* current) {
|
||||
// We can defer clearing _succ until after the spin completes
|
||||
// TrySpin() must tolerate being called with _succ == current.
|
||||
// Try yet another round of adaptive spinning.
|
||||
if (TrySpin(current) > 0) break;
|
||||
if (TrySpin(current)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// We can find that we were unpark()ed and redesignated _succ while
|
||||
// we were spinning. That's harmless. If we iterate and call park(),
|
||||
@ -994,8 +987,9 @@ void ObjectMonitor::ReenterI(JavaThread* current, ObjectWaiter* currentNode) {
|
||||
guarantee(v == ObjectWaiter::TS_ENTER || v == ObjectWaiter::TS_CXQ, "invariant");
|
||||
assert(owner_raw() != current, "invariant");
|
||||
|
||||
if (TryLock(current) > 0) break;
|
||||
if (TrySpin(current) > 0) break;
|
||||
if (TrySpin(current)) {
|
||||
break;
|
||||
}
|
||||
|
||||
{
|
||||
OSThreadContendState osts(current->osthread());
|
||||
@ -1012,7 +1006,9 @@ void ObjectMonitor::ReenterI(JavaThread* current, ObjectWaiter* currentNode) {
|
||||
// Try again, but just so we distinguish between futile wakeups and
|
||||
// successful wakeups. The following test isn't algorithmically
|
||||
// necessary, but it helps us maintain sensible statistics.
|
||||
if (TryLock(current) > 0) break;
|
||||
if (TryLock(current) == TryLockResult::Success) {
|
||||
break;
|
||||
}
|
||||
|
||||
// The lock is still contested.
|
||||
// Keep a tally of the # of futile wakeups.
|
||||
@ -1854,32 +1850,65 @@ void ObjectMonitor::notifyAll(TRAPS) {
|
||||
// hysteresis control to damp the transition rate between spinning and
|
||||
// not spinning.
|
||||
|
||||
// Spinning: Fixed frequency (100%), vary duration
|
||||
int ObjectMonitor::TrySpin(JavaThread* current) {
|
||||
// Dumb, brutal spin. Good for comparative measurements against adaptive spinning.
|
||||
int ctr = Knob_FixedSpin;
|
||||
if (ctr != 0) {
|
||||
while (--ctr >= 0) {
|
||||
if (TryLock(current) > 0) return 1;
|
||||
SpinPause();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
int ObjectMonitor::Knob_SpinLimit = 5000; // derived by an external tool
|
||||
|
||||
for (ctr = Knob_PreSpin + 1; --ctr >= 0;) {
|
||||
if (TryLock(current) > 0) {
|
||||
// Increase _SpinDuration ...
|
||||
// Note that we don't clamp SpinDuration precisely at SpinLimit.
|
||||
// Raising _SpurDuration to the poverty line is key.
|
||||
int x = _SpinDuration;
|
||||
if (x < Knob_SpinLimit) {
|
||||
if (x < Knob_Poverty) x = Knob_Poverty;
|
||||
_SpinDuration = x + Knob_BonusB;
|
||||
static int Knob_Bonus = 100; // spin success bonus
|
||||
static int Knob_Penalty = 200; // spin failure penalty
|
||||
static int Knob_Poverty = 1000;
|
||||
static int Knob_FixedSpin = 0;
|
||||
static int Knob_PreSpin = 10; // 20-100 likely better, but it's not better in my testing.
|
||||
|
||||
inline static int adjust_up(int spin_duration) {
|
||||
int x = spin_duration;
|
||||
if (x < ObjectMonitor::Knob_SpinLimit) {
|
||||
if (x < Knob_Poverty) {
|
||||
x = Knob_Poverty;
|
||||
}
|
||||
return x + Knob_Bonus;
|
||||
} else {
|
||||
return spin_duration;
|
||||
}
|
||||
}
|
||||
|
||||
inline static int adjust_down(int spin_duration) {
|
||||
// TODO: Use an AIMD-like policy to adjust _SpinDuration.
|
||||
// AIMD is globally stable.
|
||||
int x = spin_duration;
|
||||
if (x > 0) {
|
||||
// Consider an AIMD scheme like: x -= (x >> 3) + 100
|
||||
// This is globally sample and tends to damp the response.
|
||||
x -= Knob_Penalty;
|
||||
if (x < 0) { x = 0; }
|
||||
return x;
|
||||
} else {
|
||||
return spin_duration;
|
||||
}
|
||||
}
|
||||
|
||||
bool ObjectMonitor::short_fixed_spin(JavaThread* current, int spin_count, bool adapt) {
|
||||
for (int ctr = 0; ctr < spin_count; ctr++) {
|
||||
TryLockResult status = TryLock(current);
|
||||
if (status == TryLockResult::Success) {
|
||||
if (adapt) {
|
||||
_SpinDuration = adjust_up(_SpinDuration);
|
||||
}
|
||||
return 1;
|
||||
return true;
|
||||
} else if (status == TryLockResult::Interference) {
|
||||
break;
|
||||
}
|
||||
SpinPause();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Spinning: Fixed frequency (100%), vary duration
|
||||
bool ObjectMonitor::TrySpin(JavaThread* current) {
|
||||
|
||||
// Dumb, brutal spin. Good for comparative measurements against adaptive spinning.
|
||||
int knob_fixed_spin = Knob_FixedSpin; // 0 (don't spin: default), 2000 good test
|
||||
if (knob_fixed_spin > 0) {
|
||||
return short_fixed_spin(current, knob_fixed_spin, false);
|
||||
}
|
||||
|
||||
// Admission control - verify preconditions for spinning
|
||||
//
|
||||
@ -1887,6 +1916,12 @@ int ObjectMonitor::TrySpin(JavaThread* current) {
|
||||
// becoming an absorbing state. Put another way, we spin briefly to
|
||||
// sample, just in case the system load, parallelism, contention, or lock
|
||||
// modality changed.
|
||||
|
||||
int knob_pre_spin = Knob_PreSpin; // 10 (default), 100, 1000 or 2000
|
||||
if (short_fixed_spin(current, knob_pre_spin, true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// Consider the following alternative:
|
||||
// Periodically set _SpinDuration = _SpinLimit and try a long/full
|
||||
@ -1895,8 +1930,8 @@ int ObjectMonitor::TrySpin(JavaThread* current) {
|
||||
// This takes us into the realm of 1-out-of-N spinning, where we
|
||||
// hold the duration constant but vary the frequency.
|
||||
|
||||
ctr = _SpinDuration;
|
||||
if (ctr <= 0) return 0;
|
||||
int ctr = _SpinDuration;
|
||||
if (ctr <= 0) return false;
|
||||
|
||||
// We're good to spin ... spin ingress.
|
||||
// CONSIDER: use Prefetch::write() to avoid RTS->RTO upgrades
|
||||
@ -1928,7 +1963,7 @@ int ObjectMonitor::TrySpin(JavaThread* current) {
|
||||
// might update the poll values and we could be in a thread_blocked
|
||||
// state here which is not allowed so just check the poll.
|
||||
if (SafepointMechanism::local_poll_armed(current)) {
|
||||
goto Abort; // abrupt spin egress
|
||||
break;
|
||||
}
|
||||
SpinPause();
|
||||
}
|
||||
@ -1960,26 +1995,21 @@ int ObjectMonitor::TrySpin(JavaThread* current) {
|
||||
// If we acquired the lock early in the spin cycle it
|
||||
// makes sense to increase _SpinDuration proportionally.
|
||||
// Note that we don't clamp SpinDuration precisely at SpinLimit.
|
||||
int x = _SpinDuration;
|
||||
if (x < Knob_SpinLimit) {
|
||||
if (x < Knob_Poverty) x = Knob_Poverty;
|
||||
_SpinDuration = x + Knob_Bonus;
|
||||
}
|
||||
return 1;
|
||||
_SpinDuration = adjust_up(_SpinDuration);
|
||||
return true;
|
||||
}
|
||||
|
||||
// The CAS failed ... we can take any of the following actions:
|
||||
// * penalize: ctr -= CASPenalty
|
||||
// * exit spin with prejudice -- goto Abort;
|
||||
// * exit spin with prejudice -- abort without adapting spinner
|
||||
// * exit spin without prejudice.
|
||||
// * Since CAS is high-latency, retry again immediately.
|
||||
prv = ox;
|
||||
goto Abort;
|
||||
break;
|
||||
}
|
||||
|
||||
// Did lock ownership change hands ?
|
||||
if (ox != prv && prv != nullptr) {
|
||||
goto Abort;
|
||||
break;
|
||||
}
|
||||
prv = ox;
|
||||
|
||||
@ -1989,20 +2019,10 @@ int ObjectMonitor::TrySpin(JavaThread* current) {
|
||||
}
|
||||
|
||||
// Spin failed with prejudice -- reduce _SpinDuration.
|
||||
// TODO: Use an AIMD-like policy to adjust _SpinDuration.
|
||||
// AIMD is globally stable.
|
||||
{
|
||||
int x = _SpinDuration;
|
||||
if (x > 0) {
|
||||
// Consider an AIMD scheme like: x -= (x >> 3) + 100
|
||||
// This is globally sample and tends to damp the response.
|
||||
x -= Knob_Penalty;
|
||||
if (x < 0) x = 0;
|
||||
_SpinDuration = x;
|
||||
}
|
||||
if (ctr < 0) {
|
||||
_SpinDuration = adjust_down(_SpinDuration);
|
||||
}
|
||||
|
||||
Abort:
|
||||
if (_succ == current) {
|
||||
_succ = nullptr;
|
||||
// Invariant: after setting succ=null a contending thread
|
||||
@ -2010,9 +2030,12 @@ int ObjectMonitor::TrySpin(JavaThread* current) {
|
||||
// in the normal usage of TrySpin(), but it's safest
|
||||
// to make TrySpin() as foolproof as possible.
|
||||
OrderAccess::fence();
|
||||
if (TryLock(current) > 0) return 1;
|
||||
if (TryLock(current) == TryLockResult::Success) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -353,8 +353,14 @@ private:
|
||||
void EnterI(JavaThread* current);
|
||||
void ReenterI(JavaThread* current, ObjectWaiter* current_node);
|
||||
void UnlinkAfterAcquire(JavaThread* current, ObjectWaiter* current_node);
|
||||
int TryLock(JavaThread* current);
|
||||
int TrySpin(JavaThread* current);
|
||||
|
||||
|
||||
enum class TryLockResult { Interference = -1, HasOwner = 0, Success = 1 };
|
||||
|
||||
TryLockResult TryLock(JavaThread* current);
|
||||
|
||||
bool TrySpin(JavaThread* current);
|
||||
bool short_fixed_spin(JavaThread* current, int spin_count, bool adapt);
|
||||
void ExitEpilog(JavaThread* current, ObjectWaiter* Wakee);
|
||||
|
||||
// Deflation support
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user