PHP 7.1からphp.iniの設定値としてopcache.opt_debug_level
というのが増えています。これを設定すると、OPcacheの最適化フェーズごとの最適化の様子が眺められるようです。
/tmp/hoge.php
<?php
for ($i = 0; $i < 10; $i++) {
for ($j = 0; $j < 10; $j++) {
echo $i+$j, "\n";
}
}
上記のようなプログラムがあったとしましょう。
$ php -dopcache.enable_cli=1 -dopcache.opt_debug_level=0xffffffff /tmp/hoge.php > /dev/null
そこで上のようにタイプするとOPcacheの最適化の途中経過を眺めることができます。
$_main: ; (lines=16, args=0, vars=2, tmps=7)
; (before optimizer)
; /private/tmp/hoge.php:1-2
L0: ASSIGN CV0($i) int(0)
L1: JMP L13
L2: ASSIGN CV1($j) int(0)
L3: JMP L9
L4: T4 = ADD CV0($i) CV1($j)
L5: ECHO T4
L6: ECHO string("
")
L7: T5 = POST_INC CV1($j)
L8: FREE T5
L9: T6 = IS_SMALLER CV1($j) int(10)
L10: JMPNZ T6 L4
L11: T7 = POST_INC CV0($i)
L12: FREE T7
L13: T8 = IS_SMALLER CV0($i) int(10)
L14: JMPNZ T8 L2
L15: RETURN int(1)
$_main: ; (lines=16, args=0, vars=2, tmps=7)
; (after pass 1)
; /private/tmp/hoge.php:1-2
L0: ASSIGN CV0($i) int(0)
L1: JMP L13
L2: ASSIGN CV1($j) int(0)
L3: JMP L9
L4: T4 = ADD CV0($i) CV1($j)
L5: ECHO T4
L6: ECHO string("
")
L7: T5 = POST_INC CV1($j)
L8: FREE T5
L9: T6 = IS_SMALLER CV1($j) int(10)
L10: JMPNZ T6 L4
L11: T7 = POST_INC CV0($i)
L12: FREE T7
L13: T8 = IS_SMALLER CV0($i) int(10)
L14: JMPNZ T8 L2
L15: RETURN int(1)
$_main: ; (lines=16, args=0, vars=2, tmps=7)
; (after pass 2)
; /private/tmp/hoge.php:1-2
L0: ASSIGN CV0($i) int(0)
L1: JMP L13
L2: ASSIGN CV1($j) int(0)
L3: JMP L9
L4: T4 = ADD CV0($i) CV1($j)
L5: ECHO T4
L6: ECHO string("
")
L7: T5 = POST_INC CV1($j)
L8: FREE T5
L9: T6 = IS_SMALLER CV1($j) int(10)
L10: JMPNZ T6 L4
L11: T7 = POST_INC CV0($i)
L12: FREE T7
L13: T8 = IS_SMALLER CV0($i) int(10)
L14: JMPNZ T8 L2
L15: RETURN int(1)
$_main: ; (lines=16, args=0, vars=2, tmps=7)
; (after pass 3)
; /private/tmp/hoge.php:1-2
L0: ASSIGN CV0($i) int(0)
L1: JMP L13
L2: ASSIGN CV1($j) int(0)
L3: JMP L9
L4: T4 = ADD CV0($i) CV1($j)
L5: ECHO T4
L6: ECHO string("
")
L7: PRE_INC CV1($j)
L8: NOP
L9: T6 = IS_SMALLER CV1($j) int(10)
L10: JMPNZ T6 L4
L11: PRE_INC CV0($i)
L12: NOP
L13: T8 = IS_SMALLER CV0($i) int(10)
L14: JMPNZ T8 L2
L15: RETURN int(1)
$_main: ; (lines=16, args=0, vars=2, tmps=7)
; (after pass 4)
; /private/tmp/hoge.php:1-2
L0: ASSIGN CV0($i) int(0)
L1: JMP L13
L2: ASSIGN CV1($j) int(0)
L3: JMP L9
L4: T4 = ADD CV0($i) CV1($j)
L5: ECHO T4
L6: ECHO string("
")
L7: PRE_INC CV1($j)
L8: NOP
L9: T6 = IS_SMALLER CV1($j) int(10)
L10: JMPNZ T6 L4
L11: PRE_INC CV0($i)
L12: NOP
L13: T8 = IS_SMALLER CV0($i) int(10)
L14: JMPNZ T8 L2
L15: RETURN int(1)
$_main: ; (lines=16, args=0, vars=2, tmps=7)
; (before block pass)
; /private/tmp/hoge.php:1-2
BB0: start lines=[0-1]
; to=(BB5)
ASSIGN CV0($i) int(0)
JMP BB5
BB1: target lines=[2-3]
; to=(BB3)
ASSIGN CV1($j) int(0)
JMP BB3
BB2: target lines=[4-8]
; to=(BB3)
T4 = ADD CV0($i) CV1($j)
ECHO T4
ECHO string("
")
PRE_INC CV1($j)
NOP
BB3: follow target lines=[9-10]
; to=(BB2, BB4)
T6 = IS_SMALLER CV1($j) int(10)
JMPNZ T6 BB2
BB4: follow lines=[11-12]
; to=(BB5)
PRE_INC CV0($i)
NOP
BB5: follow target lines=[13-14]
; to=(BB1, BB6)
T8 = IS_SMALLER CV0($i) int(10)
JMPNZ T8 BB1
BB6: follow exit lines=[15-15]
RETURN int(1)
$_main: ; (lines=14, args=0, vars=2, tmps=7)
; (after block pass)
; /private/tmp/hoge.php:1-2
BB0: start lines=[0-1]
; to=(BB5)
ASSIGN CV0($i) int(0)
JMP BB5
BB1: target lines=[2-3]
; to=(BB3)
ASSIGN CV1($j) int(0)
JMP BB3
BB2: target lines=[4-7]
; to=(BB3)
T4 = ADD CV0($i) CV1($j)
ECHO T4
ECHO string("
")
PRE_INC CV1($j)
BB3: follow target lines=[8-9]
; to=(BB2, BB4)
T6 = IS_SMALLER CV1($j) int(10)
JMPNZ T6 BB2
BB4: follow lines=[10-10]
; to=(BB5)
PRE_INC CV0($i)
BB5: follow target lines=[11-12]
; to=(BB1, BB6)
T8 = IS_SMALLER CV0($i) int(10)
JMPNZ T8 BB1
BB6: follow exit lines=[13-13]
RETURN int(1)
$_main: ; (lines=14, args=0, vars=2, tmps=7)
; (after pass 5)
; /private/tmp/hoge.php:1-2
L0: ASSIGN CV0($i) int(0)
L1: JMP L11
L2: ASSIGN CV1($j) int(0)
L3: JMP L8
L4: T4 = ADD CV0($i) CV1($j)
L5: ECHO T4
L6: ECHO string("
")
L7: PRE_INC CV1($j)
L8: T6 = IS_SMALLER CV1($j) int(10)
L9: JMPNZ T6 L4
L10: PRE_INC CV0($i)
L11: T8 = IS_SMALLER CV0($i) int(10)
L12: JMPNZ T8 L2
L13: RETURN int(1)
$_main: ; (lines=14, args=0, vars=2, tmps=1)
; (after pass 9)
; /private/tmp/hoge.php:1-2
L0: ASSIGN CV0($i) int(0)
L1: JMP L11
L2: ASSIGN CV1($j) int(0)
L3: JMP L8
L4: T2 = ADD CV0($i) CV1($j)
L5: ECHO T2
L6: ECHO string("
")
L7: PRE_INC CV1($j)
L8: T2 = IS_SMALLER CV1($j) int(10)
L9: JMPNZ T2 L4
L10: PRE_INC CV0($i)
L11: T2 = IS_SMALLER CV0($i) int(10)
L12: JMPNZ T2 L2
L13: RETURN int(1)
$_main: ; (lines=14, args=0, vars=2, tmps=1)
; (after pass 11)
; /private/tmp/hoge.php:1-2
L0: ASSIGN CV0($i) int(0)
L1: JMP L11
L2: ASSIGN CV1($j) int(0)
L3: JMP L8
L4: T2 = ADD CV0($i) CV1($j)
L5: ECHO T2
L6: ECHO string("
")
L7: PRE_INC CV1($j)
L8: T2 = IS_SMALLER CV1($j) int(10)
L9: JMPNZ T2 L4
L10: PRE_INC CV0($i)
L11: T2 = IS_SMALLER CV0($i) int(10)
L12: JMPNZ T2 L2
L13: RETURN int(1)
$_main: ; (lines=14, args=0, vars=2, tmps=1)
; (after optimizer)
; /private/tmp/hoge.php:1-2
L0: ASSIGN CV0($i) int(0)
L1: JMP L11
L2: ASSIGN CV1($j) int(0)
L3: JMP L8
L4: T2 = ADD CV0($i) CV1($j)
L5: ECHO T2
L6: ECHO string("
")
L7: PRE_INC CV1($j)
L8: T2 = IS_SMALLER CV1($j) int(10)
L9: JMPNZ T2 L4
L10: PRE_INC CV0($i)
L11: T2 = IS_SMALLER CV0($i) int(10)
L12: JMPNZ T2 L2
L13: RETURN int(1)
$_main: ; (lines=14, args=0, vars=2, tmps=1)
; (dfa cfg)
; /private/tmp/hoge.php:1-2
; return [] RANGE[0..0]
BB0: start lines=[0-1]
; to=(BB5)
ASSIGN CV0($i) int(0)
JMP BB5
BB1: target lines=[2-3]
; from=(BB5)
; to=(BB3)
ASSIGN CV1($j) int(0)
JMP BB3
BB2: target lines=[4-7]
; from=(BB3)
; to=(BB3)
T2 = ADD CV0($i) CV1($j)
ECHO T2
ECHO string("
")
PRE_INC CV1($j)
BB3: follow target lines=[8-9]
; from=(BB1, BB2)
; to=(BB2, BB4)
T2 = IS_SMALLER CV1($j) int(10)
JMPNZ T2 BB2
BB4: follow lines=[10-10]
; from=(BB3)
; to=(BB5)
PRE_INC CV0($i)
BB5: follow target lines=[11-12]
; from=(BB0, BB4)
; to=(BB1, BB6)
T2 = IS_SMALLER CV0($i) int(10)
JMPNZ T2 BB1
BB6: follow exit lines=[13-13]
; from=(BB5)
RETURN int(1)
DOMINATORS-TREE for "$_main"
BB0: start lines=[0-1]
; to=(BB5)
; level=0
; children=(BB5)
BB1: target lines=[2-3]
; from=(BB5)
; to=(BB3)
; idom=BB5
; level=2
; loop_header=2
; children=(BB3)
BB2: target lines=[4-7]
; from=(BB3)
; to=(BB3)
; idom=BB3
; level=4
; loop_header=4
BB3: follow target loop_header lines=[8-9]
; from=(BB1, BB2)
; to=(BB2, BB4)
; idom=BB1
; level=3
; loop_header=3
; children=(BB2, BB4)
BB4: follow lines=[10-10]
; from=(BB3)
; to=(BB5)
; idom=BB3
; level=4
; loop_header=4
BB5: follow target loop_header lines=[11-12]
; from=(BB0, BB4)
; to=(BB1, BB6)
; idom=BB0
; level=1
; children=(BB1, BB6)
BB6: follow exit lines=[13-13]
; from=(BB5)
; idom=BB5
; level=2
Variable Liveness for "$_main"
BB0:
; def = {CV0($i)}
; use = {CV0($i)}
; in = {CV0($i), CV1($j)}
; out = {CV0($i), CV1($j)}
BB1:
; def = {CV1($j)}
; use = {CV1($j)}
; in = {CV0($i), CV1($j)}
; out = {CV0($i), CV1($j)}
BB2:
; def = {CV1($j), X2}
; use = {CV0($i), CV1($j)}
; in = {CV0($i), CV1($j)}
; out = {CV0($i), CV1($j)}
BB3:
; def = {X2}
; use = {CV1($j)}
; in = {CV0($i), CV1($j)}
; out = {CV0($i), CV1($j)}
BB4:
; def = {CV0($i)}
; use = {CV0($i)}
; in = {CV0($i), CV1($j)}
; out = {CV0($i), CV1($j)}
BB5:
; def = {X2}
; use = {CV0($i)}
; in = {CV0($i), CV1($j)}
; out = {CV0($i), CV1($j)}
BB6:
; def = {}
; use = {}
; in = {}
; out = {}
SSA Phi() Placement for "$_main"
BB1:
; pi={CV0($i)}
BB2:
; pi={CV1($j)}
BB3:
; phi={CV1($j)}
BB4:
; pi={CV1($j)}
BB5:
; phi={CV0($i), CV1($j)}
$_main: ; (lines=14, args=0, vars=2, tmps=1, ssa_vars=15)
; (before dfa pass)
; /private/tmp/hoge.php:1-2
; return [] RANGE[0..0]
BB0: start lines=[0-1]
; to=(BB5)
; level=0
; children=(BB5)
ASSIGN #0.CV0($i) -> #2.CV0($i) int(0)
JMP BB5
BB1: target lines=[2-3]
; from=(BB5)
; to=(BB3)
; idom=BB5
; level=2
; loop_header=2
; children=(BB3)
#6.CV0($i) = Pi<BB5>(#3.CV0($i) & RANGE[-- .. 9])
ASSIGN #4.CV1($j) -> #7.CV1($j) int(0)
JMP BB3
BB2: target lines=[4-7]
; from=(BB3)
; to=(BB3)
; idom=BB3
; level=4
; loop_header=4
#10.CV1($j) = Pi<BB3>(#8.CV1($j) & RANGE[-- .. 9])
#12.T2 = ADD #6.CV0($i) #10.CV1($j)
ECHO #12.T2
ECHO string("
")
PRE_INC #10.CV1($j) -> #13.CV1($j)
BB3: follow target loop_header lines=[8-9]
; from=(BB1, BB2)
; to=(BB2, BB4)
; idom=BB1
; level=3
; loop_header=3
; children=(BB2, BB4)
#8.CV1($j) = Phi(#7.CV1($j), #13.CV1($j))
#9.T2 = IS_SMALLER #8.CV1($j) int(10)
JMPNZ #9.T2 BB2
BB4: follow lines=[10-10]
; from=(BB3)
; to=(BB5)
; idom=BB3
; level=4
; loop_header=4
#11.CV1($j) = Pi<BB3>(#8.CV1($j) & RANGE[10 .. ++])
PRE_INC #6.CV0($i) -> #14.CV0($i)
BB5: follow target loop_header lines=[11-12]
; from=(BB0, BB4)
; to=(BB1, BB6)
; idom=BB0
; level=1
; children=(BB1, BB6)
#3.CV0($i) = Phi(#2.CV0($i), #14.CV0($i))
#4.CV1($j) = Phi(#1.CV1($j), #11.CV1($j))
#5.T2 = IS_SMALLER #3.CV0($i) int(10)
JMPNZ #5.T2 BB1
BB6: follow exit lines=[13-13]
; from=(BB5)
; idom=BB5
; level=2
RETURN int(1)
SSA Variable for "$_main"
#0.CV0($i) [undef, ref, any] *SCC=3
#1.CV1($j) [undef, ref, any] *SCC=0
#2.CV0($i) [ref, any] RANGE[0..0] *SCC=4
#3.CV0($i) [ref, any] RANGE[0..10] *SCC=5
#4.CV1($j) [undef, ref, any] *SCC=1
#5.X2 [bool] RANGE[0..1] *SCC=7
#6.CV0($i) [ref, any] RANGE[0..9] SCC=5
#7.CV1($j) [ref, any] RANGE[0..0] SCC=1
#8.CV1($j) [ref, any] RANGE[0..10] SCC=1
#9.X2 [bool] RANGE[0..1] *SCC=2
#10.CV1($j) [ref, any] RANGE[0..9] SCC=1
#11.CV1($j) [ref, any] RANGE[10..10] SCC=1
#12.X2 [long, double, array of [any, ref]] RANGE[0..18] *SCC=6
#13.CV1($j) [ref, any] RANGE[1..10] SCC=1
#14.CV0($i) [ref, any] RANGE[1..10] SCC=5
$_main: ; (lines=14, args=0, vars=2, tmps=1, ssa_vars=15)
; (before dfa pass)
; /private/tmp/hoge.php:1-2
; return [long] RANGE[1..1]
; #0.CV0($i) [undef, ref, any]
; #1.CV1($j) [undef, ref, any]
BB0: start lines=[0-1]
; to=(BB5)
; level=0
; children=(BB5)
ASSIGN #0.CV0($i) [undef, ref, any] -> #2.CV0($i) [ref, any] RANGE[0..0] int(0)
JMP BB5
BB1: target lines=[2-3]
; from=(BB5)
; to=(BB3)
; idom=BB5
; level=2
; loop_header=2
; children=(BB3)
#6.CV0($i) [ref, any] RANGE[0..9] = Pi<BB5>(#3.CV0($i) [ref, any] RANGE[0..10] & RANGE[-- .. 9])
ASSIGN #4.CV1($j) [undef, ref, any] -> #7.CV1($j) [ref, any] RANGE[0..0] int(0)
JMP BB3
BB2: target lines=[4-7]
; from=(BB3)
; to=(BB3)
; idom=BB3
; level=4
; loop_header=4
#10.CV1($j) [ref, any] RANGE[0..9] = Pi<BB3>(#8.CV1($j) [ref, any] RANGE[0..10] & RANGE[-- .. 9])
#12.T2 [long, double, array of [any, ref]] RANGE[0..18] = ADD #6.CV0($i) [ref, any] RANGE[0..9] #10.CV1($j) [ref, any] RANGE[0..9]
ECHO #12.T2 [long, double, array of [any, ref]] RANGE[0..18]
ECHO string("
")
PRE_INC #10.CV1($j) [ref, any] RANGE[0..9] -> #13.CV1($j) [ref, any] RANGE[1..10]
BB3: follow target loop_header lines=[8-9]
; from=(BB1, BB2)
; to=(BB2, BB4)
; idom=BB1
; level=3
; loop_header=3
; children=(BB2, BB4)
#8.CV1($j) [ref, any] RANGE[0..10] = Phi(#7.CV1($j) [ref, any] RANGE[0..0], #13.CV1($j) [ref, any] RANGE[1..10])
#9.T2 [bool] RANGE[0..1] = IS_SMALLER #8.CV1($j) [ref, any] RANGE[0..10] int(10)
JMPNZ #9.T2 [bool] RANGE[0..1] BB2
BB4: follow lines=[10-10]
; from=(BB3)
; to=(BB5)
; idom=BB3
; level=4
; loop_header=4
#11.CV1($j) [ref, any] RANGE[10..10] = Pi<BB3>(#8.CV1($j) [ref, any] RANGE[0..10] & RANGE[10 .. ++])
PRE_INC #6.CV0($i) [ref, any] RANGE[0..9] -> #14.CV0($i) [ref, any] RANGE[1..10]
BB5: follow target loop_header lines=[11-12]
; from=(BB0, BB4)
; to=(BB1, BB6)
; idom=BB0
; level=1
; children=(BB1, BB6)
#3.CV0($i) [ref, any] RANGE[0..10] = Phi(#2.CV0($i) [ref, any] RANGE[0..0], #14.CV0($i) [ref, any] RANGE[1..10])
#4.CV1($j) [undef, ref, any] = Phi(#1.CV1($j) [undef, ref, any], #11.CV1($j) [ref, any] RANGE[10..10])
#5.T2 [bool] RANGE[0..1] = IS_SMALLER #3.CV0($i) [ref, any] RANGE[0..10] int(10)
JMPNZ #5.T2 [bool] RANGE[0..1] BB1
BB6: follow exit lines=[13-13]
; from=(BB5)
; idom=BB5
; level=2
RETURN int(1)
$_main: ; (lines=14, args=0, vars=2, tmps=1, ssa_vars=15)
; (after dfa pass)
; /private/tmp/hoge.php:1-2
; return [long] RANGE[1..1]
; #0.CV0($i) [undef, ref, any]
; #1.CV1($j) [undef, ref, any]
BB0: start lines=[0-1]
; to=(BB5)
; level=0
; children=(BB5)
ASSIGN #0.CV0($i) [undef, ref, any] -> #2.CV0($i) [ref, any] RANGE[0..0] int(0)
JMP BB5
BB1: target lines=[2-3]
; from=(BB5)
; to=(BB3)
; idom=BB5
; level=2
; loop_header=2
; children=(BB3)
#6.CV0($i) [ref, any] RANGE[0..9] = Pi<BB5>(#3.CV0($i) [ref, any] RANGE[0..10] & RANGE[-- .. 9])
ASSIGN #4.CV1($j) [undef, ref, any] -> #7.CV1($j) [ref, any] RANGE[0..0] int(0)
JMP BB3
BB2: target lines=[4-7]
; from=(BB3)
; to=(BB3)
; idom=BB3
; level=4
; loop_header=4
#10.CV1($j) [ref, any] RANGE[0..9] = Pi<BB3>(#8.CV1($j) [ref, any] RANGE[0..10] & RANGE[-- .. 9])
#12.T2 [long, double, array of [any, ref]] RANGE[0..18] = ADD #6.CV0($i) [ref, any] RANGE[0..9] #10.CV1($j) [ref, any] RANGE[0..9]
ECHO #12.T2 [long, double, array of [any, ref]] RANGE[0..18]
ECHO string("
")
PRE_INC #10.CV1($j) [ref, any] RANGE[0..9] -> #13.CV1($j) [ref, any] RANGE[1..10]
BB3: follow target loop_header lines=[8-9]
; from=(BB1, BB2)
; to=(BB2, BB4)
; idom=BB1
; level=3
; loop_header=3
; children=(BB2, BB4)
#8.CV1($j) [ref, any] RANGE[0..10] = Phi(#7.CV1($j) [ref, any] RANGE[0..0], #13.CV1($j) [ref, any] RANGE[1..10])
#9.T2 [bool] RANGE[0..1] = IS_SMALLER #8.CV1($j) [ref, any] RANGE[0..10] int(10)
JMPNZ #9.T2 [bool] RANGE[0..1] BB2
BB4: follow lines=[10-10]
; from=(BB3)
; to=(BB5)
; idom=BB3
; level=4
; loop_header=4
#11.CV1($j) [ref, any] RANGE[10..10] = Pi<BB3>(#8.CV1($j) [ref, any] RANGE[0..10] & RANGE[10 .. ++])
PRE_INC #6.CV0($i) [ref, any] RANGE[0..9] -> #14.CV0($i) [ref, any] RANGE[1..10]
BB5: follow target loop_header lines=[11-12]
; from=(BB0, BB4)
; to=(BB1, BB6)
; idom=BB0
; level=1
; children=(BB1, BB6)
#3.CV0($i) [ref, any] RANGE[0..10] = Phi(#2.CV0($i) [ref, any] RANGE[0..0], #14.CV0($i) [ref, any] RANGE[1..10])
#4.CV1($j) [undef, ref, any] = Phi(#1.CV1($j) [undef, ref, any], #11.CV1($j) [ref, any] RANGE[10..10])
#5.T2 [bool] RANGE[0..1] = IS_SMALLER #3.CV0($i) [ref, any] RANGE[0..10] int(10)
JMPNZ #5.T2 [bool] RANGE[0..1] BB1
BB6: follow exit lines=[13-13]
; from=(BB5)
; idom=BB5
; level=2
RETURN int(1)
$_main: ; (lines=14, args=0, vars=2, tmps=1)
; (after pass 7)
; /private/tmp/hoge.php:1-2
; return [long] RANGE[1..1]
L0: ASSIGN CV0($i) int(0)
L1: JMP L11
L2: ASSIGN CV1($j) int(0)
L3: JMP L8
L4: T2 = ADD CV0($i) CV1($j)
L5: ECHO T2
L6: ECHO string("
")
L7: PRE_INC CV1($j)
L8: T2 = IS_SMALLER CV1($j) int(10)
L9: JMPNZ T2 L4
L10: PRE_INC CV0($i)
L11: T2 = IS_SMALLER CV0($i) int(10)
L12: JMPNZ T2 L2
L13: RETURN int(1)
うむ、全然わからん!
とはいえ多少読み解ける部分もあったりします。たとえば、$i++
に相当する処理が最初のうちは
L11: T7 = POST_INC CV0($i)
L12: FREE T7
と2命令だったのが最終的には次のように1命令になっていることがわかったりするわけです。
L10: PRE_INC CV0($i)
また、上記ログ中に「SSA」という単語も見えます。これはコンパイラ等でよく用いられる静的単一代入(Static Single Assignment form)の意味でしょう。昔はOPcacheでSSAを作ったりしていなかったと思うので、PHP 7.0の頃より賢い最適化が実現できているのかもしれませんね。
「おい、なんだか変なログが見えるぞ!」 というだけのメモなので、興味を持った方は夏休みの宿題として追いかけてみてください(丸投げ)