Top


定義場所(file name)

hotspot/src/share/vm/runtime/objectMonitor.cpp

名前(function name)

int ObjectMonitor::TrySpin_VaryDuration (Thread * Self) {

本体部(body)

  {- -------------------------------------------
  (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.