<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                # 協程CPU密集場景調度實現 [TOC] ### 搶占式 vs 非搶占式 如果服務場景是IO密集型,非搶占式可以表現的非常完美,但是如果服務中加入了CPU密集型操作,我們不得不考慮重新協程的調度模式。 在Swoole的協程系列文章中我們曾經介紹過IO密集場景下協程基于非搶占式調度的優勢和卓越的性能。但是在CPU密集的場景下搶占式調度是非常重要的。試想有以下場景,程序中有A,B兩個協程,協程A一直在執行CPU密集運算,非搶占式的調度模型中,A不會主動讓出控制權,從而導致B得不到時間片,協程得不到均衡調度。導致的問題是假如當前服務A,B同時對外提供服務,B協程處理的請求就可能因為得不到時間片導致請求超時,在企業級的應用中,這種情況是不容忽視的。 ### PHP的實現方式 因為PHP是單線程運行,所以針對PHP的協程調度和golang完全不一樣。我們選擇使用[declare(tick=N)](http://php.net/manual/zh/control-structures.declare.php "d")語法功能實現協程調度。Tick(時鐘周期)是一個在 declare 代碼段中解釋器每執行 N 條可計時的低級語句就會發生的事件。N 的值是在 declare 中的 directive 部分用 ticks=N 來指定的。不是所有語句都可計時。通常條件表達式和參數表達式都不可計時。以下類型都是不可被tick計數的。 ~~~ static inline zend_bool zend_is_unticked_stmt(zend_ast *ast) /* {{{ */ { return ast->kind == ZEND_AST_STMT_LIST || ast->kind == ZEND_AST_LABEL || ast->kind == ZEND_AST_PROP_DECL || ast->kind == ZEND_AST_CLASS_CONST_DECL || ast->kind == ZEND_AST_USE_TRAIT || ast->kind == ZEND_AST_METHOD; } ~~~ 協程調度的邏輯就是每次觸發`tick handler`,我們判斷當前協程相對最近一次調度時間是否大于協程最大執行時間,這樣就可以將協程超出執行時間后被搶占(其實也是主動讓出),這種調度表現為搶占式調度,而且不是基于IO。首先來一段PHP最簡單加法指令執行的壓測 ~~~ <?php const N = 10000000; $n = N; $s = microtime(true); $i = 0; while ($n--) { $i++; } $e = microtime(true); echo "php add " . N . " times, takes " . round(($e - $s) * 1000, 2) . "ms\n"; echo "one add time = " . round(($e - $s) / N * (1000 * 1000 * 1000), 2) . "ns\n"; ~~~ 這里測試機器主頻為`3.60GHz`輸出結果為 ~~~ php add 10000000 times, takes 310.37ms one add time = 31.04ns ~~~ 也就是1kw次`$i++`操作,耗時310.37ms(我們忽略開始和結尾少數幾個指令簡單的操作),每執行一次`while $n-- $i++`操作耗時約30ns 。 Tick的基本原理是,在腳本的最開始聲明`tick=number`,表示每執行`number`個指令會插入一個ZEND\_TICKS指令,然后執行相應的handler。我們有了以上操作的壓測,我們可以做一個粗略的估算:**假設**PHP的一次opcode執行操作為`opcode_time = 50ns`(因機器和指令類型而異),如果我們想要實現一個協程最大執行時間為10ms,因為我們調度的時間誤差粒度為`number * opcode_time`,比如: > `tick=100`,handler執行的周期為`100 * opcode_time = 5000ns = 0.005ms`,10ms 誤差為0.005ms > > `tick=1000`,handler執行的周期為`1000 * opcode_time = 50000ns = 0.05ms`,10ms 誤差為0.05ms > > `tick=10000`,handler執行的周期為`10000 * opcode_time = 500000ns = 0.5ms`,10ms 誤差為0.5ms 可以看出這樣的誤差顆粒是完全可以接受的,誤差的范圍和tick的參數有關系,當然tick越大,對性能的影響為每`tick=number`條指令執行一次tick hander 約`50ns`,性能的影響非常小。 下面有一個例子 ~~~ <?php declare(ticks=1000); $max_msec = 10; Swoole\Coroutine::set([ 'max_exec_msec' => $max_msec, ]); $s = microtime(1); echo "start\n"; $flag = 1; go(function () use (&amp;$flag, $max_msec){ echo "coro 1 start to loop for $max_msec msec\n"; $i = 0; while($flag) { $i ++; } echo "coro 1 can exit\n"; }); $t = microtime(1); $u = $t-$s; echo "shedule use time ".round($u * 1000, 5)." ms\n"; go(function () use (&amp;$flag){ echo "coro 2 set flag = false\n"; $flag = false; }); echo "end\n"; ~~~ 輸出結果為 ~~~ start coro 1 start to loop for 10 msec shedule use time 10.2849 ms coro 2 set flag = false end coro 1 can exit ~~~ 其中coro1的opcodes為 ~~~ {closure}: ; (lines=18, args=0, vars=3, tmps=5) ; (before optimizer) ; /path-to-tick/tick.php:12-19 L0 (12): BIND_STATIC (ref) CV0($flag) string("flag") L1 (12): BIND_STATIC CV1($max_msec) string("max_msec") L2 (13): T4 = ROPE_INIT 3 string("coro 1 start to loop for ") L3 (13): T4 = ROPE_ADD 1 T4 CV1($max_msec) L4 (13): T3 = ROPE_END 2 T4 string(" msec ") L5 (13): ECHO T3 L6 (13): TICKS 1000 L7 (14): ASSIGN CV2($i) int(0) L8 (14): TICKS 1000 L9 (15): JMP L13 L10 (16): T7 = POST_INC CV2($i) L11 (16): FREE T7 L12 (16): TICKS 1000 L13 (15): JMPNZ CV0($flag) L10 L14 (15): TICKS 1000 L15 (18): ECHO string("coro 1 can exit ") L16 (18): TICKS 1000 L17 (19): RETURN null LIVE RANGES: 4: L2 - L4 (rope) ~~~ 現在基于tick的調度實現已經完成在單獨的[調度分支](https://github.com/swoole/swoole-src/tree/schedule "調度分支"),測試用例可以在[這里](https://github.com/swoole/swoole-src/tree/schedule/tests/swoole_coroutine/schedule "這里")可以找到。 ### 寫在最后 使用`tick`的方式實現的有一個最大的缺點是需要用戶在PHP層的腳本開始處聲明[`declare(tick=N)`](http://php.net/manual/zh/control-structures.declare.php "d"),這樣使得這個功能對于擴展層來說不夠完備。但是他能夠處理所有的PHP指令。同時我們在處理`tick handler`的時候,HOOK了PHP默認的方式,因為使用默認的方式,PHP用戶層可以注冊tick函數會造成干擾。大家可以發現,我們的歷史提交記錄中有一種方式是基于HOOK循環指令的方式實現的,我們假設使得CPU密集的類型是大量的循環操作,我們檢測循環的次數和當前協程執行的時間,即每次遇到循環指令的handler,我們去檢查當前循環的次數和協程執行的時間,進而可以發現執行時間較長的協程。但是,這種方式無法處理沒有使用循環,假如只有單純的大量PHP指令密集運算是無法檢測到的。我們權衡利弊,最終選擇PHP`tick`這種方式實現。 ### END 歡迎大家隨時反饋。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看