Skip to content

Commit

Permalink
8330051: Small ObjectMonitor spinning code cleanups
Browse files Browse the repository at this point in the history
Reviewed-by: dcubed, eosterlund, fbredberg
  • Loading branch information
coleenp committed Apr 22, 2024
1 parent 3e185c7 commit ee7b2e9
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 75 deletions.
169 changes: 96 additions & 73 deletions src/hotspot/share/runtime/objectMonitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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());
Expand All @@ -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.
Expand Down Expand Up @@ -1854,39 +1850,78 @@ 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();
int ObjectMonitor::Knob_SpinLimit = 5000; // derived by an external tool

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 0;
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;
}
}

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;
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
//
// We always spin a little bit, just to prevent _SpinDuration == 0 from
// 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
Expand All @@ -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
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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;

Expand All @@ -1989,30 +2019,23 @@ 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
// must recheck-retry _owner before parking. This usually happens
// 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;
}


Expand Down
10 changes: 8 additions & 2 deletions src/hotspot/share/runtime/objectMonitor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -353,8 +353,14 @@ class ObjectMonitor : public CHeapObj<mtObjectMonitor> {
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
Expand Down

1 comment on commit ee7b2e9

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.