hotspot/src/share/vm/runtime/objectMonitor.cpp
int ObjectMonitor::TrySpin_VaryDuration (Thread * Self) {
{- -------------------------------------------
(1) Knob_FixedSpin が非ゼロの場合には,
Adaptive Spinning の代わりに固定回数のスピンが行われる.
(これは adaptive spin によりどのくらい性能向上したかを図る指標として使われる機能, とのこと)
この場合は, Knob_FixedSpin で指定された回数だけ
ObjectMonitor::TryLock() によるロック確保と SpinPause() による待機処理を繰り返す.
ロックが確保できれば, 1 をリターン.
(Knob_FixedSpin 回やっても) 確保できなければ, 0 をリターン.
---------------------------------------- -}
// Dumb, brutal spin. Good for comparative measurements against adaptive spinning.
int ctr = Knob_FixedSpin ;
if (ctr != 0) {
while (--ctr >= 0) {
if (TryLock (Self) > 0) return 1 ;
SpinPause () ;
}
return 0 ;
}
{- -------------------------------------------
(1) まず, (Knob_PreSpin+1) 回分だけスピンしてみる.
(スピン処理は, ObjectMonitor::TryLock() によるロック確保と SpinPause() による待機処理の繰り返し)
ロックが確保できたら, (スピンが有効だということなので) _SpinDuration を以下のように増加させた後, 1 をリターン.
* あまりに大きかった場合 (_SpinDuration が Knob_SpinLimit を超えている場合)
増加させない
* あまりに小さかった場合 (_SpinDuration が Knob_Poverty より小さかった場合)
Knob_Poverty + Knob_BonusB まで増加させる
* それ以外 (= Knob_Poverty と Knob_SpinLimit の間)
Knob_BonusB 分だけ増加させる
---------------------------------------- -}
for (ctr = Knob_PreSpin + 1; --ctr >= 0 ; ) {
if (TryLock(Self) > 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 ;
}
return 1 ;
}
SpinPause () ;
}
{- -------------------------------------------
(1) 次に _SpinDuration が Knob_SpinBase 以下だったら Knob_SpinBase までは上げておく.
もし _SpinDuration が 0 以下なら, 諦めて終了.
(コメントによると,
_SpinDuration が恒常的に 0 以下で落ち着いてしまわないよう, 初めに PreSpin を行っている, とのこと.
他の方法としては, システムの負荷や競合状態等の条件が変化した際に
サンプリング的に少しスピンしておくといいかも, とのこと.
あるいは, 適当な間隔で _SpinDuration を _SpinLimit に設定して長時間のスピンを行ってみてもいいかもしれない, とのこと.
(スピンの失敗回数がある閾値を超えた際, など))
---------------------------------------- -}
// 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.
//
// Consider the following alternative:
// Periodically set _SpinDuration = _SpinLimit and try a long/full
// spin attempt. "Periodically" might mean after a tally of
// the # of failed spin attempts (or iterations) reaches some threshold.
// 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 < Knob_SpinBase) ctr = Knob_SpinBase ;
if (ctr <= 0) return 0 ;
{- -------------------------------------------
(1) Knob_SuccRestrict が指定されている場合は, _succ が空でなければここでリターン.
(Knob_SuccRestrict は _succ が既にいるなら spin しないというオプション)
---------------------------------------- -}
if (Knob_SuccRestrict && _succ != NULL) return 0 ;
{- -------------------------------------------
(1) Knob_OState が指定されている場合は,
現在ロックを保持しているスレッドが runnable 状態でなければここでリターン.
(Knob_OState は, 現在ロックを保持しているスレッドが runnable 状態でなければ諦めるというオプション)
(ついでに, (トレース出力)も出している)
---------------------------------------- -}
if (Knob_OState && NotRunnable (Self, (Thread *) _owner)) {
TEVENT (Spin abort - notrunnable [TOP]);
return 0 ;
}
{- -------------------------------------------
(1) _Spinners が Knob_MaxSpinners を越えていたら, 諦めて終了する (0 をリターンする).
越えていなければ, _Spinner を1つインクリメントする.
(Knob_MaxSpinners は _Spinners (現在そのモニタに対して spin しているスレッド数)の最大値.
ただし, Knob_MaxSpinners が 0 未満の時は制限なしを意味する.)
(_Spinners が Knob_MaxSpinners を越えたケースでは, ついでに(トレース出力)も出している)
---------------------------------------- -}
int MaxSpin = Knob_MaxSpinners ;
if (MaxSpin >= 0) {
if (_Spinner > MaxSpin) {
TEVENT (Spin abort -- too many spinners) ;
return 0 ;
}
// Slighty racy, but benign ...
Adjust (&_Spinner, 1) ;
}
{- -------------------------------------------
(1) (ここまでで前準備は終了. 以降で本格的にスピンを行う.)
---------------------------------------- -}
// We're good to spin ... spin ingress.
// CONSIDER: use Prefetch::write() to avoid RTS->RTO upgrades
// when preparing to LD...CAS _owner, etc and the CAS is likely
// to succeed.
{- -------------------------------------------
(1) (変数宣言など)
* Knob_SpinSetSucc は Spin するときに _succ を設定するかどうかというオプション.
Knob_SpinSetSucc が有効で _succ が NULL なら, _succ を自分にする.
(これは, アンロック処理を行うスレッドに対して,
スピンしているスレッドがいるから後続の起床処理は必要ない, と伝える効果がある(?).
See: ObjectMonitor::exit())
* Knob_CASPenalty は CAS が失敗したときの penalty 値.
* Knob_OXPenalty は観測していた _owner が変わってしまったときの penalty 値.
---------------------------------------- -}
int hits = 0 ;
int msk = 0 ;
int caspty = Knob_CASPenalty ;
int oxpty = Knob_OXPenalty ;
int sss = Knob_SpinSetSucc ;
if (sss && _succ == NULL ) _succ = Self ;
Thread * prv = NULL ;
{- -------------------------------------------
(1) (次の while ループの中でロックを取りにいく.
ループは基本的には _SpinDuration の数だけ回り, 以下の条件で終了する.
* _SpinDuration 回スピンロックしても, ロック確保が成功しなかった
* ロックを取るのに成功した (A successful spin)
* (Knob_CASPenalty や Knob_OXPenalty が -2 の場合)
CAS が一度でも失敗した, flicker が 1回でも観測された (Spin failure with prejudice)
* (Knob_CASPenalty や Knob_OXPenalty が -1 の場合)
CAS が一度でも失敗した, flicker が 1回でも観測された (Spin failure without prejudice)
なお, Knob_CASPenalty や Knob_OXPenalty が -2/-1 というのは, それぞれ
「CAS がものすごく重いので一回でも失敗したら即 spin は諦めなさい」, および
「flicker を一回でも観測したら即 spin は諦めなさい」, という意味.)
---------------------------------------- -}
// There are three ways to exit the following loop:
// 1. A successful spin where this thread has acquired the lock.
// 2. Spin failure with prejudice
// 3. Spin failure without prejudice
while (--ctr >= 0) {
{- -------------------------------------------
(1.1) Safepoint 中にスピンしていると
Safepoint の開始が遅れたり GC 処理の性能が低下したりしてまずいので,
定期的にチェックしている (現状では 0xFF 回に 1回チェックしている).
チェックが行われた際には, 結果に応じて以下の処理が行われる.
* Safepoint が開始されていた場合:
Abort ラベルにジャンプし, スピンロック処理を終了する.
* 〃 が開始されていなかった場合:
SpinPause() を呼び出して, 少しの時間だけ待機してみる.
(なお, この最適化(?)は Knob_UsePause の 1bit目(1)が立っている場合にのみ行われる).
その後, (もし何か登録されていれば) SpinCallbackFunction に登録されているコールバックを呼び出す.
(現状だと, SpinCallbackFunction には何も登録されないようだが... #TODO)
(Safepoint が開始されていたケースでは, ついでに(トレース出力)も出している)
---------------------------------------- -}
// Periodic polling -- Check for pending GC
// Threads may spin while they're unsafe.
// We don't want spinning threads to delay the JVM from reaching
// a stop-the-world safepoint or to steal cycles from GC.
// If we detect a pending safepoint we abort in order that
// (a) this thread, if unsafe, doesn't delay the safepoint, and (b)
// this thread, if safe, doesn't steal cycles from GC.
// This is in keeping with the "no loitering in runtime" rule.
// We periodically check to see if there's a safepoint pending.
if ((ctr & 0xFF) == 0) {
if (SafepointSynchronize::do_call_back()) {
TEVENT (Spin: safepoint) ;
goto Abort ; // abrupt spin egress
}
if (Knob_UsePause & 1) SpinPause () ;
int (*scb)(intptr_t,int) = SpinCallbackFunction ;
if (hits > 50 && scb != NULL) {
int abend = (*scb)(SpinCallbackArgument, 0) ;
}
}
{- -------------------------------------------
(1.1) SpinPause() を呼び出して, 少しの時間だけ待機してみる.
(なお, この最適化(?)は Knob_UsePause の 2bit目(2)が立っている場合にのみ行われる)
---------------------------------------- -}
if (Knob_UsePause & 2) SpinPause() ;
{- -------------------------------------------
(1.1) 各ループで CAS していると競合しまくるので, Exponential Back-off する.
(とはいえ, Niagara みたいな CMT (インターリーブ式のマルチスレッディングを表す Sun Microsystems 用語) を
採用しているプロセッサだと余り意味ないかも, とのこと)
具体的には, msk 回に 1回しか CAS を試みないことにしている (それ以外の場合には, ここでループの先頭に戻る).
そして msk の値は, 最初は 0 から始まり, CAS を 0xF 回試みる度に 2bit ずつ 1 が増えていく.
(つまり, 0 -> 0b11 -> 0b1111 -> 0b111111 -> ... という感じで増えていく.
10 進数で言うと 0 -> 3 -> 15 -> 63 -> ... ということで, 4 のべき乗(-1)で増えていく.
ただし, BackOffMask を超えないように調整はされる.
なおコメントには, 2 のべき乗でもいいのでは? (msk = (msk+1)|msk でいいのでは?), とも書かれている.)
---------------------------------------- -}
// Exponential back-off ... Stay off the bus to reduce coherency traffic.
// This is useful on classic SMP systems, but is of less utility on
// N1-style CMT platforms.
//
// Trade-off: lock acquisition latency vs coherency bandwidth.
// Lock hold times are typically short. A histogram
// of successful spin attempts shows that we usually acquire
// the lock early in the spin. That suggests we want to
// sample _owner frequently in the early phase of the spin,
// but then back-off and sample less frequently as the spin
// progresses. The back-off makes a good citizen on SMP big
// SMP systems. Oversampling _owner can consume excessive
// coherency bandwidth. Relatedly, if we _oversample _owner we
// can inadvertently interfere with the the ST m->owner=null.
// executed by the lock owner.
if (ctr & msk) continue ;
++hits ;
if ((hits & 0xF) == 0) {
// The 0xF, above, corresponds to the exponent.
// Consider: (msk+1)|msk
msk = ((msk << 2)|3) & BackOffMask ;
}
{- -------------------------------------------
(1.1) もしこの時点で誰もロックしていないようであれば (= _owner が NULL であれば),
CAS によるロック確保を試みる (TATAS 方式).
CAS による書き換えが成功したら, A successful spin.
必要に応じて _succ や _Spinner を元に戻した後,
(スピンが有効だということなので) _SpinDuration を以下のように増加させてから,
1 をリターン.
* あまりに大きかった場合 (_SpinDuration が Knob_SpinLimit を超えている場合)
増加させない
* あまりに小さかった場合 (_SpinDuration が Knob_Poverty より小さかった場合)
Knob_Poverty + Knob_BonusB まで増加させる
* それ以外 (= Knob_Poverty と Knob_SpinLimit の間)
Knob_BonusB 分だけ増加させる
(なおコメントには,
実際にループした回数(ctr)に比例して増加分を調整するといいかも,
みたいなことも書いてある)
---------------------------------------- -}
// Probe _owner with TATAS
// If this thread observes the monitor transition or flicker
// from locked to unlocked to locked, then the odds that this
// thread will acquire the lock in this spin attempt go down
// considerably. The same argument applies if the CAS fails
// or if we observe _owner change from one non-null value to
// another non-null value. In such cases we might abort
// the spin without prejudice or apply a "penalty" to the
// spin count-down variable "ctr", reducing it by 100, say.
Thread * ox = (Thread *) _owner ;
if (ox == NULL) {
ox = (Thread *) Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
if (ox == NULL) {
// The CAS succeeded -- this thread acquired ownership
// Take care of some bookkeeping to exit spin state.
if (sss && _succ == Self) {
_succ = NULL ;
}
if (MaxSpin > 0) Adjust (&_Spinner, -1) ;
// Increase _SpinDuration :
// The spin was successful (profitable) so we tend toward
// longer spin attempts in the future.
// CONSIDER: factor "ctr" into the _SpinDuration adjustment.
// 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 ;
}
{- -------------------------------------------
(1.1) (以下は CAS に失敗した場合の処理)
CAS が失敗した場合は, 残りループ回数(ctr)を CAS penalty(caspty) の分だけ減少させ,
再度ループの先頭に戻ってやり直す
(CAS は処理レイテンシが大きいので, すぐにやり直しても大丈夫, とのこと).
ただし, CAS penalty(caspty) が -2 や -1 の場合は,
「CAS がものすごく重いので一回でも失敗したら即 spin は諦めなさい」という意味なので,
この場合は, ループの先頭には戻らず, ここでループを抜ける.
* -2 の場合
break でループから脱出する. (Spin failed with prejudice へ)
* -1 の場合
Abort ラベルにジャンプしてループから脱出する. (Spin failed without prejudice へ)
(ついでに, (トレース出力)も出している)
---------------------------------------- -}
// The CAS failed ... we can take any of the following actions:
// * penalize: ctr -= Knob_CASPenalty
// * exit spin with prejudice -- goto Abort;
// * exit spin without prejudice.
// * Since CAS is high-latency, retry again immediately.
prv = ox ;
TEVENT (Spin: cas failed) ;
if (caspty == -2) break ;
if (caspty == -1) goto Abort ;
ctr -= caspty ;
continue ;
}
{- -------------------------------------------
(1.1) (以下は _owner が NULL でなかったため CAS を実行しなかった場合, もしくは
CAS を実行したが失敗した場合の処理)
もし, ロック状態がちらついていたら(flicker)
(具体的に言うと locked -> unlocked -> locked のような遷移が確認できたら)
今回のスピンでロックが獲得できる可能性は低い.
また CAS が失敗した場合についても, ロックの所有者がちらついていたら
(具体的には _owner が ある non-null value から 別の non-null value になっていたら)
同様に今回のスピンでロックが獲得できる可能性は低い.
そのため prv という変数に, 一回前のループ時の _owner の値を入れておき,
non-null -> non-null の変化が起こっていないか確認している.
_owner が non-null -> non-null と変化していた場合は,
残りループ回数(ctr)を OX penalty(oxpty) の分だけ減少させる.
ただし, OX penalty が -2 や -1 の場合は,
「一階でも flicker を確認したら spin は諦めなさい」という意味.
この場合は, ここでループを抜ける.
* -2 の場合
break でループから脱出する. (Spin failed with prejudice へ)
* -1 の場合
Abort ラベルにジャンプしてループから脱出する. (Spin failed without prejudice へ)
(ちらつきが確認された場合には, ついでに(トレース出力)も出している)
---------------------------------------- -}
// Did lock ownership change hands ?
if (ox != prv && prv != NULL ) {
TEVENT (spin: Owner changed)
if (oxpty == -2) break ;
if (oxpty == -1) goto Abort ;
ctr -= oxpty ;
}
prv = ox ;
{- -------------------------------------------
(1.1)
(Knob_OState は, 現在ロックを保持しているスレッドが runnable 状態でなければ諦めるというオプション)
(ついでに, (トレース出力)も出している)
---------------------------------------- -}
// Abort the spin if the owner is not executing.
// The owner must be executing in order to drop the lock.
// Spinning while the owner is OFFPROC is idiocy.
// Consider: ctr -= RunnablePenalty ;
if (Knob_OState && NotRunnable (Self, ox)) {
TEVENT (Spin abort - notrunnable);
goto Abort ;
}
{- -------------------------------------------
(1.1) (sss(Knob_SpinSetSucc) は Spin するときに _succ を設定するかどうかというオプション)
Knob_SpinSetSucc が有効で _succ が NULL であれば, _succ を自分にしておく.
---------------------------------------- -}
if (sss && _succ == NULL ) _succ = Self ;
}
{- -------------------------------------------
(1) (ここに到達するのは, 以下の2つのケース.
* _SpinDuration 回スピンロックしても, ロック確保が成功しなかったケース
* Spin failure with prejudice のケース (CAS が一度でも失敗した, もしくは flicker が 1回でも観測された))
このうち, Spin failure with prejudice のケースについては,
(スピンが有効ではないということなので)
_SpinDuration を Knob_Penalty 分だけ減少させる (ただし, 0 未満になってしまったら 0 にする).
(どちらのケースについても, ついでに(トレース出力)も出している)
(なおコメントによると,
TCP の AIMD のようにした方がいいかもしれない, とのこと)
---------------------------------------- -}
// Spin failed with prejudice -- reduce _SpinDuration.
// TODO: Use an AIMD-like policy to adjust _SpinDuration.
// AIMD is globally stable.
TEVENT (Spin failure) ;
{
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 ;
}
}
{- -------------------------------------------
(1) (ここに到達するのは, 以下の3つのケース.
* _SpinDuration 回スピンロックしても, ロック確保が成功しなかったケース
* Spin failure with prejudice のケース (CAS が一度でも失敗した, もしくは flicker が 1回でも観測された)
* Spin failure without prejudice のケース (CAS が一度でも失敗した, もしくは flicker が 1回でも観測された))
以下のような後始末を行い, 0 をリターンする (ただし, 以下の TryLock() が成功した場合だけは 1 をリターンする).
* _Spinner の値を元に戻す.
(ただし, 元々 _Spinner の値を変更していない場合 (= MaxSpin(Knob_MaxSpinners) が 0 未満の場合) には何もしない)
* sss(Knob_SpinSetSucc) が有効で _succ をカレントスレッドに設定していた場合には, _succ を NULL に戻しておく.
なお, _succ を NULL に戻した場合には
眠りにつく前に _owner の確保を試みておかないといけない, という内部規則があるため,
ここでもう一度 ObjectMonitor::TryLock() を呼んでロック確保を試みている.
確保に成功すれば 1 をリターンする.
(なおコメントによると, 安全性を考えれば TrySpin は極力単純にしておきたいが..., とのこと)
---------------------------------------- -}
Abort:
if (MaxSpin >= 0) Adjust (&_Spinner, -1) ;
if (sss && _succ == Self) {
_succ = NULL ;
// 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(Self) > 0) return 1 ;
}
return 0 ;
}
This document is available under the GNU GENERAL PUBLIC LICENSE Version 2.