From 50902535bc98f352c04b8ca5b23bc30d81bc7657 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Sat, 27 Aug 2022 18:16:10 +0200 Subject: [PATCH 1/2] Add basic introduction to OPcache and map_ptr --- Book/php7/zend_engine/zend_opcache.rst | 83 ++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/Book/php7/zend_engine/zend_opcache.rst b/Book/php7/zend_engine/zend_opcache.rst index 6331ceeb..f9cb24c5 100644 --- a/Book/php7/zend_engine/zend_opcache.rst +++ b/Book/php7/zend_engine/zend_opcache.rst @@ -1,2 +1,85 @@ Zend OPCache ============ + +While PHP is an interpreter, it does not interpret PHP source code directly. Instead, the source code is first parsed +into an abstract syntax tree (AST), which is then compiled to OPcodes (and used to generate classes and other data +structures), which are then passed to the Zend virtual machine (Zend VM) to actually be interpreted. + +As you can see, there are quite a few steps involved to run a single PHP file. Without an additional extension, PHP +will perform all of these steps for every single loaded file in every single request. Since usually PHP files don't +change between requests both the parsing and compilation steps will always yield the same result. OPcache is an +extension that caches the OPcodes and data structures like classes between requests to improve performance. The AST is +not used after the compilation step (with the exception of constant expressions) and thus does not need to be cached. + +Normally, all the memory allocated during the processing of a request gets freed after the request has been processed. +So to store the compiled OPcodes for future requests OPcache puts them in a shared memory segment (shm) that can not +only be accessed by the same process after the request has finished but also by other processes handling other +requests. This means that if you are using php-fpm with many children they will only store the OPcodes for each PHP +file once in shm. + +This comes with a significant restriction: The OPcodes and other data structures in shm must not be mutated by any +child request (at least not unless these changes should also affect the other processes). If any changes to shm are +made there is a locking mechanism to avoid data races. + +The classes and OParrays (arrays of OPcodes, i.e. functions) in shm are mostly immutable. There are currently three +exceptions: + +* ``zend_persistent_script.dynamic_members`` stores information about the state of the cached script +* ``zend_class_entry.inheritance_cache`` is extended if a new combination of parent class and interfaces is encountered +* ``zend_op_array.handler`` can be replaced at runtime by the tracing JIT to start executing JITted code + +map_ptr +------- + +As mentioned in the introduction, shm is shared for all child processes. Thus, no changes can be made to the data +structures living in shm unless 1. the changes are supposed to be reflected in all child processes and 2. the shm +segment has been locked before any changes are written to avoid data races. + +Sometimes the data structures contain a field that should be unique per process or request. The ``map_ptr`` mechanism +can be used to achieve this. In short, instead of storing a pointer to per-process data directly (as that would affect +all processes), a unique offset is assigned during compilation. During runtime, local memory is allocated to hold +enough space for all the entries referenced in these offsets. Each offset then corresponds to an item in the local +memory segment without needing to know the per-process location of the exact element. + +Here is an example from php-src. Classes can contain data that needs to be evaluated at runtime, called +``mutable_data``. This ``mutable_data`` contains fields like ``default_properties_table``, ``constants_table`` and more. +``ZEND_MAP_PTR_DEF`` can be used to declare a field that holds the offset into local memory. + + struct _zend_class_entry { + // ... + ZEND_MAP_PTR_DEF(zend_class_mutable_data*, mutable_data); + // ... + }; + +``ZEND_MAP_PTR_INIT`` assigns a value directly to the underlying offset field. This must only be done when the +structure is not yet in shm. + + ZEND_MAP_PTR_INIT(ce->mutable_data, NULL); + +``ZEND_MAP_PTR_NEW`` can be used to generate a new offset and store it in the given field. This must only be done when +the structure is not yet in shm. + + ZEND_MAP_PTR_NEW(ce->mutable_data); + +Once an offset has been assigned to the map ptr ``ZEND_MAP_PTR_SET_IMM`` can be used to assign the value in local memory +with the given offset stored in the map ptr. + + ZEND_MAP_PTR_SET_IMM(class_type->mutable_data, mutable_data); + +And to retrieve the value from local memory ``ZEND_MAP_PTR_GET_IMM`` can be used. + + zend_class_mutable_data *mutable_data = ZEND_MAP_PTR_GET_IMM(class_type->mutable_data); + +To summarize, with this mechanism, the relative offset ``mutable_data`` is the same for each process. The local map +itself lives at a different address in each process. The associated item will be retrieved by adding the given offset +to the base of the map. + +The map ptr can also store real pointers. This can be useful when the data structure doesn't live in shm and thus +doesn't need to store the value in a separate map. The fact that pointers are aligned by their size is used to +differenciate between offsets and real pointers. The offset start at 1, 9, 17, etc (assuming a 64-bit pointer size). +However, pointers (unless padding was removed) would not get aligned this way. Instead, they would be stored in +addresses that end with 0x0 or 0x8 given that they are 8-byte aligned. + +To store a real pointer you can use the ``ZEND_MAP_PTR_INIT`` macro. To retrieve a pointer regardless of whether a real +pointer or an offset is stored use the ``ZEND_MAP_PTR_GET`` macro. There's also the ``ZEND_MAP_PTR_SET`` macro that will +call ``ZEND_MAP_PTR_SET_IMM`` or ``ZEND_MAP_PTR_INIT`` depending on whether an offset is already stored in the map ptr. From a4babcbda938a99527b64c8bb245ed3cf08bd214 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Mon, 29 Aug 2022 20:33:13 +0200 Subject: [PATCH 2/2] Process feedback, add diagram --- Book/php7/zend_engine/images/map_ptr.png | Bin 0 -> 35695 bytes Book/php7/zend_engine/zend_opcache.rst | 101 ++++++++++++++--------- 2 files changed, 60 insertions(+), 41 deletions(-) create mode 100644 Book/php7/zend_engine/images/map_ptr.png diff --git a/Book/php7/zend_engine/images/map_ptr.png b/Book/php7/zend_engine/images/map_ptr.png new file mode 100644 index 0000000000000000000000000000000000000000..3b394dc0c6621c997557bb7425e417139a6bbc03 GIT binary patch literal 35695 zcmb@uby!qg+de#ifP!?1q?Cfh5Hoal!@vwBF~m?q!_aAgihv*?3W$V+A|NOYVh{re zlA?lAiiEWExAv{~^SDG=<8{rU@+2J z7>q>yBn9}!6@5Mf2BY*3*0v7z3B!5f-C%;!ntwhCO2Iw+f`SF5wFIT4u>StyuAW%; zK&(%YxUX9<_zGP2@pJXWdAhm&xkm~vB_#%z6O)p+l#~~gR)~k3WL#AL^9hu{_%({rT3_jo|6x27N274W{yU%%Ce@tk2(D%)l_jB>z0<`R~|Wu#!k; zgp8OZ5{DF%l9iDcla`T@7QJuB?xvas zp)y{wSW9bNSs&A2Q-53Oz+i8|I)P=J+Yptr868^J8pSKCOF`3ZRlz1EvI2B z6%wZFZ!IXLLBLvCs5@Il=!4e4kBpax3og{cI!IkRTw2e=UD{mE+&sv}#2fUcfwnbp zHMG<-3G&x4G($_`j5LFET?iiP!4bxxQj(e&R2W8G-B?>sD=PiRj&YU4+W3Jk&>DKidO@;*aH#tLLsv^1lhAN?w51FRi3OLX1LV-|f%srAgsiWg zjfR=Oyt=u$w>g(kOUbBv-9{6SHCGR6GsTu0#f{=6957V>tL%PU;0oXWOn+Iuxc-rk`N?Ca!5J+&pv89GSM%!D~+9=rD2WM!hX@s*j4e&&She=u4cv|Xf=;1xX^)ONq zXpF4}!V@LsBkh967)W6PWh{+sjpfZfT?pRl?k<|PG6wiCb04gnfuE#|yREsACNw0B zAqsSEsD-xFi?A{#NLt9L`?`mKue?3o5bn0<;81+Hq>r?#j1^K&Qz}?D%+<=>$j29D z>q!8c0%scPsfjbtK!68yjeYeaOf6)55d;r|P&cVSxd>M`LxMEM5cFf}YNi(o{?#E^ zIm=2DoQ(qv{KDW!q_2@SPRb?7-_6;@%_dmZ+FTpsqDjz|(bSP5gz6h>1^L-(mrT_^|Y;}Jlrka zLJhnL?nWUxT80?YP`s%u(gJO0h0)hC^3`<@H`l<1N!rMxJxn|#;h{R_x`r-(0oph- zZ8J?B4O3Z78%s|^9dEp+zh;P;y0M!ECPWi2Z)9zNK=}I_hWeOcJ#4i#ZG6B~5Hdy{ z;f5YM+ITrjJvhNjSIa68r)h&Xl(fKEhRAwqdzwp1d*G}B{B%N%WuPZ4tgwoM5s370)5hDln#ftoA=TAw-2xDX z=AI#;-lo!^e-kY)q_=dqg_#ss8DPc0mJZbkb+z$~uy(WcbJNwpdj`qb1ZWxP8XBAV zg$I}z8ft*MY<;Byw1H7Ear4x6lM3}T@H7mBW5T=xBnbv+NtC}NI81VZXk%w1S-7MD zJ{axo8KGmVAEs{QWu}G11cc#(J$!v#&=_ZT8EJKtW+1{w#?Qk=FU%!0B0S8}!^J}1 z0AXq%XM;3!wsLU|k+QLp(+iO{lg658_Ab@Xz{H)EKy$KdLf*Bg&W`LFr@s@TAM7o#(OYJG^tl}WthX#gHxX}EcV|?v=r8$ zwbn$gTbu6Ch%Fc~LV3^MeNG`c=sGf3kTbG@kF&5AmArEPVqb!Wrm zgOc=sRIjph+s3Y$Y0%Vy%0g(r(#C_I2mK*PNfu#V@}l-*=HGVCdL+>2IOZ-Pa%6YO z+wvzUHL9XZ@omS#!m)|m|6EEXMe(vz;cL2ugtCpV-6_|kn!Qb$F!71qa7&8{JfFHj zbrKg8lQ^O-Y=$R4QTry-NcK;&Y9-9{M4UmmvK7@8E0PX;*gGAubW+fRUi<}c<2^?3 zQR^aa_Q-wE2lQ>Fxf(ndp;>&*ApW^9Z?L zKabo;gGXvLD{kw@GlHhXs)HLPIauU&XVaH+-1DlX+g3{rL+VG*VMXAZwcI!sJBBlcKQ2YwCa!FSnrj1;n%wR zmR4~I-c}ko*Mp4S3$6<7z9U#a<`6mYC`H99a<%0}^wx+Kpf~_$N`nT@BER+hdbo8-P!cz{6C?$_Bpkz1I0TBMs6Wq%}p;vUWncP96D7u z`owNBWX#?@%bt+axwQ6<#}Bknbyvdi3wGn4_fyHZ(9Q3IgiadS@zVSlubZr5HMqWe z6B)u5oNA_WUr#vht-gPFdAW6eW&0Ji#CQiK&(c=R!P>j+gN=bnmHnlekOvWqWrXFr zF@+1N+v6^y4MFbwi#JB%_ZtbzUcH%O7cJ|(M#^`CSaN7S5K(qR#c@tZdxM|yDBehD z*aOyO#rkHj?C6z-g-<;DV~(*C^SOS$2OQ=THEz8M#MYy|pY>BQ`|JgityKn53l_HA0GT73Nt z9yD*%N!<*s!O*tO%WdI7XkIdp>X*gQCqD;2bp>wp$wpef0Q2KGQ1ovKoHO|O zrKM@?UCZJA&WjS`+>dms2h$9ns$JgMzPZ8j#O{SJVeJhYo5(XKq~b!J?E_A*bGrx4 z>-6F;p}^)dd{ovdU|DbhZYYo`N~wjbKH+rnBo+N$I~m=0uSDC@Xk5(U-mc-Ln{2F3 z52W31gDDb^A9#cJeVk()H`*MPP%2;pQxRX?;BoZ76E4B;mdMcD%Ub#ltj7Y5KXRB%#^tBR|mTr z%5H4AFUQA2z}}vWr{w-l$0O-{W$VX>ceU)W$G?5MZ~XdpQ=(BKQ{@sZ!MMo8P{wjc;r5pZHN(7hBbI3srWLY5 z!Ti8n8b~MoYNZC}IFdwB7yr}PbQ@?4!2()N#Hhh5BmOUL0;vD_j;kx$ua*_A4h_odK0A3Un(pGFBU;0X~?VcKNPw zE<0ISAYtOYF&d#q<$4Kx{r0*VO&0+SXhJ37E56MM1Lx?^DZ}W;{{pUsW-9x!woM7> zy_Z7N@ZQC279y_O5-nY<_7*H4F6!iP9Nvoe16F!{Y9&%&kwgJ ztMuS@_qWe4IdMDif^p7&eQuRitd<1KU8WxLvjF)~shWrrWVIl_VY|@(;E8^^(5X3< zh5JF3Hnq6Xhnm-upK>V#KkmzsJ}(S!irDh1ux}!0WQ+OkGZEl)67)nW+=lqa((ZRa&#_TImJ{7&i`nc#d*jz>1u-CzPel?8J}a159@<}pneWMx?blqIa*?x zzcRMcwAxCWGfvVNy6XFmC$rDKbf6ayQ087KESSxz6@|_+8VWcf}f~irKy#6?|%;bbmybiNqlYcaZA+h z`c>K=wF4^NpN387(p9XaY;@^X+vK;d97Xct%FxYRirSGlj#bLrX-sgSk^?;P;2u6`Cp1 zCOCXwb^zZInKHV)@_BOfsr}C_3i$p9Q`HGzs}2@iQj|v_FHX6jmu(NMCRfz2jw2!k z%~`oH7mjvnjtcrDY!_*^Tl&~tD~BJdQ-n#Mx2V|TqeI!S{-uf9&sO_K z`>XpQbSl3K{Jx)6jtDji`4Tom)Aa4eMWt_sHqBwx2%nG7VYrz(;j_3I``p5Z-HTo< zImyaVz+&W$dNniaC6TgUwaMfgP(41#2sXMqpF#>_4-&QzJd2Z^iVEWIF_53nc3B!S zl-Sby0XeVw1aMZy>&KmMI(IH9t~R^4yt`fJ^WyWU&uO=wjA~u(@FEzF4#YAk&rx7M zspKk#KJD~!btN49UL)+zWkn8WY0TC+zqt`(Uzu+^UNaq?-=>JF8K*^pv-HwO#AII+@la#vOh=^?QozEz zFO~EmWVZLtp~#qo=~=VM1*#SCjXz3OY{)OfPk@*Fp_bjU+_L*?N^EIg?7`Y~vh)7H zKzvTPOw*&*yeH#O%63q2lteg=t9a5*eM6(`=C7B&>?;L2s7C zi_5eMUDCWp_hh=`+0VS)r{ZKn&&6GJj6EEYW`34biFsmswdY{-gK6X;Ol~Jg;>#2; zFO#x&-gq&!ek`od_eTG}9CN|O8_8-=WU738uz?I;cb)XEYB8`(@x4){X6=NT5g@-) z76%+bE^)W0u}DoEN*G>!uZc30`I)LoEBtDN!{OB(i3YU^J^pO5`Y7#2b-0~C&NqiE z(bHGi9LB$&4g6eVV=Q^u$!Ax%W{XOyTK9VTRrc{a8kj1F&v{Yn-5T#GvjuIAqz9B{ z8dUXl-zFI|6P~Mr%mC96=yuFbC%}>DA^Sx?>c;FgC30KO!rQsQNy#o(##pq zhUX4E%5REh14kt&lu+T0;%%V1LV^2%0YkeHx=yd_TjQOp^-FX^H}9HdcGTL+IXBc@ z5i}KF?Nro8^6HULmWTg6M&VSdi|tD`?hoe1$jU(2R0~4B&x9}G?%soiSILBbWR8oz z7T;nrRT`9K#G1-&5T&DK738(_TY^D|zLy^sK=Ku2Dt)paXM?(Bi0f|>!b|v|*EE0E z37NB@AEF8PvnVqXk$vfHPvR*TfunNM;NS+_Qev`WvVtx!4@Y2{sGro&M;mBOdneW3nMvPH>rcA>;Qrz!2rVNV%!UvoKdq^sNv7UAb@9Y8 zrqqNhW$Dfglj`bFv7s^orQ$aP!sz=6jl9$#?x4ce`hP6%M)C4fxe5~34O?G=ZqwqT z?yWf?_f8Z~=e=@5%4Nz7c9GPksO&vPcnx<+yxD&LP?K^p;2qcDV~acUGs()U%^ONm z)WoOuO`{@~kEivQn}UCQ3)frgaaPWj3nRtL_aN@M{zXjXFJm7z z$EfOR$t$n7h7T7jsv1|3`G#k(+aS^1k#uMJ7CIH}KI!wq{?oZVaQ>k*N2P=lr{?hO zTW0vi{O-Z;*>uyWqXn<8uWRP}ayjk=eAiT=)5?J{wft(tjAH6*>z19-Bv%Mp; zUC{qPF{|7YI(=nAslJ({;YDg5PW3NK&$AHDGGa)aGCjWEmk+wtXPe9+yG?|-3S6js z-Q>RVso|*Ogj&!IMl%Js9%jY8?~k|?Z1z&n37Sl>v!Z0GVytvaRYLhHQCw?YO-1$+`1R2xR zba*6)WFrNr=)7E$LVFknhpcy)Lw+T_2hP&d!~JAu8vdhH*d<9yE zyHZ^}uuMv%;1FRiVfFq9DPHPBsp$(2zvjXH9+6IfiYTGjt5h($R3GAxhN=Q)-Zm6p zt32224q9K_nLfX;>H&{A2upcSdUw`NDCpFxYV-W3$#V3?RZ82z1#ld@EXE(Y2X}lV z=^EmRp`=F_s}#0<>b!As?ni0+T z(TCe*Og!=iNuqEnDtk_ov&y|CV#~SQKpf30mE-pP{X_0I*zhd6F{cmcX7`NQJlA@g zkh>=FmYqvQ`sv^H?vQ=wW$Z0ci6Rme3m>-QV6{c&-s8_6cAhTKVx>ZEn7`zvHl)uX zFDb`7S)>e}E++pJ=Sa29I@Cm-rplY$+&c*Gt&4j2C{HWRX7L1-C*7b~ds_3rZ_TyD z)tX-MqZkFS@Q0=~cv#(L{BS^3^ZJb!OgzH9n`?_VOUX)S*YHRq(yQ%!1JlY>c$5d7 z0wp{W#Pjn5YBwL_Je@KX zJju+BskRu{xsdp5JJv*#A)e;%%qG){KZ??oyo?^UY^Lw<#j48*q>y|f7}Y27Nr_yY?t+ne{JPOg9LL<#UTrkPO_ zQ3JF3$e%kG=nD7Nq0oD1q0b(=Sqi2zGyL!g$b+^_p=kY zNT%I(RW3ZgJComi(}=@mL^kU8ua^q2TG6T6VV(>vc<;Ra5_SAlm^~#(ZwmD7t@*oR z)Sk#@f>v2SAdsc-Mb({I299zc+5IFM0`<9ceO>k_$IU(H@@)3hCiT&jMvQlLO2f9^ zd?2C6@!O_eyXl@LMSbi`zecN6i*GK!)KIgNOyh4Yd4_4}Oi{(p;j(n^8&Ak8C?Zgy zl?e_9d8GYolT6t8oOaOx65B{{o9 z(Jtao3PV}<2sI`Z!+v3}D-NPD7cSt#EXGzej3ep_4z{pqR>9wSQvQ0tsEhz#)NR2VRbMsOwc-0{TmjD54?enGhG*~ zt)z{rO%mzC`S%Ey{{Bk$TY2|edyUG~h15RSFN)wKTC)0#SBsCP;=)9^67Uqm>c@lE zG>)I0f<4tzNg5{Ij$+^qG-A$;B=LSrntFQOjA>3#!DN5@ImKgE%rk}p{(2L)yPa0c3Ot4s^j6Qs8d*%vxHMCGt{RsOvpG%-5Y_Y>!HIF(v~{n zBuuXve?M-*DI6JRtyJ4_{h?Cry`5q-FBhESM-B~drrYm=S<+xwz8Wv@gIjl&_U;Up z;4Apq=FuWDR<1~v@^Ta5R0Z-HZrsEvj{;X^F4Kr8Fj;su|{8^iIOO&7S}H`$ovE>^lK2B%)U zD|%WqnJlEEq~wmtUDq+IA%g3E^n^+ks<_PwiS8+P5KcV{EeTUM<0)YG`g5e;$?Cvz z$`Wj%ZWk#yTb#O@KcS^LOjRH#EBHm0H>*z~?8|tP5p$Nt^CYd!uuVBq=|=iw!Cj@K zxrS@@!-K`o6sWv8a?LHBFQ(FF8yB?{@TX}+v!)Qfo1tBREIblq*k` zmvB8*>7{zBQDJAC=%B@I&Sa(DyhO3{OkeN1y_0Twnbb~km04xUiz0O*pg*LD!I_O< z_H88XvCr3>dtW`1oRCT`4>JV-=?9R725Eyj{dgk4LySp?cWO?)Jk3{|qO_iP9+mR^ zH0-XxrSt1&nep^tvHBNv&g}?cPNH^Ftz38zfZz=z>o#(em`F z1vu$k9Cek!|779Cx$2E}R97?s;7jF{NhGhu^?huYHq_XdUfT5hP|{GEc_~vso6dyg zo>F*C{uk!^efVaE;PT9NF;b^Eln_l#cfubP#aBJ*vK`TVS%TBJu?WOIxY?`sK z$B1sg;&Jfcj?*V>v;mwrB$B#LMswup)RWI35GnEGj9`N*#!FlZ21^onKXmEoS>Sip!dn7QS8oa z(6vZ2Ifg_PQTxVWQ!@ig-R62h`$Wz%Gk7jPS(z`s2msY2MBI9(;Rc#&Hi7iujy|PF z$qakWK{P#0?QG$kQE*`nNWM2LZpkH`pCC8A2$|^IJ?wa-(b;+1RXaQD5b~*2(Luhvvwqs zKp46KNH(Nt-}&sr{5>db3;1%g5ktjn2b!uDycvGk2`R5IA0)PWvbK`qx%XH*|{pw%5xUb2Jjgn_n4Eo^hYi#V~oh`IS=b4fmx}yUayjD0YX{3z}dR?mc!@glNa@lF}rUp z8W|p*(5o)Fbw=`)n9%PMc@$C(nf2k_H9DS^>jeKs&OH2*gtfWeq0Tj$l{a~pcXEx+ z7Uj$|H20t5&T@z-o9ZkJ4r}K?;=g0g#-xzcJYP=azx|8mmbK46*s)zr$S40!Erjcv zf3*NKqs`$P$t7oU z9zpu-O<@q&&I_M`8ONkeH0N$E`bU6VUr5&vypCYvg6*plwQ3@8{rX!_@=n6g-g!Qm zYZ@f6^$$xO%%$e zooEa1&Gp=suCa#|Yk5R&FL?s8&x>S}ymxI@N!+f!Qa7-|s>%UAJP0>@aNo)5p$0hz z0L~Z&1us$-2QBo!6%yr5Pf&#rSMKU$3RF~umWF)C?U@HNyB;nbqCT)2yD2JU1?mcl ze6{xzLw|q09;tX1;We9kL4IAH5toDy`sk1SO|3uwM+u6TNBPp;~*I$F&iL+u!=Rkg8v1U0rCLBlSk?L`PYm zXhW^2skpO&46${0J~w7lYOAK3>Lkr=6a_BcJuZ!umn>tP-I+v~zPt9BmJ@qo9rk8~ z^JhbjFA+b*{3tU|)}+x3m%wV{ln3n$0KKlPaQeN@E74^>4bDsNgd+g{A3(EF2Em;V z@2xIv&$5Xd3eDbA0?hnp+1C;rCTE-{t_FSOr|y+H;N$lb>_uO?nI%&5>t=#d7QgQ8 z&4arlKOh|h=ac_6zZ@R`SEpT+uYuj0m889GBy_{z1dAa_+tf_QQaQSU(>LiGqp=`@ zGws8lsGW;@R|y*)JKY|S^c*T;>{!Z=J^GR7FwK@Cv3&w2gGst(6V((&ShLa2B{2&` z=)Mxg-0Y-PBv$aVuod(zSP2lPGifN&b=&6zYeNFN~?9ndfkq!-i zm8&^6)2}3IGTDf4;12Ut^D3YpI`H>&FM-i_{LkoX)NjU-lU##j5I0yZe&V=6$vPmq zo4(f081m+w!HFz2a|v^e?}HI|14#>ezHFW z^94Y5(c$@XG0hFkDnWn~p`N<0{Z)Qeg+dB!!{slR)N&8HIaiIIx)m0~1P5GZtScF# zCDL#oPY@Z+OO`j&W2s?z`SCu1Qe!~FaIOXL_N|_@-sBxTw4&z%Z-uhbNvWnYosAnP zqHQnX%jusUypj-!Az1?W`bsUz71^RGo^5>l zOS>q%kdNHp5scyW^i6ZmMd2SUY46p8Gd=iId#;JrP{CfpgqKe!=sy_&Y#!w#KpPc4 zt%V}v((0+Q=N8WAM-9n(rTM_#yXJ7KiiW8Ep1w@menKDQ8Q)@1c-pvtQj-S<)3lYR z)I@r>!$Nn7B(A%3YO~>5fh3sq(RKNRk@(1XKVEkFkdb>m&iu6?90d|Nz~bv z*v7?v8@)!UH+@$mP<-smhxr3(%t6j4-Aqc}JQ}CxAr(*RMSjq`%|$~-!b3>B7k&^( zBDYK4Awe5ccB1$(ytjtB?Ia17YRW<0`05;QjN~cY2gTWhiOt0)rTX#ki@e`YZhUw3 zo0O?K0d{`N{oZ~POC=+L5}_;}e**0yxI~MG;Krpx_MW+UrrOA`+7~-3k^s@k>a04YAaxLU5AqW zT6Bmi=A6FQ%{OECy+j-dAf zRLVncZiER{5i~g4x&GYio(`vQ8@QT^M>&u-?Ek={NBjG{w~Q@vYao@|kz#LT>cRZQ z0sJ}wog5A+H%uuVK4<<>Ox%J58;=hn06+yk6c1WD{!vEo+CfsIUcIsh|DntNs2q4H zftQ`1E+{wrpV9u2*ZF~|?J6HFMl(Ww*xU#$_(uSNDo6z0>*j?F@pMv%(HBDLRT=)( z`j5=#uima6#MphAX(&a_Dln5x%_54w zst{TcdaCS}A0@d^ir- zkRSUI(hC62q6VmZo)2%GuO)>B&-{NMJg)?H`}?Dm`d;y-N-cOKkjsq&I+fct+rG~0 zu6@|otB@GYd#FesHBXl#MEDe-+XP&0iwfUl({g<-SHNoq{W4XXyC4`TE;@xv7b+I{ zE90@eNUx9$i5awKzvdbvijw7PnLw= zRP@dR`_RuJkc{Hy6`R5gD~Ir6D7*Q>z1Rx6Ka>Yz#X6v5eASaF=KXW@Ni8LJSo{8E z?XQl-*ujtafrjMqJtOHQL~CZ11A0>cjOWlU2a+Z<3njA2Z?-K z`mkk+@^&^P=!3YepFrH_XgV2o1OQ);<&Sk6`;fMimzpHR0&}%@FLAx9& zH_k=?YN}pWD7;+NvVBka==aPvpi{HPX+_7y#MKEz|W6Cf;h#cXP3#&4LF78 zEezFzyfWMo_7;-+fc3|^=SLHWl1Jb;H9r7a%n~xm2-w!v&#@O!1n0vj(Xx35h;w>_ zHdg?WPTRwrdAz^9Y)79n7ty{xsLKxjxB!WX?8MH{wc-m{ilg_pRfIOHpN;(vOfBcjHojMrFj1smcH^&Q z9-+Fzii?-c?Azb`F>@Bu547U@=b!W;Cy69X7h%1>MAyhtd14Q$eX8HEa{DL&oiI%# z34^8s*|xPv>GW3fVtEDCRh#MT3*NKJ9cn;+!1qqZZ)PKVjxh1@tFROv5ytSjQ)ExjswjrYdU} zB)J`GNDlUXR@DO&Hwnlw{5&zompu`Ho=Q*Dyn}^L5p`bqnemJ!0fE!_WAjJ&PcH)q zAW43UHx$hlMBUD7_b>2v>f@EN8~46J%-j>B@mSA8r~TttA^&z%q4pT`=>4U+AyQ*4 z7_hWsU&0r-K*qfhrO&A<^yPiq(O$1Z^cH&ORn5f73;cZ4YU}Q~NFPoCVL%DjL)>R0 z;I$2x!_4CiGsmbe!Y=SmR#-`-96SexCOe#IH{@YZqnGm}Wp^R@P`zV$IhZb9+EN_32z%vJ=n(N@YG>d;N_>khCFe z&G}fd_wzV&!f6-`UO+rF%oGin5>^?ub^{u@JfAq#i|F#%iPN}&yya0CG7L+`s}Tic z>ET5zaG^Uy>8{6c|7o?hJUq2EPuv>-u0(EM2B|j3=!v-W3jtX2UeZzW8XkYTT?dlj z!qLsN?KK`awb{lk3lsS_k2*Q2NG>s+nmhZH@^$3q_pgI11*u{QDzM#ZQ3`cH1%v^` z9rL|22Rd&8|QM6u`IJxFvI5MdY&lPb)SN+kJ>c#tCq!-Did12WwrXZyP`>g)) z34Z0F6EauV)FGQ zf~as?VRTnn3_)a(jaZ_A9ufX`VGpQg#faBpJy2sBLzmM?9)cKR#kl_}Uv}SBb4#8- zc0rAb8B)WpAwF=O14@66SL){J|6VClk2h-zBhkh&2^Wq1i5bW%E>Av({YvwONv?bl4&}5Bg#I-k z7Z$oQ@vUAJOIbUckf$8^k&-9MDL-bfEVi1S=nMDn!KDgrSoi(aB5w+VeZeK`dp~4x zxSKzav(`XnbKE6mEAa=2fqZYWNz@-5?(d@xE2>%-$AI#2>RkG)x>1Sr;4JU3z$(y9Ow7nH9W%MDgX;;z)1(mk94BApBdpV<;{hH}UGD z<)`gZzn-FPeXU1e8Rkl#k<>u>rI-7CuRpsv9%zOw;Wx9{9Ks$NKTsH%?|Vw}y9XL`!HVZvJAv))sVvKEE4ab9I=@w z&zAUl<>kz6+9_aDYE+{4&Wn5r-h_!{9dz2nrQGj}DSu9DhHd=& z4pGBi0PKdIEQTTC&YS7W)B$2~`)5^~ZA0K3@n|Zfi^3>W5@>JUs?htfxFb7FN zA9aur-+Eif5Z^Y{4@;kOWjTp+p-pr`x^7tZ{XKKUi}VKBG-@p2Fr)Swpj)m7brBH$ zscShg&eC#_e-a1eXvUyWCu3}_UvbqMgoI0P*c?UUCRoL6U%rVS0>W94HDoFS=^2O8 zLc>B{ZEe3o?>$++l`)3!H(u=_)l}P*eAOVpdf0aQwQt+uHaVQ5%6JgYvHFo{T0C11 z!P&+uw@^NRbD#<;R|N4((10=$NQ21W!XUwD^#SmkB!4YHE^9SKdk?|xe+E?>2^rse zPC|5MEwZL{2%lq#r8T!Htn_PMyLJfhUI#X{Nl@Yd zNv@Z{=_3rRKoOwBrRI89Rcny>^ySJ9+(Y`D4LafJJ)Pt8>18z}HGR}JP77Q8XG=*- zN27LzKh}BgUuzY&Zxo11LIrcF)DC;1tm@*gL!e|J5;2%;Rk5 zQ+IdY3((RQ-wRZMri**+&F%I>S7}}arNCJ|Kfg+3+D`rB<%N%zgrn5IQd6+=d5u>7 z*k)WmczY4VnYWKrr{-SQ>Z2|1{xR!nJBgd#@Z6fb@^AmRhvBavb3vP+h5jbR zP-U4`NHrZ4YjTX}m5?DT%8E>YH$v6@KW{V$L5Kd6J)=SPjGD7_`@7`~K-yhqX%$im z)o7rKL1z44J^Y`05Nj2;dAlfLR!eVaD2?<^MjU6Q=DjXB+t8Uifq|f~4D-U*-c+Ii zgfLecy1jCl^%{ZuDl#mml70w4hq7{NT)If92z3)_JN}VQ@=|%l)4=SxxciWOg31A! zz8Bu4y4Uu%c23gq`LmiMe=v-~VMl$U!+1<-2PxH3T9<7A;kG*K++@*T}+O z{->5Mt4qYGheQ$5it=Q|{8tT~$GciN-{@CYP93e@cha|$U%ta6g)UNSy#U~rKDO$S zflpAtFga84W$q~{@W!W7pxE{2)24p$rX?xL*8mCl0)ZbF9b20_=MiIz-ulY20$yEu1}V3PHkQO%xntp7)9+&ZX0 zz&%h5Me=o6bG=MSmjqN;eFyIW$dhAkDGn6=x@1y+sW~iQg*G!nh};=qMI+BsN%f&p zF@baFWM;iu(st2z9y2M2z~JLSE!vLDP!a$M>_)NMTVswdAU~TTdeE`@KNvnOKGNOvKE zGhfFp+8Cur>I{6p`+UANkn?>?r>cYy4Dzh!UL!rg)GN(A*F|C( z`jSP*zb7sN#dyf&SV~{3r&BV$QFKWRx_j z1#mibfFkXpm7h(`_)&DDSj`%MbJs##ho4qLq(z8k356kv)&H3SI{Y+Wxg8)HULdAl zyooiu@&Y(k@_|FJ0V;vegaBfhM%k+r6qFm7P~YaxLaTB z8Tr7XDOnux%+HD1QnFuwby$e8MW%nf>+`-K9+Voj6DHiUNR{(q_B<)w7jKA;3xLz6 z0wJEIgBVAP|EXq^BELaaE8~20u-p24Ku8=(fXaZlxr!kwbPuH46Q^O8N|mnKVPJ_r zvl(WRwzC?-{;Q1;B!Lzse+5-$ z;rR=FHj{b04=N~9xdQ+$(?)tx3%O(i0joaoUoNq$_ z@L)Xp#b0z`7+|%U%1U0BkkIm!45IA*;=WekRn7P4mGiIzNk#73IWw}qPtZYt@YjNh z-ZBWjK5LJz__vcV&=x|qNbL@UmFnxE`TyoK1!o{Y_*|y=UDBjh_M?qGAz{pq)nKKW z)wb|kH~xM47Z@VV@tLx#HidC~wKh^_UL}7n&1QfFyMNXsL?|7!BVE>-_ZKwYEmtEU z&R4(|Uoh*{kk+sZ*qEWulh>HX1HUN6k^2OB{Zf4F@&1$$$N1aJ%!y96SQ`}X zbxBv0@sii5K`Vb6#fmhCb40#BQEMZYGcwx@*|)LIs($yIpkF+D*^*Z&q(8Ek#yq)8 zS`B9Jh0}`{g*Z;~%NbXVng644cj5={=^itRmzudthDZw{0*cdToS=HqkfreevNsM9 zKLnfRGVLls9TpPAl&G>@a9C`!#O?7t8Nx`A?Nk7xUAFk4Zw>T9h1=pzBW)-E+lEcY zJi05K97SFWMRe%LW*uI`kJHRLL7~=X+vgtD48*{h>5j`j9SP*c*K8I+WLpfvo*3%=M3mh_VO}}1IKLLhg6sT^m6g~**mmjUP zkp{45r_LBHm%=bGZ|Thc$hb$I+AE$Tvc5PxrI>x44PqX{<(5b0IH7Rh^5t^zxja%5 zq6t8N8bf~KfJmVRXxqbOo`c_nVRPLHa7z3Pv&8GT!@WfQ^LGK1BdmcI-v=DF7zlCw znVQ5^07_>Gpz6jhUnP2}B-gj*9d{&=FclqIVNrT}@+6L!jRlZd9pF_zf=PNq;Z&Om z2`-)p@}Myw1jho|d%c}s%Ls%955g5eWpfiiLzNbx!}xm&RO&c>Z&%u1swIG8WeD=~ zbiW^PZRgxGcIMF%knio_w0}ej0D>_yhP8}BI5ix+)f^tOIyEW*OI|4{Q0hE(_Swnt zBT%S5`q-S``$I|c=a~IvD0669Y5b{VV9)>x$hoM_=4zVh ztI6oZ*W6*#@eZpHj7dgG8~mHb3F(k~CxF|j zQhuXYB^>a;TZzR@fP%7X6JcZXUn(y|rszLqg$NEX% zpU7*Vl3<)aMF|C~z0O6**0mJ|TJ8KkfWcuAQc{XQ=H7I$-mAQ5cy8Uc+F83z9Mf^6 zCcrvf90gG7Joa;m|K@B+3m3Ne1uC$)v3dC*7Q9dntN_GPuG_}ruTDVT6%rb7s6@R? znHsTj^ojEq%_YesSIeG@$vkYh-h~$c$@8bsK=d={&O*4p7i!Q(@W}89z%*9(aKeu&E9|G4wx)(k;%axEW9|iw8^M z&-*424(Xx$Jxd?p0B*3>CDu9#B8-NXhu}CNJ>K(PAA|^!d?uwCAmD*V)l;*H2lMz; zCZHbzH@pr0T*L4DagJZz&Wht89Q{^bCbw+b@w{tK1j^|u^ItE7*JKtRR_2P=l|8-S%yW~_UnEaQd&|P38^8ZJ7nl?P?4dA z8Z1JZ8Ke=U8$?1{O0b9lqy#|_lol0fNhK6;U$@WuuJycYA8Q|LeOmj&`UW#|&)jv@ z|2)s%eR_Cj`WReD`J3>ke(Uc=(a;hhQ$DqDhd~4<^M{6=Pt+ z`Q!r))<^B&^9fX$2<5e_k^NLH>iO%rwW~8Y!4Yfnn!`3Rtm4 z4V$7eU5-YS_wFs9@^P>T$eK-#p3eG=*a0umiJNjVhP!w!Kww8Gb;bu&M8GW~6E0@j zHgF;Dkc3;7;?u*SWl)J+9vzu$rvT05xzfvtrVw-PI#ec3ba+*7$C&eTzmdOp8d;(1 zY5%)q@;vqbv7DWuQ1YGsk(}MG7OpjNs<^N)eMOpY>;!f%9h`~JofyXh29l;yMC7-4HZn}=f+@{;9o&B&w3{VHLk29sy-0D0 z{|18e)Gc6e{ApIs_ok067zh@Dz^(ymQnk=DBW?Q5>R8WS^5=}6APF$O-G5tk3^j4| zC9yxPVd-@Ch_{);MSb{aTP z0^HtXpuiSlF$2nA7!2k4TPl7XKi8@^U%pwU)6PBtVshxvROBHjg-(G?IP;xY)sycQ zKnc#fFBHR)ob~LP>wIgVs+#K2BK?Ek&o9e2)PPw@G@AWq^6;fi_xTcbkyePp0d!B< zfRL^EwiA^^l?pH^D)Y<*UGkNxGo^WD>bj6+S#)z%FRCCq(h8Q%&#LG)%<9yJ@UP)#=KrK6^+Bt=Z#c2uKb zj^wTCuN0CjI&WowHn~$#lC*mf%+h^)Pi>f!A9N@YnU_?0X22^gK|7YsxdeZ7sBiaL z+*4>kMGNS~W!AX*E(B8wxCTsT&?AauJ0I6CaG=wT7`;^d)a42O231AFtZ`h@F~Mb|i~(WOxE@=qr+LNw{uMx1nQc_} zO+8)ED!&Q;s3%acXy#1*tt=cf%rkw}HuTsywwy4WmVnGSt-pGd;s3H`HW zRq`)di%e{=6#FI8u*Cp=rXlSm?4vrEpt=D8TCa?B*5Yl_a+5HFf~vZ7<-z1VeS*d3 znc_kqcqZF&DSqfeu0oJKd87fZCdO`YQ8Sr_E0gEwZDQnQR3_aqi`dabF2)pVNROnm zP>8X8+&4mAzk`s4X8|8x#Sx z9|M=t(XSY2uZX zz&_A7L6GfwR9JRe#1~;FYvSZUh9Fu|1S&77(OMU)#y(+jo5rt_Wq(Ar*6*kDL4$p; z1d_mqcW=23!G11KD1Lc;CTxXSK@~aEU_|uctkqII!xKpKiRZ(UJY3BVS7A1He6X~eFq&e*X$a5Tu>OJT&5AuzR zoC5CLsRuFkxD`>9Ht=&dR&$vWrVEyab?;rC?nf|@ZICnE7D+pU)AjV_OaFlLHlNed zMvoHb5qu+9ahlMHlmW~q1oxX0tth*qoPHtyi2klEc53?bY-DFGlI~qFXRStpurdlk zj4YXYMT%|g0xN<^E_rlrL@2=wDVrYPb!2O&{r=VQTVy{^SAy+|q!v0sCByeFLw^Ol z?qu&fn#Efqm2A{SxQR9x+u$rAqA7=y+q3Z;W!3!nm)QFW#i5}ua_=h$Prs4m{ z!5UzaVdfj;$_Z3Xhu(2ajYnr_%eFbYUrV_3CqLsQ!>067I|zqowx|rN%3z?V-k(wQN_;cVdcjNvv@4kU!;w4`JkF|Y5FZdZIONe_Mz zZMwoGOMuVl7ib~G7mCA?NpVg-;;1*auv86B&*D~Op9fdod)P!5xh&jwk>!|Ug#;V- z0=_v(9h>w;q!-Qm0(Zy#0ky z_64nLFtGN8=43fkAjIk799hz0sZv-r7&w&teIIZ>m$REETGacb$w$+{XI58ckQJoJ z#dAgDyi<3o5T_x}^|&#ivP*fG>VJmw?)mY(LbDCgHB(t@^7Q2t{62J;!ZRG+G*F_6 zcwWa<`-`VoCCqaL?A4`VrmbIARgla$L-(PnBXem7WYkj47aHv!PTkSM71P1_jrX_cNUuvOlq)o@f>mS zkvq&X-{4a;ke0#PDJLR>BHqYe9SjEnw&6H31e>iO=Iwc$ ziiE+Zz*!Te*y+>=p=X&7a4xdN6%3En6;ZM&rx8g=^3@-yf<=LhS7bUddgwIZB6(g5 z=o7NZIbV+~yAdzr4?WI?fW9awg7?igW(;Qm_NiB z{~^m}b8?q!V1Z?)oSgqWsr-HfbEdCu>A^!-`bPEzaaR{$@kdW_7{B0DZd%NxG}eNn zr_6@M)(<|0cW_Sy)%&nyyFQhsP3XZ%8^T1mE|>BYa}GZ$U-0HryOG={wP(SfFA7u> zpR40lkTK=o3V~eYi~K{M6Nzm>#^2zil(Ngfz^)^#ZJD^|0xe(4^2ZY1rU0P3fxtSPOoAks8AT%ZYhHYhbSJps{rl*UO<`#U?Tisgx&2?V9Vlr04%9TV@i2VsrC`dO?Z!F zx6FP-@?fRWVgWla)!Y>fyrGZ;!hBeRq;Gu80yh(gvB*8{fm1sunlR%KRA0K)H0!@@U*nWQ z-z&%eS^Bw43*@D&=cKh9zI+W)jz9_i8iWmW{{xbF?_$pnk?GpI5Du?eW^Ws)p_`vX z`QDs#0o8kpjjH${aR+WtUEa#f1t^n;z_gTCzC=HCdV>s_kvMgn_XFd#1-RgWirde} zzJ)~EVZ^}dzX`aJevx#IF<3JLPXYu4Z8DOZdtz(FcyMijZP55n+K*a<**4_Q#3s!& z@Gq+s7|TH2s^j8FC1(s3T7fka!LUf#s}C}M`QLj9*xBt{f&Jmv#Xdq3%55@!_1D_! zt?wrdw~d{h&m_I!cP44~#)vM8p}V=+m>Wt^cAtDzpl}~mH2dpP9}w;{exb0-?lS^W z)kcg?okKpU6L1^M$jdJtez|{ObicHU5YsS_{D$y~q3@^Uw3T3m7rBHQy1_Pp3EDv{ z&(8y9rE&NqPL*kknb8Y{S|HJMyhbqD;y&6%BRcT3xW9&{=}2~Y!a>Ls^564Y86QMl z!bx-@lF1EIGvFU(uw)Cj7{vTxx9NP;dqwJ6o&KKS`;veI2|ndl7_nL4?>r8|rhE0@ zpnL|8ahc%`)&@Rgg?uqmc8dc@3l9)t|JjMwkri&7HL-8i*1iucDN2|x9Hgk7_&sIR z9UyfB|IP!2g+hsEWj}YM9FCYRZ#WC{{W@SgLmIy=%o6jNIPs^*bas} z3_$*V7^MQKfx0K(z=jvPHok9URCFLz4UmnvI+bsPXQLpM`JlC1o%1W`eD0jfyXYKU z;5(*AOp2nNKmb=Xq~JeHC-VkGJQcqyu+Yxi%53xlI77ZH!*d$ooUL62*uC=jFTP4V zFT`87BOK;|VSl86(#3qcuklt&thaRX=PUelL7<2|12kp!4?1iVXb(cuq44i40K#!M ztiddmDuZF{eG2s!SYLkx*bYnwWYY3jmG{i`BKQ#^42cG+Z|?lCB2U)_#UqdLcWo>- z+=dbc-)RN;r_b5-$Kr^wnsO0clE=Upy>2IhwQF>R-P%=a0df}wXNim9w)Ld5=@z>)AD_T&tpHio%1KGyuu0-V4>64Bcv z_t_Q3E^xma!-0f*a?Ed+V6;hQr$9_n zzD#bqt1O$;1MX-#k4Hv$#97yAW3{8Fu!SR*$!h?E2&BBf zy24Z+{K1u&?Ae2}TxQ1oMS|j91&E3ZsuS&ig1)d3W7!KiI>p#wI&UKLsbno{0vI!Q zS%JDhW%JMdZ|kUpDA=1Sh2z%O)wrC?2EEnF^5&DPIX<-3ZTlf7M<2*IOKS zuM6YB`ZyR^BN%9XBVGA!S99dr01|EP=o&DL6oAG%?opk)hs0ftp-ysxs#0?2jCk;W zPq|T{p#Dk^Hw&efUOHD7mg#|TS8n8RQ=j`aA;~;9i)(WkaDE^pUvXOXEnzqW_)F?H zk(OZM#Gh@{(`pXj1Q4lg>2pPuiHDW?+Z5~PdrOYcZnWlXtW_`wmVw<#YL}HH*uPw7#WQo2`M_P~7asg_nxL#QFdR*pd!k^B3Y? zdCRUF1&6-Va!3Sti*K8On^P>Q>Sww&$f~#{8sO3)+(BGTmE~Xd1art6^%qfN4veNv!N=o4P~uAc z8Z<6df3kKfQ%?bdD3jg=`+uaV9D!)g7HwwAR;vCCyc6xEJkAPj0uZ_ozU`eR;1Re( zGa~wbc*OpfBm8%cF!x{QN)R%Tj0so#DpTKtPIcQwtNVYv84A_linzsCbEN(4-uvSj zDTPo2FBm`Um8u_t`z|{C-uR`zzU;6LOsb8D!9T^2v%y#w+zH!2d&_JjnG++Hwf-lq z`0^MM{MJM|^mPDs%uSqH1Ac5EqDm3MYXp?{D-&d7g#uM?B60JdU-sQSQ;f`xn< z5mS5=Rcm0pv}>a%#SW?JAN-RGz#te6-yLGoMA!=h_j$5MHix83EY&#){Ns+(p!Lay zFC<3)7phnWt9E)=;s;cUM}|305ZmT;hDcVG>KwZR!4##_dP#rHyaKqJ1SS94>HkP7 zZaD-E{3)=dVThV$WrkIZ_)9-0YVLnFwYe=mfwBxPT~r1299(T`fHkR|d{0Yck?IXs1>V_jz|?;0 zW>Vx7(3Gbj4F3oQ*un0~wGgz{H;|`H0$l6cb(IAyAqM2`sXP9-v~NwwWg(pV8xDxiMSSz6hn045+ z3<<_TK)Kd58?qQ4`xRI4YTv5|KKv)hP4>GXqBr*4s#hQGX%)4IRNEN6cqWI?VQvA%%|hLWopOU zcW1~|PY<1EoDxLishU72DJhXd={@uuiKiFA`lXjMa4`j*t+G+ytvsgxe5?Hz1`s2u zt5Z^@fYp5lbP1bg4&*5xfCTjOANF#X_*V#UIYg%60!%2yjG4beRjrly^;VN+3OI9IyJKh$!oa+4f`qKEkp*Q3i?rKxn|cr&3nEwu ze;kNq2HB4jD1WI=1oQ3P2nsR5dPP3Ed^cDJKv@?$yl@$yd#VCBAz1+AOoyg#Rx?xD z-SLA=PF%}vKS4_>1}%xNYPHZ5z>>4umU6vjNp1qA?4|bqPwoktiAZuiXK3Y+Y^Wup zNgfGhgjZs8sSHa2+m|W%)n+}&AHHy7EFwaKi)=E2Hmx-0k5M}@L&3h_fG`HW|KO12z#96@c#HdwjS_(sWNQ4o zar093pZZPvaOjUKnY3E5WCUw8`f)_OCo#TTwkx%xlVr$hN^OL5nW4Ye>M|(?ImXpa)5Cc)ETl_|#Yf0x zr8q$2_J??GOT5N8PFYaw_}dpvp735_cQ-&O_6nM?vMJkW`l+RB+DNj5kW1Vm{bpw|eCf z9}l+G#1#s>Zu_u!`(YPAL~mr%N4-&h0^09KUv6!hqQZhj0Hn^J;p;yci00raf4R|^ za95IFu}&$T=b!Em1;c8(7$M=-P_((f*$%b2TZ`8pEcE4@+ zwMf{kumjHpu=cuXs3z13nu3*LC9KV zdC0O{GVC1uCS;@6ankc4$|qipm*-4(R@LJRe{bh@bluyW_R|g#=bagExw=A*$^1sk zb3=5Ul$(xy@@jF#+1I&qEk`S4kS4^Og6e{7%yLPtT=Yq5^Lmd}q)X_mkh)>aSi z_X$mY#hF|_hg2QK(ig;@nr}D5OOWA0pqlzagSD)GnP3)KfeXbJ2zu6x8-j|()t@@0 z`Tb(gO^=exh*)n@`Q17PE303Aw3Z02Z$@L49OR>QuCAlUODc%`zzESA6JH{2!m|x7 zX?zaYMS+JS^vT^w`9Oz%MUIzLHaJcZ5I*}SSFCaaeRG7uY`d;utcW}7$3ih z(}!N*(NE7(jN2q(id-rE^;eR(a74N>**zRoWvCZGDC`m7H4^v(Y~>um?LwSU1eK#Y z->7}@2P8%fSed~1_+!?x4J;X;f;bvHLjw!}X`rXfgin=%G;ZcsPo8ijCk~m}BLcY5 zqvo*tWZo`us+`gQ!DN42W=r?%L6^D8=}16|-R37d;Nf2;83A`m4IvVTyTna9NkWOU zTgVH^$8wlCDYwS3-;Q1WzUV7(I~-Ko-(`JvX5^+d5qZmMuMclZ*=8Fj4FNtnJpJZ< zHo4(`FCgm$qjCNScKmlp=yi~YJ-7E22N?u=gRWQ2p93p1q3R&1zxeF~pn3@uOLJ~% z35pGw8}qX&4+9hxYzj-{MnEjUfzC~e=wAZFJ7j_s#+IK)WWA>u)EVQJ?<=keu8)-^ zJg|*qW_(7}O8y_y!`#AWTF?G7J-nFC&y#j&)^HY9WlOdFWY@4L?_Ex;ViOqBD;$yfDno`becg3()*Bd-=^*vpDb)k_3a<>LK6jC45*HV_s4Z~rw!sCF zwX3|}zNn!xX^aXrGI8L1P2ziHuF}LFtc-S-%xQEI8gOBcZJ7v*a_9?VJ?ZT(=u@X( z)K-@rwNrgEN+dxt&cbZ$6Mryk+4{VTtm*q6m?is##>1O1WQ*fsl0gg3)Eh!Q6A6iT z*~39$$}FJ$renjvQWx{91p`3`%0QFtufjP|M!9jy-;`;%)N+uJ=_jy2{Ovs)F!z+~ zb-Y;2F-m+4akZNut~Q40BHK)oJ#2+~?7FKnSA=W~Bf25qvc|SSS7MNv#F{pCG3yb` z7P*463mJxSIlu^Mf;`rSU$tR*V-$$&G|VSR+7Y{2_oyoVIDM8V&NG#)z-gsOfDkX7 zx#8D|8Ss5)Z_b^?*lz#~DuUL+!o~H-zA>}daXS7agzcHw*JUs2^%94OCJFx;iKYP+ zJq}XtfsO?JD8Ufg9R5oq9XpQf7#_ae#L#F#r92iE8fyYwpWYBYUXu6Jw`rO@l0U>w zH@<)&c52!~e+6B4?ggz}Cy9O4&)MSPHOa{OX)raks4OS7}^^%JW0f&%_D=dB2F$-L)OPtnRAUgY@U%#cH-yZ zG5GYn#*pZ!Hc;)~dD+WEmEY;wm|dh|Ym-s3qeG+X=G?y-z;_S9vX}Y=ZQ9Poh(|$9;uok!V~c zaLE58;0*8}c*;%8X3cuMps`3p*4xKH)%1jUMBxWt{E)eTscvv=G(3r&C_;_lhT_pZ zoRx9s7ZZ$xx@ag~#CYCuI?M!)sv=T-GnmqMdxsv=t3MNmV;8j*j~Scah+y0=57 zlRj{A^Jhs-?-9e+N%Gk3PWzm>s$qi=YfuLQVzVg*iWT0TMqXe*D1qqcyYo(z(rL1^ zYRZaQ0L<3ubKhHdyWN?We8X~QJ{&5iiXDepbCs$qf;rhEO;;7(GV)SZ^nkI(yc16n z?27N1qPs>{0vvX(SWLE(8WKQ6j@t5z-O~gisi#!BJi7xY0C@K#c|~+ex&J1?f8xav z91TWLAO%)UlD|5GWN+bUM8(CZ{V_FJx3k`2HL6(ULh9ci=TDk0gC}INs~*skUC4EJ z;wJAc7JL*zc7X&<{+W4OgiJf0+g-XSMV^E{?qO_PtYB;fcfX($N>GsOb(^wZ?L+V2 z1J${>Q-=q2Ko1+K|@^ z@Ua`yTz$q@U!XEi@$b2|$kn|Blv3=hId0x~JG|%hF2hp_HX5>;dXb8ItEs(|oeej2 zJ&G-+IFZn$)EP%!B z%J3*1SZmVOy2J-Ix$EhdUoB1gbwELeH5j^pQ+}-jF|%^~UBZA{-9m&YHq*MomGoxFhMPZ8*_`JS^Hsr=A=aF)Pc2mk z&4PlZ9ruAxNo2&?hPI6uy*Y<@M>&p7eJCqAvOeCApdfk~@$h!EJ{eEtH{FVlcfDmZ z&@P!`7?LHzl^2@=UDM@2uo!y`4k7}bV&NaLMEl&z3`u(kwhamlS5=*~Ba`8~MG#70Io`5E$x0{%yjzSAthXSBH!0a4*0#!K ztGHRVRHm&|tYA2JY8I?^nX)r%ZB&|)N}Df`yu^^c&e1FVT*)Bh%Vi|NexEI*JT-5U zfqhHlTkpYYnEIO(Ifn`YCjv<|p_dVEdbD4jGK!`j6CtVM^lB&pqX)f}N+>b6bZFj| zFy`0V=J|DP|Iu*M0il2N(e_XfsgDg0jbXh0yUMMwP(|1$e^Gb-ro%g4uU-bBGd7^- zMGd~x!#elWU)VUuTH-EhWYI!xO%?Rw&W9rAV7R=Eu~FodRAQDoV^#vo`Q$#;5V#K!xgvA5)ap^qxNUXmrvcS z7OHrfu+ej{nh1KByZmdWITs{ChcN9T~y}DqpP_TkqWl7i}44n(@<@VR}Ai6&SxVa>%Lad8z++i{dRRaXWN-ONG_4$0^O=R^%Nn z=0%CqZ_;2jqylP<=tIt=WcP}FQglGs8d)M5baG`L(7X|lV9Qon{~frrWQNit-{3@_ zkaC9j`fU)SS z{(JxXq8rsd?uA74J{7sa+BiRvdb?~!$CT3Z#kVov{Wd-Y{sN&`f$kaw%2hHMm(#xa zt+!iR^jhicRtCE8&7>#dQUT;zdB`p#YoMwY+)tEVfhbrQ7Q3tW;%q_H8ATqKcJ_YH zJ4lKmzF}Bc-v)R%oQ*;+6U&CM{>S`}>=Ntlb0NsNT-uki*^JF?AP>@!X|tsEyPD|H zT6fzJ=gO~p;pN7G9hG~PA4K%}24~reKVn!7K*zMncxFh@F64a?z)cPMN3tsZcHX+Pa`0LVXGr`L*W!G=Z|pcFwA&2SXVvVJa++uS zFhdk;m-#DTf?So9H}I6c6N^N2-+e9DCt1mqSp6A(1F0;XkE5?X)%TFI7J3lYgi@9Q zDwCN$pM%vahT5x`{BFrDQ=7Eh-py)@k2f}y--pdjA51*|PBTtv(D zZ$OI97$)$TwtK&+a~$fOO&q4|(^z`Mqmk#5tXWdWJN{6Cs^ItJw?5|8xxf=jwLL8; z($XVGX5RU@l-q7)<6KA~w^c-Pg&zk`Jt{WFw2S&}t!7tJ*GX|l^N?AT=IBgrNVm?4 z;=#UM$`6oi`EG4j^7tU15Pg4@ZAJc(X_%TeZln781iZ=F-{Nr`e8w7e5m?2iIw80J z!0|+;`4gH}xCM6c0N(HKXVHE&ZWM+U6GUGSN7?%e%ciS>%9 ze7+^YcYEMTR~ZPmI63<-wmKE<3RF42Cq>sL9Ww8HBf78}WrVd?uM65D*{&sBs42Uj zZVi7fqfveo(mpty+N>h3wV~zVrBk!C<;7B+`*8CPf+uPJ8%~o~LdpM!&)gUz*$@dj zt6fK`L$IEKn2ES)O&Zh|9uQxmGPCmIa77QEv!2(@Ie%@?|JC?1e(8L+%6qdj`1f=s zZ#H$_EDHwnnNvQaoO*p6oiy}P+d)T=Wg*nZvm5i#t?&AUYo;mA^V;3FIvP!1v$H5? zO7=}G*OV260im2OBU6a_>I@+)0h93PX^a; z$^Prrr`DkV#!WSE&J8J%57pv7qgo<_&*yq8&P2_`bZs;w>3eF%r?V!#=D6XdF%^<@ zTX`*BnBFC}wUKq)I^$c<`Z^gg`q$EU^i=yu(wuZ_1GZTk9eCMJB*25p0TF_Gds4;p z?%<&Hal5o&-8kn4VGYr1rskMCw4+QvGJM~$r}iJ)OG#ciX=;6fn`)@eUw>bv4hEN( z@7wYMb#TuaZAFKm0C}0I5?&f>6@^ffbKJJoDSLcfYr#&^kRIKcjrr~AC*NWl%JC3+ z;nxdga0MFz*?+1~L|0(mNewEFVmv%KkNXklTA%cS3X}gsi!@KdZw~>O&xXBS1vxQ6 zZsleHxATFyaZa_kWrklK{e|C{v>NvFvf>6^oBjIyo7NKiEIEQ4?Erv+w_j0OQl;4K z6BHzRy0rM8hd0ob`K~}YSN*Z%SdV7GL%Y!+J;y#p;keiFjnH#)pT6J{>f|#QYAXDY z=4R7K_w36jzgLfq9_^PdJoftT7YUyawJ(ew3g38?(n^9jN90K(HXw>1$fGmUr?14q zP+FoKp}#2QGkqV434bi#2-evRZ8rU-cqwP=p_oZlLAb*}g>zob`6{P{HtaM$WGfZEOQ4m=a=(t)vkJ#D*wSC_mfC|iNUg{uj zuc7v}*6O_J;C1F6VN;*=?Wv9f;y&gKkcxepeuIAEMv{fBfL#k+H<~${YKVH~;*l@I zWF0&KH+3SWF&`2S4^A8^Y^;nH_pXsGUe@e$t^3h4z?=u57#j`}>pA5ZW6AFvnLm&4 z7?giyu+&~W+`xicA~=F^b-m^lY3?mm;(fBFa!l2EmitE9p{^;r=bcxYPwi1wtBM2i zu0E;fx9S&1NYI2$2J;Vct}xi9!f{jlwc1OPwz#rRM7YEQnJf1$52TW90VdyAK!kYq zL`2kQ@R&!Tv-jwv`@`+enI=xbC*Wd7WMw)4%Q%`6_Al%FB4RPwB3*taZu-{ zu4|l6vK_LQ?+;lT%Q+q*(Az+;3JbN#hLv4gjB) z%3Gio>Xh@80DV-4TL=7_1TIzyFXULH9lmxf+U;(D9uvrLFNmEYVbz%JM^{$%!7UbaN4!Z=`2aG-Q@1UJJc=B=Yu^B2J?3B>j z*J%+Ol%9l05ShLfTb?FAnf+J5^P0I5{_WgpZz3Br;0fyD>=G5#A3c=HV|}f-ppazq zwL9gdb6^Jdz4JygcuW(^8FiBM4N7};avqfg{s`A1`_Q6x}RCnCQi7n3)U8bnV zpq{36BN0L_B4Y@=>CtZtBAsq(8EHaGf6*$H$oymrTqx!@M!U97k zxJioa!Cl1aRnM?}k}Cz9*r`3!)GXWZOQ_uK`s)T6$IQA+*&6C#PH1*qco06XLQZMM zEun<04e`H4jyQwnclRLL0mXTH2(7~mLF0F#ck#PJ?Evf9LnyNLx=%P_{T$cZsyKv` zCrrbLKfsvZ3->7qBR+Nn0=ntJ*II;PHv`s=-lO>nd3h)$!94E6=QqUDAWo-K`MWwL z%3Y}FyKA&u^wVdH7ZYoaX{sBlPhzUxV z7`Kdx{m1n8K|qb7m~5oQD*Z+f7)}XGtSHULx@_&@3WE{G z>mfBPWz?&jgXitz?9|_LVOIj_nqk#1nt4iVs6l^(!QCj>!@4_sw%WV=8zBi1M=~p_ zgX=lA2O1sfuT;3;%^k1J7Zl)BO6Kxk`Bf=n1J0n9s`Li_!vUCQ7f9}0Wh2fkRxffE z5^1Onba5a?KW#4AlQmli0qf3@a_`Mndg2yp4xl&8_JE%B;QoY5BCX%B`~&!vk2cO3 zC4=F;*Yekk)fpyB!A)%>=5@`}m>ACCQ`Pps&DDWl^5uiMLb*ivI|i~L<9FgVI=C@C z1F496f;aVv8|&ar<+CQwZ5SpS!M37x*XO;Nz_v~OtuJGsJ-PFAb+5||1$}W!*Vjft zQ${jh7x8>eFwetpDC}EpvJkgasH6N7s&nt;TH)0DYHn{WIZoN&rsgpFt?mrj(3{2B z=gQ2;NjO z!(ZhjR@c#HkdJ{^rtaLZvT)mHR}ux=u3Lj`H-8Lnl&CYr!e5$$|Ej+Z4{GBKglMzd z(hZMH!OLdcI!8q;!w%Gk3D0L6oCnT-|bmnLg@N;j$7I`XX|N4A2 zXoW9x%U&Z(@ECC6-O?0k;{W=5CHS{oID5SWzYnhA^&3We+JC<1jWBKY+{V~aPchyd z>~~4oVANlSA@LOK)N{s>y<+?>_${&UM9J>I4&(3&bl8{ve?9EuJ1qiVHU2sXT4LiO b{BPK+w4d*K4(vgzgu(Q*jFFEop(6edEAm{C literal 0 HcmV?d00001 diff --git a/Book/php7/zend_engine/zend_opcache.rst b/Book/php7/zend_engine/zend_opcache.rst index f9cb24c5..c65c3d7b 100644 --- a/Book/php7/zend_engine/zend_opcache.rst +++ b/Book/php7/zend_engine/zend_opcache.rst @@ -1,49 +1,66 @@ -Zend OPCache +Zend Opcache ============ While PHP is an interpreter, it does not interpret PHP source code directly. Instead, the source code is first parsed -into an abstract syntax tree (AST), which is then compiled to OPcodes (and used to generate classes and other data +into an abstract syntax tree (AST), which is then compiled to opcodes (and used to generate classes and other data structures), which are then passed to the Zend virtual machine (Zend VM) to actually be interpreted. As you can see, there are quite a few steps involved to run a single PHP file. Without an additional extension, PHP will perform all of these steps for every single loaded file in every single request. Since usually PHP files don't -change between requests both the parsing and compilation steps will always yield the same result. OPcache is an -extension that caches the OPcodes and data structures like classes between requests to improve performance. The AST is +change between requests both the parsing and compilation steps will always yield the same result. Opcache is an +extension that caches the opcodes and data structures like classes between requests to improve performance. The AST is not used after the compilation step (with the exception of constant expressions) and thus does not need to be cached. Normally, all the memory allocated during the processing of a request gets freed after the request has been processed. -So to store the compiled OPcodes for future requests OPcache puts them in a shared memory segment (shm) that can not -only be accessed by the same process after the request has finished but also by other processes handling other -requests. This means that if you are using php-fpm with many children they will only store the OPcodes for each PHP -file once in shm. +So to store the compiled opcodes for future requests opcache puts them in a shared memory segment (SHM) that can not +only be accessed by the same process after the request has finished but also by other processes handling other requests. +This means that if you are using php-fpm with many children they will only store the opcodes for each PHP file once in +SHM. -This comes with a significant restriction: The OPcodes and other data structures in shm must not be mutated by any -child request (at least not unless these changes should also affect the other processes). If any changes to shm are +This comes with a significant restriction: The opcodes and other data structures in SHM must not be mutated by any +child request (at least not unless these changes should also affect the other processes). If any changes to SHM are made there is a locking mechanism to avoid data races. -The classes and OParrays (arrays of OPcodes, i.e. functions) in shm are mostly immutable. There are currently three -exceptions: +The classes and OParrays (arrays of opcodes, i.e. functions) in SHM are mostly immutable. There are currently the +following exceptions: * ``zend_persistent_script.dynamic_members`` stores information about the state of the cached script * ``zend_class_entry.inheritance_cache`` is extended if a new combination of parent class and interfaces is encountered -* ``zend_op_array.handler`` can be replaced at runtime by the tracing JIT to start executing JITted code +* ``zend_op.handler`` can be replaced at runtime by the tracing JIT to start executing JITted code +* ``zend_string.gc.refcount`` for persistent strings can be replaced with map ptr offset to cache class lookups by name map_ptr ------- -As mentioned in the introduction, shm is shared for all child processes. Thus, no changes can be made to the data -structures living in shm unless 1. the changes are supposed to be reflected in all child processes and 2. the shm +As mentioned in the introduction, SHM is shared for all child processes. Thus, no changes can be made to the data +structures living in SHM unless 1. the changes are supposed to be reflected in all child processes and 2. the SHM segment has been locked before any changes are written to avoid data races. -Sometimes the data structures contain a field that should be unique per process or request. The ``map_ptr`` mechanism +Sometimes the data structures contain a field that should be different per process or request. The ``map_ptr`` mechanism can be used to achieve this. In short, instead of storing a pointer to per-process data directly (as that would affect -all processes), a unique offset is assigned during compilation. During runtime, local memory is allocated to hold -enough space for all the entries referenced in these offsets. Each offset then corresponds to an item in the local -memory segment without needing to know the per-process location of the exact element. +all processes but each having different addresses for this process specific data), a unique offset is assigned during +compilation. During runtime, local memory is allocated to hold enough space for all the entries referenced in these +offsets. Each offset then corresponds to an item in the local memory segment without needing to know the per-process +location of the exact element. -Here is an example from php-src. Classes can contain data that needs to be evaluated at runtime, called -``mutable_data``. This ``mutable_data`` contains fields like ``default_properties_table``, ``constants_table`` and more. -``ZEND_MAP_PTR_DEF`` can be used to declare a field that holds the offset into local memory. +The map ptr can also store real pointers. This can be useful when the data structure doesn't live in SHM and thus +doesn't need to store the value in a separate map. The fact that pointers are aligned by their size is used to +differentiate between offsets and real pointers. The offsets start at 1, 9, 17, etc (assuming a 64-bit pointer size). +However, pointers (unless padding was removed) would not get aligned this way. Instead, they would be stored in +addresses that end with 0x0 or 0x8 given that they are 8-byte aligned. The macro ``ZEND_MAP_PTR_IS_OFFSET`` will be used +internally to check if the ``0b1`` byte is set. If it is, the value stored is an offset. Otherwise it's a direct +pointer. + +Here is an example from php-src. + +.. image:: ./images/map_ptr.png + :align: center + +Classes can contain data that needs to be evaluated at runtime, called ``mutable_data``. This ``mutable_data`` contains +fields like ``default_properties_table``, ``constants_table`` and more. ``ZEND_MAP_PTR_DEF`` can be used to declare a +field that holds the offset into local memory or direct pointer. + +:: struct _zend_class_entry { // ... @@ -51,35 +68,37 @@ Here is an example from php-src. Classes can contain data that needs to be evalu // ... }; -``ZEND_MAP_PTR_INIT`` assigns a value directly to the underlying offset field. This must only be done when the -structure is not yet in shm. +``ZEND_MAP_PTR_INIT`` assigns a value directly to the underlying offset field. This must only be done when the structure +is not yet in SHM. In practice, this marco should usually only be used to initialize the map ptr to ``NULL``. + +:: ZEND_MAP_PTR_INIT(ce->mutable_data, NULL); ``ZEND_MAP_PTR_NEW`` can be used to generate a new offset and store it in the given field. This must only be done when -the structure is not yet in shm. +the structure is not yet in SHM. This step can be skipped if the data structure is not intended to be stored in SHM and +thus doesn't need any pointer indirection. + +:: ZEND_MAP_PTR_NEW(ce->mutable_data); -Once an offset has been assigned to the map ptr ``ZEND_MAP_PTR_SET_IMM`` can be used to assign the value in local memory -with the given offset stored in the map ptr. +To assign a pointer to the map ptr you can use the ``ZEND_MAP_PTR_SET`` macro. It will automatically store it in the +local map (through ``ZEND_MAP_PTR_SET_IMM``) or in the map ptr itself (through ``ZEND_MAP_PTR_INIT``) depending on +whether it contains an offset or a real pointer (or ``NULL``). ``ZEND_MAP_PTR_SET_IMM`` should usually not be called +directly as it assumes the underlying value is an offset, which is not a always a safe assumption. - ZEND_MAP_PTR_SET_IMM(class_type->mutable_data, mutable_data); +:: -And to retrieve the value from local memory ``ZEND_MAP_PTR_GET_IMM`` can be used. + ZEND_MAP_PTR_SET(ce->mutable_data, mutable_data); - zend_class_mutable_data *mutable_data = ZEND_MAP_PTR_GET_IMM(class_type->mutable_data); +``ZEND_MAP_PTR_GET`` can be used to retrieve the pointer regardless of whether it's stored as an indirect of direct +pointer. Once again, there's ``ZEND_MAP_PTR_GET_IMM`` which usually shouldn't be called directly. -To summarize, with this mechanism, the relative offset ``mutable_data`` is the same for each process. The local map -itself lives at a different address in each process. The associated item will be retrieved by adding the given offset -to the base of the map. +:: -The map ptr can also store real pointers. This can be useful when the data structure doesn't live in shm and thus -doesn't need to store the value in a separate map. The fact that pointers are aligned by their size is used to -differenciate between offsets and real pointers. The offset start at 1, 9, 17, etc (assuming a 64-bit pointer size). -However, pointers (unless padding was removed) would not get aligned this way. Instead, they would be stored in -addresses that end with 0x0 or 0x8 given that they are 8-byte aligned. + zend_mutable_data *mutable_data = ZEND_MAP_PTR_GET(ce->mutable_data); -To store a real pointer you can use the ``ZEND_MAP_PTR_INIT`` macro. To retrieve a pointer regardless of whether a real -pointer or an offset is stored use the ``ZEND_MAP_PTR_GET`` macro. There's also the ``ZEND_MAP_PTR_SET`` macro that will -call ``ZEND_MAP_PTR_SET_IMM`` or ``ZEND_MAP_PTR_INIT`` depending on whether an offset is already stored in the map ptr. +To summarize, with the map ptr mechanism the relative offset to ``mutable_data`` is the same for each process. The local +map itself lives at a different address in each process. The associated item will be retrieved by adding the given offset +to the base of the local map.