From 17d0a2cd3cd5bccc6f59bd4dec8717059c0c113d Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Thu, 30 Sep 2010 20:41:04 -0300 Subject: [PATCH 1/1] =?utf8?q?Terminar=20secci=C3=B3n=20de=20mejoras=20pro?= =?utf8?q?puestas?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- source/dgc.rst | 2 + source/glosario.rst | 17 + source/referencias.rst | 15 + source/sol-mark-rec-dil.pdf | Bin 0 -> 49393 bytes source/solucion.rst | 1200 ++++++++++++++++++++++++++++++++++- 5 files changed, 1233 insertions(+), 1 deletion(-) create mode 100644 source/sol-mark-rec-dil.pdf diff --git a/source/dgc.rst b/source/dgc.rst index faf4bd6..14d78aa 100644 --- a/source/dgc.rst +++ b/source/dgc.rst @@ -1514,6 +1514,8 @@ participación y observación del grupo de noticias, de donde se obtuvieron los principales problemas percibidos por la comunidad que utiliza el lenguaje. +.. _dgc_bad_code: + Complejidad del código y documentación ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ El análisis del código fue muy complicado debido a la falta de documentación diff --git a/source/glosario.rst b/source/glosario.rst index 9dbfe53..5f2383f 100644 --- a/source/glosario.rst +++ b/source/glosario.rst @@ -509,6 +509,23 @@ Glosario banco de pruebas utilizado para medir y comparar el rendimiento de un programa, algoritmo o proceso en general. + BNF + Notación de Backus-Naur, una meta-sintaxis usada para expresar + gramáticas libres de contexto. + + CSV + Formato simple para almacenar datos en forma de tabla, separados por + comas (de ahí el nombre, en inglés *Comma separated values**) en un + archivo de texto. Cada línea del archivo es interpretada como una fila + de datos relacionados, y cada valor de separado por comas como una + columna. + + POSIX + Familia de estándares de llamadas al sistema operativo definidos por la + IEEE y especificados formalmente en IEEE 1003. El nombre proviene del + acrónimo de *Portable Operating System Interface*, agregando la X final + como representación de *UNIX*, como seña de identidad de la API. + .. include:: links.rst diff --git a/source/referencias.rst b/source/referencias.rst index b4346e0..f7a48db 100644 --- a/source/referencias.rst +++ b/source/referencias.rst @@ -66,6 +66,12 @@ __ http://www-plan.cs.colorado.edu/diwan/cbgc.pdf Dynamic Data Structures on Distributed Memory Machines. Transactions on Programming Languages and Systems, volúmen 17, número 2. Marzo 1995. +.. [CMK01] B. Cahoon and K. S. McKinley. `Data Flow Analysis for Software + Prefetching Linked Data Structures in Java`__. International Conference on + Parallel Architectures and Compilation Techniques (PACT). Barcelona, + España. Septiembre 2001. +__ ftp://ftp.cs.umass.edu/pub/osl/papers/pact01-prefetch.ps.gz + .. [BH86] J. Barnes and P. Hut. A hierarchical o(N log N) force-calculation algorithm. Nature Volumen 324, páginas 446-449. Diciembre 1986. @@ -296,6 +302,15 @@ __ http://www.digitalmars.com/d/archives/digitalmars/D/announce/ANN_WeakObjectRe __ http://www.digitalmars.com/d/archives/digitalmars/D/announce/Blaze_2.0_15246.html +.. Bugzilla +.. ~~~~~~~~ + +.. [DBZ3463] David Simcha, et ál. `Integrate Precise Heap Scanning Into the + GC`__. The D Programming Language Issue Tracking System. Issue 3463. + Noviembre 2009. +__ http://d.puremagic.com/issues/show_bug.cgi?id=3463 + + .. include:: links.rst .. vim: set ts=3 sts=3 sw=3 et tw=78 spelllang=en : diff --git a/source/sol-mark-rec-dil.pdf b/source/sol-mark-rec-dil.pdf new file mode 100644 index 0000000000000000000000000000000000000000..bde07b9330411defceae1e7fc254a2c387d65004 GIT binary patch literal 49393 zcmYhiWmH^CurP|dW{|<%f(`CYAi>>baCi5?HE4nahaiDK2=4Cg5^QjH_m6Y$U2nZV zJ-w>CYgg5-UD`vZA}zzp!N!e7H*lO#fW|`!qI5E~MH3cgSFmuja<``BeTS&A%h=ev zTewoP%h;Q^TS!}&Ihk9CilVu>yIPnypm}Glw8_V_2~5-RPH3*Uz>~QqpZfn6H~Rw5 zN=fA*6@@oB4P9?PCiU}`uKmbWP6?|S-Hll@kv$$cW$pCZV7wsve|0ZAYD-fN@jnm}OREYZQlKo+m@jZ=`? zkLIUiilac*pQghMXQJkLFAN&lw_ft|t+z!AXV^8j4wf%TT}sDj4b zn%z%=o=-Yn;FwN4zJZqqJnG1Mm!E7XE>{*W>)rR7%wra~@42Au=8&!ahCg*XB~BUu7t#Gglv-IVBONc!DXRseVZ)N)oHJ$P5t zn!Rr`!@@DK=>rA*`aWmT5%=FwIc5*zs-$Z6S5uPL;dPoIJM+pXGjv{(cl_4USPUDD zMVM00;q-cG_`l3JdW~&1edY(QYynx}#?*_y=@D_$6>pX=COmt@`?n)l&U>u(FAt{0l`tw>(H4op$P-Vso5 z{vU$kST-ChJF%F-po-X_`l+Bawg^d_ikIKi&qZvn2mgZr?LP!D|A(OApny4S>0iRf zP2;1@T!;TCs9cjV6Du1G`V|^fJropq8ZhVeivRLdd*TU7wXIV62pny+px^5z}9R>by5&`FoGo zPs=hzi%s{zFM8T}#7&((&_K5I@&`YgSWsq5!0-9Zj0yi;@`(Y2oj`|y75%rtXfBfe zn1wL9$#U+q8y7Nv1^2F*hlkgTIR9O)+^%%bt-bY=#w(ee1!Uak(D!K`B&YD=|Ukl_4WO5LA8(X&@ovS7aCq{HD&ARDs zpn@Hh;`nkp`N7^@4>PWANS~U|I3~%+0A4||eiuomvG2$EuP!m!IDfYo|3zZZ%r3mT z=Md00o0F)4C0+T*@hOe_#-%aBQ6&?(u!nz@Kg@~m5|PhcK{wkY-$MHzg-^LM#)u}v zq;%$f9(tAfzXQLbBi2j~Sn-AZj%UxaE0>;f!6F*N$C-n|XCP(u+fk7E;bVfBgo*Wj zmMrnV>dJ2bxT`3~OTBRl(Bu4GgJ)LvDVN1>Feztlb=mO|Py6)^#q|Y5DSSIeK9zu$ zFx`*L+bSiio$5Gy!l}X=J6FEcGo(nAjOFI+8nkrz!RNe{=th+nxAqBMX8rMbUg_2) z!2NiAjO$?!@lDpyAn&E#BEJ|-Bm$@U6uX6nq$-<&<8gX#L8~B0a`-CH{EU1_<>bs` za%qr`Sys+Hu2%5tsX4r|&yG<4|Dsc^KTB$-*!VPYbvNI1lQ0ib@|e+%vsM;Pq!7S9 zy8%e9KRnON-JYki1+Swbx1M$^^<<(&t4!TUL{s4u24rQv;99zgh?(C1_t5IlYB#|M z>a6y+RVMCvm%6L2JluiLtX|vLvqYn4=gR^51dk=9rGnl4qQfl?I#rY2LIydFe6f@&(>7synDXh$6&p!XN`bBWR{XBsjs*SQ7PfUmOa?YulmQiku5L+C z-(CmR^Z%+rraST_Q+iickllx(XcE0yE{goUn{x2asd65yl*%HWRMqPIBJ zn@`(4v7RAqQ@AA_{=4<=r5qfFC>EgpiXZ<1=wAEp{av3AZcrEc@_ybvODd>!$um^Y zrA&GaqC=`MqV*SWCzOsn`(KvUWO12^pHL18Uqno_{w1m#5x_}!#sM^X-N?V+OaDDo zA%3Uv`@|my@4Nm+3U?>8;K98&t-GI?#JR;dlOnmsh%^=T@^kn}O1ZcHN8~>Ig4^PN zTvU)thSOAV-U}TS68-*ZyV4u*i9rEpZNZcCCobANrXc|@NFcYAfJpM1)MjT5hs}g7 z@hd&)^%~%*GWL;5`GqYZ7N&!!lKBHhPTC#)y!3sJ^MSr#4SQlD$|z@`&z89S_c=ku zV%K4StcImP(9V}BV336JL4ktM^OIARc$AWU4vywc;9~cO>>tUXL5+G!B}$*dA>2OQ zf#1;R$h}g=?CvRZ^;)KHp|@v=JHL;oKOKG>i9yu!*!8E5cMSpQ&I4p$Zm#@v<+GGutmSEkae- zoshxPMrno^C;O<+{yl#AVT9S=zGP?CQUsYz@_z2=rQIJ%FS>BAZ~xarXit9f#4#}~ z_HeSW@co`l*cvsZM1j`b+otMoR>eI!@OW0))KW|$GxO8{2@cKb7;a<;?v(yp%*8XC z6KR>siwy1EmE8y~jUQdA*+{&7VzXHnZ)sZ^gL%+?GP;UrC5!E>MH5t`;o~rv{ z_tpKC%H7U-y}dV1;*(gdq7kT{znpD~^$v@=#r3Gt)K!k`RT9fM* zX1gc`HVZm9vnvN(*GuB4kC9|)n(MVxtJO`sRw?E*d%5;*3cNaUgctRgD#ajym_MDQ zKOXBRg^&&vG(fKAXSXym_1oL1B!*1Dj26Gd)r7gIB&IM}^q6m-)qrcoM0($7e`j95 z_`=Pj7Fu-Yo-?H)_0uq~W0%&&q69j&Q+8*m9czeATq$0bCsvz^lL)vwQ@Iq9|vwS}G&6~YYh|Jv9@B#uj0 zyY0BKWfK0iIp%zFnU!3JX}K))97&4VxL0lY)VsO;pxTmV-{se*imu}oN?wO5q>Ua& zRtI^?a+&RC*eca@dm)oOF`9I_D*dY3uNs@ts6|@mcgXR9ds{tI11THFM))<%9cdtr zqE1KYJW|+J0(LP|=|G0vb2>1eA*+^?J|pR>fTIV9n1AVlA7_259ENxH%%katAD?sYbBLq>wOHYiJ8yqWblC$`Fh%l^J4+7} z;mC&|XWcR;@RGyer%|_Gz?5bXdRAS*EXENcsBgi_de_>z4#R_+`;Udt`CS#n?2yr6 zTK7;0600UW!EuN%^j=K=V2X_jh2P_f1{K_663#uGbQ!U&<~TG<#hn$l@C57VvsE zs^aW;lOpHb@(og)akCIkQ(?-aCoN(PhIjy;aF}r}iuKMVP&{Ld$|2FQT2+)*aIc#Z zju}S%kk3Aoz)-8h4~#gb{!ygmZ*`DH!yL#-E#mUFt!PP=;)hO!DW?6t#a-m>-ZV6qtmVC{u0 z_6A1j89H-I&e47ocqO>N{VqOX-$I~-;i5|h@OSYu+)Ju$BeXg7(`j(3D;#qaCNG8- z+UO{qs)cdaRVTQ#YPIj1p({ar^vjjf)D&yM8tUyP0OB@pYxp4}VuJ+@jxN;S;ZTIz zuidD>3G0*EQrah`mec6*;l~)2)<>3r=?P|V5%pXvcMddRjzQWibJT4%fq6Pc8`51o`ZakFtquaKv;y_3sVGs zN;C?7^@W~?&ZY0D;<=y6L18v2Hb87(!Qs^7kuVrCk+2O5@p@$WfaU8FcLmegXN8-~ zq5vfo0c4|!eDdarfYE(y z!6yg*{2&ZfR2YV@`ZONfi2-OC*{AVbSIcibRz9$C5vV=Pk8;_09iYAwRF`A9sK=GJ zG2Y`8q&%d>lwOkv9l7OGun%&e^vns!KMp(!buv0KJ$4}7e5rf#VisGze>t&ulynTp z?B0hcy4b(mQSZ-N-7i)W<2@T_S~*MaD<`0ce+md`u#VHnCC@j_`I;};?|6h4%w-m@X6Yh?Y(;!hf9@sLn>E^Ot_jh!zTA|e;`d^NU6=BB@=7=%Xgs;@ucyLC zAK+SpI;Mre5PjxDJnA2%>z}6FECSEM{+1=VvS3Ub9=>>tp#8tc@#N!#m{+f1V^spa z#33%?2pb2uqf>>;0|}Tj2M5s|Ju?8{)%E$wREh&lZ(-&#};kGW; z`cgb!acEnjcU5Ud4OpXwL7LV<#Nbg%0oIux%m9!sNo=k_&GnQQfT=qDKH0S zi=QhDoxwX`jVbH?PK{qev{z}#JS0CAuppb>jcU2kuFjtU-t7CZaizzIp?g;-Ol4O$ zGtyJX&9weA1(`uQ-?a!oO>wPOdTLujVLWgu&E$F83(jQ-y7Ux#2yw16lukhoruZ)M z)R`Pi!6Uk3&eY|Nt^|6(%vCx=L_wMkxtrdo&Wmt)=+$?A=i@EET#yI;aPYLXw zDT8eOf8Vo6dEBPkLBM&FC(+J`EhBPzfyZeHn<_XCdDIau^a2K<=|t7L^SD@+2#{WY zlo1)D0K?(C@LK^!ms?0VBBHS_aF`&i$k*s`s8uSkih*DP0*2#vScpYMkvH@*fzuf^ zO7FZXrFst*xR`1Y0G(9TXaMi}e+40^g90MjCUE=>x!s7QN@W}h=di&8O#Ypsy7v&_ zN8xZ>VJ{EkUPg49Mxub+IB_gUNfxbW!w?F+=QEq~pzqlAjk8E@+Ala-aIvT>90QRR zqH$DAEBN-RkNY%z(L#pdFZ#fJflOYBGKYW?d5{IKzkL__ZbfR2zvKbE9;lgVy^83o z(O}-~$2RW9$}-asLLKxj^kb56LLGK{WR<_=<}Pf8YP7{_x`x`s{|93!LV=wu4g6w7DEYX%K&Dq*`$eJTIJX&fYJdEWj= zyb(f!c?^a`-RZVyZsVAj$ecQ)KX--WpY9hB*YFy_2Q_@foZu845`(ivy(<$&xzLjK z@<@sPZa#C0JAyJbDd9ntF|;p4u#dOc2oIFT$bhgc%M*22&Ei3FvJWM{i4LQ98(-z6 zvn`Xen-8yF(*Q8-Z@*ZDv8^^#o2UE*##6H2nca_YhVchPM5%5>Iy&md7WbC~)K`{N z#@BWoEB}s11TJc`@1&sMIKFD4U%*v7nDYPW@4PKrAyuD>owRae7$u>K#8hRE%kyM=Z9#4Zzi_+84CNjqg~s>|}AB2v=lLJ)Sl95fa zckXY0jlq*|z%jG|3@Q)5o4XH;Fr%WSf6DiWxqyE~SG<78&UP&`9{|M!vlX7L5tCZm zlJ{+HlBkl3_qYKD;kp5%zWpwr(>HmDRxA3ncw)?ON(ytBLoS=R*EQDC<<6Mq)qc1*( zJKu%xZZ74>laT)T{tfXX5QP-B*}GO%rO93jU+p9bPx$!ICXntsC2V(Hs0k-qiLxhb z!b0Rzq$Mj7wAKaUGNwqdpSs}onYSig(Cu0wCcw&!#MJBN;o#7D6&xGo8*o^4SWMLev82Rav`l?_*RcI6gWg~tXZR+Joa_^yb+E4Y^nqc;8Xny zo}ZEWGhhyf2+P-(PEHbq2T@qDzL?KyMQ$%0#Y%nf*_xyB8Gmv8TreuDdH>{5cbJHG zzZ=gfN_p-tM2lEBCD4zoGw{=!7SYibpIekNYC{3vUmT3?s`kF)>QWBP^rM@GsCc6{ z9pLr=EA9feTLd%cVh9R@ zj+DKJ0wu4RE*l(z-TFoDp)<#LB{@Et)l%bw{<*>XeBZ`HK1aO?@^vHw=1XxSUFdrN zOqpRVMnB_AdHySK2r?2v+@CI93l;*k<0?@4=9?2{$FT<95)RNJXNcV$ruX8tk)?=6 zcfr{wha$G71+(Ny@$qGreS8c?t!qc$QRVoZj&7TA@bCaRd`wDQaNP)sB)-HNBdG!#Saf#FNlcC|@fy~9gB3x+GI|dZR_fM_hg2DM| z5bJ9TO}z^tk1-Bm4~@du8HU43{w~9@t!SF*b;0hyX8MnbzNK>iYc(6HS^r8IH&(Iv zZX#~2&9b4YtSFaZ0Ix^!?%nApO@oOusO<@=h*;_L^)p0jLOJf8C6h0_BDt)bQe*fp zSdV1+do=)+#}IJh)tvf7f>CC{glaZXOrQ#w@zFp@rn?+K0QkiFGbT`W`y+ZoQ^CYd zfF9$c#BCrFxXG*RrXy&%Gq<^1m9t!MLdYPMeXj|u_H+!Ca>?=PoodV4BP(oI@}3YX zF8Xgc*%3KgwCVF(`}^TQU>R!@&q7eO1 z#0oq1F3Ze11AmC)`cQO_1@EqDbjGJ;LP!wamFte9xT$fX35)&C2iZmA&SwC|OEKnH zSn5_e-8ofO`J-j~>Yjgby!qzpA?#5_G8 z?N+iyDSDjbN2ZyB}p*c6t>LD!I<`h{!8?YNfG0*%2iiEj@J$wz~N?Wa81b zx)(w;H%(vFf7(bXqQdc~c_JL%XfA-w`Oo{Fv$Q7zoSNnd#Qz}YflU+9jPFoomt16^ zS{ju>O#hMFPrQ?Lc_!b^Z)q6rlmlg-BSgFL3OLaF-uG8@$KfVU*d;HDYbmiSQ+9$@ z_Y6ifxqc#W#XPOp^qKFo*COCH(@0+Ih+@Z~mnu=Rb#gjZA2x+F4TWS?BEyZN1;qy= zA|rR<-TB}s)PfP-&F>6R8%_w*=@sNCFr=wReelKo#z@%d<-QY$qIhJ<$Smjw z9^?=%a{_Y88J)Jh5a2LLHZpW25DnnWAKee?LP`yv5+JEkCg82AJQPVY>PUVw{T7Z5 zbvqDA3!0F%fak3V$6P59oM_UkVIb+W6|Ja-#SwSOLGI$G`JWt7YP%>6n*sE9AWqH{ zd-K`%Z2C!Kuj#roDFrB@lKD+wyAX*zvy( zL*!A6B2b2vwY`sXf!rx!&DcJqe&ub8WwKoq>aId-Sa$-f&X~#I@ty zR8c{#HTxKA{FWd7&WZngRwoz%lkw*Ah5gXo46w3%Ma+^+8_&Awk~uTXD)nefA=OZ4 z^&6`ISWlrTRTZb*b$&Ar+~82F{s#SHT_ss@v(*%4=T zo%-+FpFVLIc`CV3bedR(xSI!#Ck0GK`2dV?8U0xewB(k*TcB3H4o!MtcE#yIbxsg{ z9ETh)4H-h9Q4&^&4$pKx&3XNxDGFX7qM*ABoK;?Er_&eoeCI#nk?|o#bd&j92GuH; zqzgim`Gq>xsP5puT^?${%-?8+VH~~csh^R9d74+5h@8K=Esd%Pw_euF^ zXB6LPf;gjLK70B?fY~fcBxJ;vxL+Wx0m_*eMAHX43y)S^`y%E6iZh+w?Nt8i92I?z zg!7@JoOD;6D8QO%?OYFj`S~tzfS0zPPmH$1h*!+|rZV&GfTup6X0g7P*F9{n-IU}p zESR}ow9$2G!L7}VBy7x>r(OcuWya;%etnF;OqpIJSAOf6N5Sn`Dg8<(g1H5xi36qHEZ&Rl}AGhs! z+pw%>C+dyd7LC;^^c$Q(k&?%Lcr<^rFF>TpyhP2M-0%Iz(?|uaE{#A%0<-q+);QrI zf!oes?6TudSbKo%pGJ{?DAy)bzvW(GIYwo~u`MbfqkI1ecY(!?nzswU*bo*E%lz=~ zJ&Zzg!0COo4oA$}wUgnEVq92D!VGD$GY{=-k>PcpEoL(UwNB+|Tld&L;s1l!t*_-{ zq7%~M#U3C2K!n4$wiWz^zk=oEVR@w=;=#@`nSHsx~VV{M(#6!{0pn1y?O z!u!0(LI87Xd0qd!ZEt<>RIjPospEvNZF*ubs9uhY8WNX%U-{DiyJUEW<7cR!~Bnpm<-HG2nTW^-Lz^ z$@?wMHt3X5-AIQwu24n7R?*y==`D-#cQL*~-*sB8tKq$W1eL2C6gDr#AO7LG!M900m zqZswX^X@CP(q|~V3V1ZJd46^O1}QbzgiFj`?DwJd_`cNCLi}`c9>*%{r>eT4nPw&U zNX)}2gK;cY7*CCvCZNl?LqIcepeM)Xxds;oV+uoYhROKJ5Sd>4&03jrBt8;Vx81g_ z@aSw*of^maFneN&cf@4+aeib1z9^6!}g2j-lkoZ9*mr@ zqg>k4oUp_0Y4IvJ|CFV9U3u-MoGkfgJZV$DFL;>hd?Z)(v#Nvtp%2i1^KAcV9`>Z) zBuFCeX|CsWvv>D4js5rh{n>0cE)gW8bl0=veZB&pAkj7V<7pg-CoOkoWTqm0s)luP z?3O_aJ@(M;Hsb_SWmD@>=3KBzEqx@hKp9>lUU#Bm+_Ux}!CuCePK>iSM7M#;I>E2PGe=IciThsgYSV_JawvcZe9}l7wZBaX6#X;( z7dh|VwK)jC5<0G}q{KgL4{)P6n2ce3z658p=x@W1g!M@48AmlSwp9poU3lMPE- ze1gNKZAyDQZ2?x;pu#}o4rdT0Lc@O2sJ=lkqA2=Mfz5GEHZCcj**N;IICA*&U;c}yUzmm!+E0|w*(f|9F^GDu<1!it(&n|#;i{-uY zK+y#!jLNSA**QZ($1&Mi2}g_qU45Jrsr*$YUIj7Te|MoRgbC~^XfM%4YT6)35mi(; zEOdUd#2Hc{spAw6a+51l?(DM(Lo+C}0P8-T zg?i%WFomg5SSMdKa&YwC_kKLU)5vGq+jWhX5_=WAwvR$UUEi9~aBNne&O^tFze{Qb z?_SkeHFd58@?1i&q_r3LFovVEWk#n$d53E;dxw^mUJz59Fg5%{#Y`^s zU+29PXw&`45FOvpvg!s4qW)gUR;>f-gsg&^z+jdiqD9!oIjLSPw!Xw zMj~&@%?JtOATJvge1JEMEv1c0P?e_Z^&R`cO_i?R))P7fIUpmr*9sTX*=e-8!9s5> z_wvS|#n-Yy-3aLuVmU8eGwGfaVu@;b#})EB#*2SrQ9Is6E1KYaDw@&yA^YQ+$(ojS zYY)fp_V@JYO)~PRLYB3S^kwr@N!K}{(HprlkoMIM+3q9Zne$}q)i68WCnJH!E9$>F z@e7nVRw|SUgelV4R=D4`gNSW5YM^FlY^c6WYa4lK)?NEW{ymnQg2A=W?qM>vavR`Y z@>)m@yWbyAIt03+g40U_C7k@G5<7Do3k_YBDiTI`Rk*9=^ zi#9RYch2l{rWc)n3v6-GtAYQdzasZ zJevQ}h;rZiwDi;@I5*8t=RL)XL|mH_QMkWuzEN%DTkWYHpoT>2i$N&~272dZGgj5> z+XJfYU%1NsbfB5zzbI`;ES4VAx2PE~ql_x-?K79FZ&YPEI3wrE{L*6|T(R9=_SA3N zr{s@c_&y!1$?Cm%t7e#(x)CMy?&@#FQitXcC|W^fN=p&ys{f5! zu;1gB>fk3m_B&_FIMkKKLF&~WIDP6Ou3&vP%53D7#&A;x?8eOg6guq2q@l!96O%aN zxy{VyK!z`yn6xc%m%aP{iKhaeBLs7$TPgOPQ^R)*|H;SGwftRDejeE9!bYbi5-abL z@+M>hgE?V8@ngd*a+xr+rwPM|G^Zfu4R}qx1%wU3Im(lJ5IF1W{c&>6T`-4j*jeod zIB1=L7B1fv=CH);z3xENnVH)7<|MzudTjw**HfZwzY!;QqC;9`Su+V9IUp~LS;e;f zPhC>IS4)fhJ9jyoLELpT&F?l#vFHBuj==xufzS2MkKO*xmm$DuJ+&yi<&91IM<3q( zvO`B(4@c}2Y^KHflkvR7ooGQzdKJE4vY8q~h9Tg*-At`PoN8#V2$#yU=ncYnrZ94G zAHifioz_mJCich)cPJY#|1!w&i-O9iHi)R_h;=Suw|V_Elfx^h#VLyElq6WzV;ut_ zv~7hiM3+LSmCr1v+}8=ol*MQ*GdOl%HUNruHm2jLDemp`p6%bsbjr#fmEO@q&(~-p z)-t`k9xJ9WY2tg0t{tYA+zH zkv>jHaKMb3-FH5`o0w- zc@uZoraSR+x%PX{$ImG>3V+TIzR}kSE*^fAgDnAuoT!o@SHTA%z?aiN5qf*Mwuqxn z-o>JEHr8CXQj?OX_+D2be%Wd3lG8lHgx@yWL?P(#b;{B)pnQ;2`u15X*SM80{$gv5 zskaX{;eBSkNg8ycs}~eu}Sy!c}QQ8!< zQ5b6cIk@);OdakuMTZr)ankx%Vi(@?BE2oac_{6yzl5V1JabGvlok#`g4Cv^oza8F zVTvs?km1kGe}iGb1$|yMEYTn!q-Yo)smiZlub^jt_F~WatvH*(Tx1H+ zHiBUUm3+J0kDgr5+rLctK%Y#=F?xK|j%8p*h%}EyJv$&nt9KR)*o!eM&Np`-z1A5m zv|J;d!mz>1zknWvpw%)Hbw1KXt;PM#EBTRzUr;hyINzB`)sumx|!16px>ohuiliW)5m|~)ywUm*}NB`>cWvw*T>*k{m4YAF9)vu?+ z7n1Z3^?9jHe@GBX@--LL z&A9LLQ3HXv{yXbb?#TUNj=^;wzFriNsEX5sI|c?7Pm&8rNl!> z{))5nLC5y&i%QW#jnz4&+GI` zRfKL450+R}LdqXuP_-$bm;<+R`A-oY?e{PM&v^o%klRvsHkJCt5Wm|}$tHa3#z*#g zM+#HYrmXNyIJ`y20cYYGA57wujE-{jM7!^7;+5O#QpJt8eTcwXQ`u8(+wLUh#w+?i z+aVI9p5sZ}GWZQkeVg!j-kKC(td9bBTxHmxC^|imDbxC7Mw}8PWA(lIw$Nukhq#Zq zC_!i(Rm1z3hd^jpoANEx+Xu!h{UXdb#sqWasT05~y?wpzbd#0pM> zR{<$|z@W93!K*;8Tw&HK4Ci1Oo&R8ocfbEesggn9*5_c;ofBis{MQS^{r1}Lly}=@ zOZIJR0@f=H$sioVEBEiQ4a~P_A&xmVZPk|a z5kETFc|{L%fz11PyrI5(TrmmZz83~)*{N9Y#PW+5A1<979;CCT`R&xQNoENC2OYx> zKyOVT9b!?7M-Pc=Q%{H46nbx3qnxdLM-@)EQnP?7wfKh)5RY0aQ-Chby~uhKl7qp? zvD6{(!Y$2jn||T&!pbghe+T+r2x(X_plfq44CjXLRQ(x(A#S7JiFVPvqga?G1@yyI zJ$~xs(YrW#E2cYrwbbSP-tOt~h8WyCPV!QLT$6cc=y_I^`Rf1CAN*c+0QJEG5df^f zu5V6@?lSB-S|SkG1w@BAcpz)<>m5tw%G*5pa_8EY^|diq>J-@f|1DgDuNwfMUCu|a z+X*Rj#5Nt^T;qBUPCU|}NHZ-iMi1=vVVZ3U>ZTlME2qHFTpvu|COUMMKcWvB_|~Ap z`zHK<5%qgb@M@WH7T22Jt|cz+mRAsmO5k?kuA{XmUfkdfK6cpr>n9*BPO{))i685n zvPqdFE3YP-YN`Rbw>c|sqto6__m*yc4it+KLz3r$1R2F&BEfbuy`CgBZh+^CB+Y#o zZvddUS`L{&SdcKh8FDfxbFGgQ@aPop3lF8}tDGwDVr3IH6Y81dFl=yUgXGCbm$)CtLKtKV+4&>P4WTrETo58(t22iSXdm?^ zY*v}Z4zrq|39-khY0eoJzuSaOWONnP^2dtlZAniv-j*-imR(bAfq-ehwV5O;Rw{|? z`9zPZ6ieuj)y6{cR-&<4V8N;KaBC8wQh>%# zxVeO;^N~Bz=*dLP+6BTW*-FqLPq*;?zmUlYPC3%kmcI@>I>W|<%O7L@#+3Lq2TI*3SVtjT+|sz|M{Z-*;7+*uYryV##LLC$-?;lNnf46E;6)mF^RRqkX(3t zOwc!R!(}Q)=BuK@+XYL-A!h~q!1KyJAZALPNh&noo*z-`gs2o=px;3}>)3^mxEs30 zbyYgvqD*!~e+bm2BiY*8i=>HLORlVQ?9e$gCoKkc_Lgi`oB@w6_ZlSpZ7qLn{yyOZ zeO?Y7Dm7RAa~9+yuPj_q5<(9Cl0hUcCyRoGRjq7VrS)4d^d$1A)?(&LPZu+W-q;e8 z2^*NI+1bbz3zCUGkh=eeL0Ncz0xHa-EZu|WYoI{K=U_>2sctoz4T08*BD4!QtzO~; zs2rh~U0;!Fud-R}c;9uAjL=3Y*;;)suwu%@{p3&$p{c00me=9gMW4fgkt&r9pb!L- z^*vADJ>p<8jKozb$G7h~j&&LE_yaeq?Uk4BtK9XISqM891avc|zPjkdT zx`nsFih^m#k8uJxf3${4y@N%@()oKgb+8tVXHofG+&-&y1h{pbP4$+1%ARA7bT}{d z9pjB`{K=olLZ1G|y3$`mRA$~Ef7;Fp`TX+q@dJ<@kD9JzyP(f=6^IL{#RTuZb5 z*Z%z~ZNneZ=zGF)bVPDI$tE-^+7qmFmqk3Rt!N2M5n>>p2eyB=I!@fU2`kwC(lq|~BIF2da5T$?CMc7y<4;1j- z`25%Voo5X{iVl_tL~ZxfcBZ`?vm6ORf?0M1n)qCh&ox5vvQ40MlnJI{^8sHSrqPr= zE{}pCiGNEX}2Ggyv49d}bG>G>iehOW2?Z6O1@l06Det=e;%b z2o}}kO$o>cBCwaRif6EgMn_O)w#qYQ{lu3OBYZJ(M!h)A8&Qy4CJa4$zg`5W~5fy z!SzH*G-{x}Ykr6V>YbU!4d?Ybwi?UCX6=*D`9r21={iRsoPUI*4oBI<6VemL(tHAJ zsqSL8=Q9;6+@0Ny%f79g@J32p$b3~tSFJAyHj@`|uHL(J8G+K;;JroWsTjGXfsJ8{ zEZGM^bs`%Us>*J3-T8Lq$4GZ&nPd06JO>~V8yJsp@lRk=-c`&NTXG6_u$n)oIKmKP zUdxpTQz(WZo9W~X(^7!x;|Qrn<+`%K7cBM)Dr`TKDS%AVfsLmNYHajda2AZ@xmguM#i)p< zJcuDqx-nOd=fO9Un*XV-G`5~mmv+`N`2D5}ieUaUSEcO9ao(3}Pjxb5J?GcY|6YHV%Q_x?uWm~ z{+I zy%=E&vhXxsQAF#yGs9--XwM>hkHXOkjx04GKhS_mkBFz^KtrZDg_5C_#OOGp?(a>C z{h@vLV0c(R1O9sAW@{2>kaCxIr#*a}_Ms!OcvRd2->7Fadu5~*`8WBsn=k6Sd3gQk zv~y4_ssmdDHW_zzM4_@3 z!5={`{;w^V>&9uQ)P;Q>C0j~>VNAx&>e&Rc^5bu$U(SRbh9JM3#>fyu`mY-i5LQMW9`jdU?)q3b8)sy9D z0Z|agQo(Mzx`!!M_h-v!2#$UX!>hcER;mE8#uEH#!4xBNYLX&YXM* z`fQjrJ09Ip6|J=xEYVpgJbO&4Jfi|dx-0NV%lpddV=?QcVtXQ}7^eKl1YpjjtUfNE zQl4J<1m>~43LXuc za57qxN$z#$(XpfY$!^tj*KYCehVh!Hvf4(AA3xh&8pL>zDO7*aL(qxV4oaXSN{}&I zb!|2!mOh)M>)H{)jtH>x=9yjpbZ^44o5pH+V>8PuKk+TPOY|5%cpVXMA4J4Nkdwk-A_{+!yH_{!f_Rfd<^{TiFi9c%5B&k+UHqdP(#&=q2lh;X z3aZDD1Fy6bRHLLcN-M2g21#Zy*f3i;q{vdew;Wf#6C5Br(ZA=NWb5-{AY};q{qrY) zxD@dr5tTC$;?7Uv@Qkm+LB&k*+lVt&zc(ml#e)-VEVUN}Lb^khRO$~OZoVhi9*6w+ zjpl(UBVan+-}ddpY*gQ$IMW{2GEW+ zb8uJ>o>{gT6vsjYc|OnMms`Br4 zV*X$vmGt7q2)~>&Ed`fe5aI*xhm+rVCuOO3)HgZ(E&b|5j*SMs$7pWm?=CPmO@OTk zzhR)Lb`wcpeZn!(&82TH9#7lg)x)I^(^YAZErae?%f}DO>2tlxWDRLWjhp@CG0c_@ zZZ>q{gjydYbm`VLb?NlIbm=<&RL8`ovPC!Yuvy?Y^uNExuWq`1tYD_{cgVQy%m)K4 z7dwODkN*#sKxn^ijSFGLjd3ZOxW4lf#zm9tN@CO)m%@q*$1bdxG0r6sH^!x`;>Ngm zBIa0S6*tDEtm4|AkKavgE~^ySWR=9Itm4Kv=j3jTOIgK@aW1Rmy7H(fgs3M;Qm!5) zu_P+1xaO9!id*A?C#yBiMU}7n>t&8>&&^wo(;P?4nZ+C@7!~!*9M_()m9?umPT={m zs$D}cER5#nIDuJf%`G7gr#UX3Fxv@RU_LSaTy};#3wrdDf?zNYBiAatKdxP@aqW>q za8z9wp=HBJV3g(`E}k$`9A2kbW%jT3=%p`difg#n?Kxrh zVHaSJ@uLvkyJxY*31(u;rY)}V0?KN6`w4@n!BK2+6=M?6GjaV#*y0*4LhNaaYq-P? z+}&((4u>r+9*DWM0upA6YrH0L7h7Bm-%M=+-^egX_}+G4B!<;U+!)s^#g>*hqm^j?u*E99qeC0KhR7DO+7a6_E5*nL6N5XX-)+{om@XR!43*NtTn*=oIEv)5$E ztcLwL=CfcH?_vZ0GaUoBMm}L0X}H=FmMdqExKP!MDJT$ebVbaU!gJJ@)U0=0AvG>I zf?p_17}sm|@(IMrtUN@GiWtzY+>1AOE;40`MYuHd?s;h8O3-06ScW#zrJYv0)foY#4^gIi9RL5;lzeF#n_2Fl++CLV;De zdWD(HUThdx>0WFYAjVm27$BTtz}T=)7X!xbtg@4Y2=GO@-R>391?jinD?$Ut?#mX$ z4Hz4RE{XwT53MKqWq+wu;7ad?{;8F{C`mcuGZFA%@jQZXurbidwC7N@_T+_mp=#~P zyl1FZg9+bU4y`Xr54bOAm^=V$jACN=jnCgaE{*-z^($I0&qGL@kTmLyO5ujDNW;z$ zdMfbYR&^zE?`G#)%!Xn5RDt=)64UW9^Ai)*dRKvG!2;l(mPxXRJLJm8`o^p|bWuwm{^9 zC~FUWFIao%d&b&b-!snsb#uM+z1{{KhehA($UquT(f4}Os>H7Eb>xig9{QfZtZSI! zvtTrG#Pq$6oHA&PCU85P`d&O?B05``8h*j=g@p&*n<7>D_*Epln1<1j zImXN&Rr%P%{S=i?Fpnu_s`2%tmt$wXE8Ix{8jQ29DC z7n%5=Dxbrl^2Gx&y(omfLgnj7q|-ynRK6I#DRdo~a&R>SJhqvG6VqvUHlO9)V_}F$6eJvewD$Ts`jy0kXgR$FID?2 zlAt4>wo~m3Ok_S@VvAAZncG&R=^G*(%DW%fjfCiz|S_Xjxu6F9FvV>VJ1uXU}1PWpncSH zlB7>v*@a6vg1Pko^p()-;IIz7K+fQdK*%R-+Vm3Q~QNrOgN;oFx_~u;%Zj=z%cDfrSd;;9$&`}5-(pSRW z4BCOwTfE#T(Xk<62r;9ClTO+tI`WQrp;Qo;Uig{h*wvF*=vSYJWocH+UQH7n`}(P| z75pj-FsNyQy<#1?_cpVXc8w3E#HsbZGPg5DhWGF^T)8^4p!?OLvvy~s)wunj;8DtbP9QIv8vt8 zt2x}rz=fZl7;nWPQt_N?6M9Q>t?|>ptl{_ z%D|iQ3&~6mIj?=w(wzjJ7Ld^yA<3OR@3Ih!H%?+uylBF7W-Kr4|#zIy|927~2TJ^uD)s3TN&%Ho(XkL^|JvS~Shjx!~`= zWw-S@XU{4VSml_czotffH9i zZ=3gJH}l>4N5N<_X%+hk6MH-{7hQ(OIPGO=#cDX~kQ%O3#(Gnd} z!>Z8|eB;z;i4Kd)ozW8f3Kr5eBkM)cQsCAp$x7_@UKJkZU;HvbFVk7dAZ|KK8N^Lz zE`x+nY0KrNvlK!?f>I$Q1T15VZnH=>>6Wv2!i8}=N!+%TLWo;tOb9XCn9Cpmx(Jd8 zN+9{RN+8+3OCZ?;l|bBh=JJIb&r$+$<5@oO7|T%!#0^#ZSRFf5E6*n z&Qb(%+gS=6A&>urF@Ynyk{Gp}r3exljqwt*ow^ybbSxO*oK644AP!{EjLW?GW6yoiwsQI#}1mYI7lt4m3 zHVMQPH)C*L_t@Klwje{iENG}sf4?kfs8PO(1#LB61sxVN5tx()gW<2n%0Xd4L-GHy z({^ESv7q@AZtXw|8qvVdjh6v!wXI$SV=@@T=oOw88yc9w*sE+|X#Ad5$Mgd*KF;ds zVL)4hbzQ@NW^gf}`4fHs0=_b4f(`bU0c{OdAq!g#N|(=!nP3Oc%YcTO|GOH{P&j(L z%w<1do@LIF=t^SDfQG8~t>iCt)Lmc2fVLn&?KGenoCY+1AZGX0_$xP{t@|>In;X!4 z_--C!Vf|iUn1^2mG_3Je3}_2BnhcvTf+;5xE|fUMeztH76cQv@X;y!a1z|~+G@jwM zi}MmM7m67b;~A*(D#kP0TCe0KB4eI)B2=Ew*9ZEQKAtYQBjN&eKUcF1gsJ!WuDK5R9132F9f|!R)BRihG?h zWr-kg*2Xc*7q*0rV)p=lk4Tbyz)n8EELv+PpEZa#XmoQr{N!PYWG2Vt%q6^()QxhZ z7xq9*q+Ut@_I?;ye_H@8rm~gYgn>U~MYkn`IgF$?J@W%&A%V6S52NdIo}TGONv)?kYcuu?T#?(sw{#=LcL%$$oBYwkHe z%ETxw;3O~u`a@PQW|@h1u^3&eG5S&kcq!Wo#Ld{ueErtw*Z9Eg@v+ebOLE+BJV~?JIvzyBLa5y9_%$5{moYNire&@3j02GgmI&8CNDee!xFo+M0hcg z8|TbbxTYs+D>W)y`IkT89PiQ~Z5MTt_qwQK;z5ubW@r8S!ez;Mo5;JrphY!`IRs($ zM^`@uRE4`@pdOvT2+SIXjhB&Zz!ZmB9FYO2vJ9;L*?4=*BmZvRR0E4Ivg8F8QvU_F z9xK?j8{L%WaKztuUK3Z~7Gw}hz&F!?`G#RG*0f^_h!$XNezpK*5k-6_XO^GDAUuE# zT@nyX3_%w6=7rd(+1uz;6-)c>YZ}gK`NjuOw7ILscEdpZ*OdkOukqGi?Aj(Xk7POSN1~l-WFc(eC#89+=yHCTfb?dT7FE+yiot?QtmL%UE6*b`O zKrt(UyQXU`Ud15rt(!w(zOi&({RvFyo%4?PSN7piBPIi85%6al2;JnfBp6f*<3|wH z#S0Sn(E-)oVYY#q1@(ywl7N$#42-wcI!WRU6=7L>dm*Y+1wGW-9*k49sj^-ByG|aW zwuL|9Q1xYM5Dhsng%1r{UEg>uq^+*7BJ6dw4E>Ra#YAomiGPxNJX)H|Cj-qQDkwIC zfDiY&t<1ywCMpAasI&xSXo&b-R%r76zPg5UYYLh_u^57DYbGF_Va^fp!e+_xoNp8h z#~?0ES92Iu$Qhd8n1i}g43pLt;Gx#gZ~X@DDb^BZ-ieJOjNppsq$nrIt!D_P4Z~p$ zoMLc30b4e=(LMOh8CKR5UkLcrZ^{|IRB**w_5xNCMl@zW zycQaHchUjes{tGrUZHp%1%E(3i}9k5+OH3a^G@B14=b>H2N?S<6ob=7BinTX9OgRG zVP2k~=_Fg2bT8L$ECyd^FIaWMI*0(PvVvpDI41DwC?fzP$~Fy~%nlLzjlC$)QS-7U`N}dH`O4D=;Gedi-$s9nUuRI(P&)?!7xDpF*~5asn@;$g#Srk+TtC? z3lPEU6I3j-fkrEF{Q}h^>E&bhukw{n1fv8eE94fiK@xYEebgZBctWp20*o5$0#k@& z32|C^1!Kpn$z6cKXQ7GSJ>h<=ct&DkH70rBPZ$#%KJ%?_n0k2X8h*!=x12h@W8&)| zqN&%;l(X6}!Ze;iTTf$cBf-EOkEq%S-H#)K)sl*f4Z8JO`tiEKQz_jHT1= z%IYT6?@^zS*3keCO94A$VG&ZfE}dPR=-_<^1$6{O9><0r1Mz7>Zdx%2K0lD8hT<(K za3wIMVrLxz!{0NsOWow=TrKV74;Yhd6XQI`LAn8Ng;Bf)9=WC+8_Y<=RUyo701T$( z2Z_zqKCGz27ml&bhm2zK#;;$flYx>eZR*7-4%>1_Q{@TD3Z(^<%)lq+GqqC><&T59 z*$}}%0eEFon*)kgL)#6}Xq4 zh6{gX`OAxjv1=wtB?d4Onq-J+swXkxcj=8JAu^z-=gzInH!fxQ zrUl>@S_F(Ef*k*%sN+l+lcHCBlqUuiD?39_S@O&{L-@jljTNd2YLT`cr%FR@%DzGv z!y9_BMrd<@F;Ez_z(E3TtmSEM-2Jr*TC(9grmo$dUw4mT;1HlRkIrkM0+rq|dgeNt z)aEmqavj!S)d!Y-)r5Q8cFeTp5dUFd(4ARR977U!n5L)ZZ>;f^iWf6ED;2Z2{)>tgQ+P9sOfC5y3}tdg z23v%Q3A`ftCN5IdZ!2fT&8a8(E)0&MYZcB6qtPj1>e6c(LX3rN7N-G?RQd^-e@Jc0 zu^OyGvKk<5)KGJ{NMeq0(Z?EavbQfF=<|N`3xk)tVp*9AWPMD+0K*$E%F$ux6AvTA zT_eCWQX^@Zx=3N2(z=KZX1u3} zxwa_5uVENm3A48>Pm)hOe%g_vH&`C!Ti_pEWd2lvO>fSn>^F?|go1Mb}+%b^F>=$pNjQuZX(A->oQT*f&$4OVh}2XfM;3VG3tJ zu8!=DU=2tGl4pZ8Y>jX!tO02j>m+MHCeuk?z(l8)(1vs?kSdOXlnHi-tHDBiD6G}EfR6F%|A6uvBkGKGzb7uj>H&NG~$4OU%Y)>v1`jOqiD zsGbxY1ZI|evDYFEYu#zJLc(S*XB2Y3dV3M#QbjkO*j+0(nRv5lObXJ*8(aCZeYI_f zgW4+^rS_pes2IBTQM)VbiOi5ZLk*24)jG`3lICwL?$PVHl-C%>l>>Vx)C<6}TpP)X zY6}5I#L?tMtdd!H(VvoSlblu(}pNNIGWvViUc55dZ>gre(3p zLhsvdB4OeyEK`_om_PPf*1vW$0c|nZ4HdhtVQU2+ZsC=bBUF^y$HB};?K&r%w zCYh!vX9)&nW)UMo=blVDoTqGPg^TgaF}769ri5gH%Z$j6wF1w<<#t1-8S)~9{YP`w zRq!(YORBz}aMN%Yjm*(qJJ>tef^foJmV^u%rDU$3SSpJJpICHg@t#UclO%&zODosy za+Yr=nIp{94LA+Tnzj70repaiU6|q}dqJ%%&PSHX7RhP2eEGms(Zb)it(&Udo{Yr{ zo?`ZjUR*P6WazU@JRLlRkCOX^F6T$&EiLhNC2?l`dfFs>7UT z4yrssQ(v}V8!s&p#uU%R$Z+4O*+~WjQlvYyM9?3mAOz&xTv_Y zUI#kHOAR(mk-5%bvUqTvgW_IA<~leddzHCl6zFd7k<%G)9Bkr%wO2lJZ~_%hBLrz0 zp2}ROUB-M}<~o;Jdram!7YmLB9T;3>F2}gcWevE@WnU;V7Y}0>xU7S5ieUuz%R6IZ z&L<$dwh*|^SSpW&%;lIAvTS+2V_ss{L657{rHBx^0G=X#9zsngwzG9x^f#kY*JT1#!c<*5O}QpA`+c3)I?-L z6MDD`ud^`}W{5$qS)b_Y5BC-jUh;|$wI-7ME14~h!xSQHPyai1muHV2l06QeZ zj?14d2XG8)q=kZsRCroPq>7#9x;#$&JnQy}&4 z2tm&g!4@3+^YMhitzWDK=fv4@9eb~q1WxbslqdR9@j*{>fv0K>E-%EA**66q8UW*c z>Lw(2yRn3e4pqixgK$gxcEWn5*c25-@ymAQW0{96V|Md-+yyVJ)D)le8;@}U;SEY9thp9U%0Mf z72QuTdOn%%C-9{BXu6-kG<&4(C#@oc%)iue&wQ)fcQfL@Zt(pISm?MMTkAdA2FHZm zuMLj-z4D_Sa^kg;4MC3Vt*Cw-(>3l;{Rp2Hs$amN`VljGQT+mAs-M5(@>_=#Ayl5K zA2GrnT_ox_1t6c8NYp{>#azOX(XdeW#Yw%7Q*#g3`v|tX=zW4=ae(-u!>0WahON}K zzmA*7XDELiSC5Yo%3sID;%gV>ujA_YC%u!hzRcxvnatNar8W}8=;aBhNq4j<@B`~j zD1T0cF2)JmsO_3u;czN{fw9G63%p=Nl|O-5H!zhy(lvx|(?CeqGp%pzszy#+1KAI4?fxQ8ew3i&yv?zqvGY z!}-~M^h9xfw!5Be$?zVa04e=&^E|DxkD`+I5# zr0RdcBtrjVH|LHOWVdi#jp={r6kXV%0D8|{2lSq~z#DL=Pk}M{lrZd&rUP=|p#b{C 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 literal 0 HcmV?d00001 diff --git a/source/solucion.rst b/source/solucion.rst index b27ffbb..9d82b89 100644 --- a/source/solucion.rst +++ b/source/solucion.rst @@ -550,7 +550,1205 @@ lectura más cercana a la realidad del uso de un recolector. Modificaciones propuestas ---------------------------------------------------------------------------- -TODO +Se decide realizar todas las modificaciones al recolector actual de forma +progresiva e incremental, partiendo como base del recolector de la versión +0.99.9 de Tango_. Las razones que motivan esta decisión son varias; por un +lado es lo más apropiado dados los requerimientos claves mencionados al +principio de este capítulo. Por ejemplo, al hacer cambios incrementales es más +fácil comprobar que la eficiencia no se aleja mucho del actual con cada +modificación y una modificación gradual impone menos resistencia a la +aceptación del nuevo recolector. + +Además la construcción de un recolector de cero es una tarea difícil +considerando que un error en el recolector es extremadamente complejo de +rastrear, dado que en general el error se detecta en el *mutator* y en una +instancia muy posterior al origen real del error. Esto ha sido comprobado de +forma práctica, dado que, a modo de ejercicio para interiorizarse en el +funcionamiento del *runtime* de D_, primero se ha construido desde cero una +implementación de un recolector *naïve*, resultando muy difícil su depuración +por las razones mencionadas. Por el contrario, comenzar con un recolector en +funcionamiento como base hace más sencillo tanto probar cada pequeña +modificación para asegurar que no introduce fallos, como encontrar y reparar +los fallos cuando estos se producen, ya que el código incorrecto introducido +está bien aislado e identificado. + +A continuación se hace un recorrido sobre cada una de las mejoras propuestas, +y en los casos en los que la mejora propone un cambio algorítmico, se analiza +la corrección del algoritmo resultante, partiendo de la base de que el +algoritmo tomado como punto de partida es un marcado y barrido que utiliza la +abstracción tricolor para hacer la fase de marcado de forma iterativa (ver +:ref:`gc_mark_sweep` y :ref:`gc_intro_tricolor`), cuya corrección ya está +probada en la literatura preexistente. + + +.. _sol_config: + +Configurabilidad +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Una de las primeras mejoras propuestas es la posibilidad de configurar el +recolector de forma más sencilla. El requerimiento mínimo es la posibilidad de +configurar el recolector sin necesidad de recompilarlo. La complejidad de esto +surge de que el recolector debe ser transparente para el programa del usuario. + +Configurar el recolector en tiempo de compilación del programa del usuario +probablemente requeriría modificar el compilador, y además, si bien es una +mejora sustancial a la configuración en tiempo de compilación del recolector, +no termina de ser completamente conveniente para realizar pruebas reiteradas +con un mismo programa para encontrar los mejores valores de configuración para +ese programa en particular. + +Por otro lado, permitir configurar el recolector en tiempo de ejecución, una +vez que su estructura interna ya fue definida y creada, puede ser no solo +tedioso y complejo, además ineficiente, por lo tanto esta opción también se +descarta. + +Finalmente, lo que parece ser más apropiado para un recolector, es permitir la +configuración en tiempo de inicialización. Es decir, configurar el recolectar +sin necesidad de recompilar ni el programa del usuario ni el recolector, pero +antes de que el programa del usuario inicie, de manera que una vez iniciado el +recolector con ciertos parámetros, éstos no cambien nunca más en durante la +vida del programa. + +Este esquema provee la mejor relación entre configurabilidad, conveniencia, +eficiencia y simplicidad. Una posibilidad para lograr esto es utilizar +parámetros de línea de comandos, sin embargo no parece ni sencillo (proveer +una forma de leer los parámetros de línea de comandos requiere cambios en el +*runtime*) ni apropiado (el recolector debería ser lo más transparente posible +para el programa del usuario). + +Otra posibilidad es utilizar variables de entorno, que parece ser la opción +más sencilla y apropiada. Sencilla porque las variables de entorno pueden ser +leídas directamente al inicializar el recolector sin necesidad de cooperación +alguna del *runtime*, a través de :manpage:`getenv(3)`. Apropiada porque, si +bien el problema de invasión del programa del usuario también existe, es una +práctica más frecuente y aceptada la configuración de módulos internos +o bibliotecas compartidas a través de variables de entorno. + +Por último, antes de comenzar a usar este esquema de configuración, se +verifica que tomar ciertas decisiones en tiempo de ejecución no impacten en la +eficiencia del recolector. Para esto se convierten algunas opciones que antes +eran solo seleccionables en tiempo de compilación del recolector para que +puedan ser seleccionables en tiempo de inicialización y se comprueba que no +hay una penalización apreciable. + + +.. _sol_config_spec: + +Especificación de opciones +^^^^^^^^^^^^^^^^^^^^^^^^^^ +Para especificar opciones de configuración, hay que hacerlo a través de la +variable de entorno de nombre :envvar:`D_GC_OPTS`. El valor de esa variable es +interpretado de la siguiente manera (en formato similar a :term:`BNF`): + +.. productionlist:: + D_GC_OPTS: `option` ( ':' `option` )* + option: `name` [ '=' `value` ] + name: `namec` `namec`* + value: `valuec`* + namec: `valuec` - '=' + valuec: [0x01-0xFF] - ':' + +Es decir, se compone de una lista de opciones separadas por **:**. Cada opción +se especifica con un nombre, opcionalmente seguido por un valor (separados por +**=**). + +El valor de una opción puede ser un texto arbitrario (exceptuando los +caracteres ``'\0'`` y ``':'`` y de longitud máxima 255), pero cada opción lo +interpreta de forma particular. Como caso general, hay opciones booleanas, que +toman como valor verdadero un cualquier número distinto de 0 (o si el valor es +vació, es decir, solo se indica el nombre de la opción), y como valor falso +cualquier otro texto. + +A continuación se listan las opciones reconocidas por el recolector (indicando +el formato del valor de la opción de tener uno especial): + +``mem_stomp`` + Esta es una opción (booleana) disponible en el recolector original, pero + que se cambia para que sea configurable en tiempo de inicialización + (estando desactivada por omisión). Activa la opción ``MEMSTOMP`` descripta + en :ref:`dgc_debug`. + +``sentinel`` + Esta opción es también booleana (desactivada por omisión), está disponible + en el recolector original, y se la cambia para sea configurable en tiempo + de inicialización. Activa la opción ``SENTINEL`` descripta en + :ref:`dgc_debug`. + +``pre_alloc`` + Esta opción permite crear una cierta cantidad de *pools* de un tamaño + determinado previo a que inicie el programa. Si se especifica solo un + número, se crea un *pool* con ese tamaño en MiB. Si, en cambio, se + especifica una cadena del tipo ``3x1``, el primer número indica la cantidad + de *pools* y el segundo el tamaño en MiB de cada uno (3 *pools* de 1MiB en + este caso). Ver :ref:`sol_pre_alloc` para más detalles sobre la utilidad de + esta opción. + +``min_free`` + El valor de esta opción indica el porcentaje mínimo porcentaje del *heap* + que debe quedar libre luego de una recolección. Siendo un porcentaje, solo + se aceptan valores entre 0 y 100, siendo su valor por omisión 5. Ver + :ref:`sol_ocup` para más detalles sobre su propósito. + +``malloc_stats_file`` + Esta opción sirve para especificar un archivo en el cual escribir un + reporte de todas la operaciones de pedido de memoria realizadas por el + programa (durante su tiempo de vida). Ver :ref:`sol_stats` para más + detalles sobre la información provista y el formato del reporte. + +``collect_stats_file`` + Esta opción sirve para especificar un archivo en el cual escribir un + reporte de todas las recolecciones hechas durante el tiempo de vida del + programa. Ver :ref:`sol_stats` para más detalles sobre la información + provista y el formato del reporte. + +``conservative`` + Esta opción booleana permite desactivar el escaneo preciso del *heap*, + forzando al recolector a ser completamente conservativo (excepto por los + bloques con el atributo ``NO_SCAN`` que siguen sin ser escaneados). Ver + :ref:`sol_precise` para más detalles sobre la existencia de esta opción. + +``fork`` + Esta opción booleana (activada por omisión) permite seleccionar si el + recolector debe correr la fase de marcado en paralelo o no (es decir, si el + recolector corre de forma concurrente con el *mutator*). Para más detalles + ver :ref:`sol_fork`. + +``eager_alloc`` + Esta opción booleana (activada por omisión), sólo puede estar activa si + ``fork`` también está activa y sirve para indicar al recolector que reserve + un nuevo *pool* de memoria cuando una petición no puede ser satisfecha, + justo antes de lanzar la recolección concurrente. Ver + :ref:`sol_eager_alloc` para más detalles sobre el propósito de esta opción. + +``early_collect`` + Esta opción booleana (desactivada por omisión), también sólo puede estar + activa si ``fork`` está activa y sirve para indicar al recolector que lance + una recolección (concurrente) antes de que la memoria libre se termine (la + recolección temprana será disparada cuando el porcentaje de memoria libre + sea menor a ``min_free``). Ver :ref:`sol_early_collect` para más detalles + sobre el propósito de esta opción. + +Cualquier opción o valor no reconocido es ignorado por el recolector. Se +utilizan los valores por omisión de las opciones que no fueron especificadas, +o cuyos valores no pudieron ser interpretados correctamente. + +Para cambiar la configuración del recolector se puede invocar el programa de +la siguiente manera (usando un intérprete de comandos del tipo *bourne +shell*): + +.. code-block:: none + + D_GC_OPTS=conservative:eager_alloc=0:early_collect=1:pre_alloc=2x5 ./programa + +En este ejemplo, se activan las opciones ``conservative`` y ``early_collect``, +se desactiva ``eager_alloc`` y se crean 2 *pools* de 5MiB cada uno al +inicializar el recolector. + + +Reestructuración y cambios menores +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Si bien se decide no comenzar una implementación desde cero, se ha mostrado +(ver :ref:`dgc_bad_code`) que la implementación actual es lo suficientemente +desprolija como para complicar su modificación. Es por esto que se hacen +algunas reestructuraciones básicas del código, reescribiendo o saneando de +forma incremental todas aquellas partes que complican su evolución. + +Además de las modificaciones puramente estéticas (aunque no por eso menos +valuables, ya que la legibilidad y simplicidad del código son un factor +fundamental a la hora de ser mantenido o extendido), se hacen otras pequeñas +mejoras, que se detallan a continuación. + +Remoción de memoria encomendada +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Si bien a nivel de eficiencia este cambio no tuvo impacto alguno (cuando en un +principio se especuló con que podría dar alguna ganancia en este sentido), se +elimina el concepto de memoria *encomendada* para quitar complejidad al +código. + +Esta mejora no afecta a la corrección del algoritmo, ya que a nivel lógico el +recolector solo ve la memoria *encomendada*. + +Micro-optimizaciones +^^^^^^^^^^^^^^^^^^^^ +Si bien se realizan varias micro-optimizaciones, probablemente la más +relevante es la inclusión de un caché de tamaño de bloque para el método +``findSize()`` de un *pool*. Esto acelera considerablemente las operaciones +que necesitan pedir el tamaño de un bloque reiteradamente, por ejemplo, al +añadir nuevos elementos a un arreglo dinámico. + +Esta mejora tampoco afecta a la corrección del algoritmo, ya que nuevamente no +afecta su comportamiento a nivel lógico, solo cambia detalles en la +implementación de forma transparentes para el algoritmo de recolección. + +Optimizaciones sobre ``findPool()`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Al analizar los principales cuellos de botella del recolector, es notoria la +cantidad de tiempo que pasa ejecutando la función ``findPool()``, que dado un +puntero devuelve el *pool* de memoria al cual pertenece. Es por esto que se +minimiza el uso de esta función. Además, dado que los *pools* de memoria están +ordenados por el puntero de comienzo del bloque de memoria manejado por el +*pool*, se cambia la búsqueda (originalmente lineal) por una búsqueda binaria. +Finalmente, dado que la lista de libre está construida almacenando el puntero +al siguiente en las mismas celdas que componen la lista, se almacena también +el puntero al *pool* al que dicha celda pertenece (dado que la celda más +pequeña es de 16 bytes, podemos garantizar que caben dos punteros, incluso +para arquitecturas de 64 bits). De esta manera no es necesario usar +``findPool()`` al quitar una celda de la lista de libres. + +Una vez más, la mejora no afecta la corrección del código. + +.. _sol_pre_alloc: + +Pre-asignación de memoria +^^^^^^^^^^^^^^^^^^^^^^^^^ +Esta opción permite crear una cierta cantidad de *pools* de un tamaño +determinado previo a que inicie el programa. Normalmente el recolector no +reserva memoria hasta que el programa lo pida. Esto puede llegar a evitar +que un programa haga muchas recolecciones al comenzar, hasta que haya +cargado su conjunto de datos de trabajo. + +Se han analizado varios valores por omisión pero ninguno es consistentemente +mejor que comenzar sin memoria asignada, por lo tanto no se cambia el +comportamiento original, pero se agrega una opción (ver ``pre_alloc`` en +:ref:`sol_config_spec`) para que el usuario pueda experimentar con cada +programa en particular si esta opción es beneficiosa. + +Esta opción tampoco cambia la corrección del algoritmo de recolección, solo +sus condiciones iniciales. + +.. _sol_ocup: + +Mejora del factor de ocupación del *heap* +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +El factor de ocupación del *heap* debe ser apropiado por dos razones. Por un +lado, si el *heap* está demasiado ocupado todo el tiempo, serán necesarias +muchas recolecciones que, aunque pequeñas dado que la memoria utilizada es +poca, puede llegar a ser extremadamente ineficiente en casos patológicos (ver +:ref:`dgc_bad_ocup`). Por otro lado, si el tamaño del *heap* es extremadamente +grande (en comparación con el tamaño real del grupo de trabajo del programa), +se harán pocas recolecciones pero cada una es muy costosa, porque el algoritmo +de marcado y barrido es :math:`O(\lvert Heap \rvert)` (ver +:ref:`gc_mark_sweep`). Además la afinidad del caché va a ser extremadamente +pobre. + +Para mantener el factor de ocupación dentro de límites razonables, se agrega +la opción ``min_free`` (ver :ref:`sol_config_spec`). Esta opción indica el +recolector cual debe ser el porcentaje mínimo del *heap* que debe quedar libre +luego de una recolección. En caso de no cumplirse, se pide más memoria al +sistema operativo para cumplir este requerimiento. Además, luego de cada +recolección se verifica que el tamaño del *heap* no sea mayor a ``min_free``, +para evitar que el *heap* crezca de forma descontrolada. Si es mayor +a ``min_free`` se intenta minimizar el uso de memoria liberando *pools* que +estén completamente desocupados, mientras que el factor de ocupación siga +siendo mayor a ``min_free``. Si liberar un *pool* implica pasar ese límite, no +se libera y se pasa a analizar el siguiente y así sucesivamente. + +Esta modificación no afecta a la corrección del algoritmo, ya que no lo afecta +directamente. + +Modificaciones descartadas +^^^^^^^^^^^^^^^^^^^^^^^^^^ +Se realizan varias otras modificaciones, con la esperanza de mejorar la +eficiencia del recolector, pero que, al contrario de lo esperado, empeoran la +eficiencia o la mejoran de forma muy marginal en comparación con la +complejidad agregada. + +Probablemente el caso más significativo, y por tanto el único que vale la pena +mencionar, es la conversión de marcado iterativo a marcado recursivo y luego +a un esquema híbrido. Como se describe en :ref:`dgc_bad`, el marcado iterativo +tiene sus ventajas, pero tiene desventajas también. Al convertirlo a puramente +recursivo, se impracticable por resultar en errores de desbordamiento de pila. + +Por lo tanto se prueba con un esquema híbrido, poniendo un límite a la +recursividad, volviendo al algoritmo iterativo cuando se alcanza este límite. + +La implementación del algoritmo híbrido consiste en los siguientes cambios +sobre el algoritmo original (ver :ref:`dgc_algo_mark`):: + + function mark_phase() is + global more_to_scan = false + global depth = 0 // Agregado + stop_the_world() + clear_mark_scan_bits() + mark_free_lists() + mark_static_data() + push_registers_into_stack() + thread_self.stack.end = get_stack_top() + mark_stacks() + pop_registers_from_stack() + mark_user_roots() + mark_heap() + start_the_world() + + function mark_range(begin, end) is + pointer = begin + global depth++ // Agregado + while pointer < end + [pool, page, block] = find_block(pointer) + if block is not null and block.mark is false + block.mark = true + if block.noscan is false + block.scan = true + if (global depth > MAX_DEPTH) // + more_to_scan = true // + else // Agregado + foreach ptr in block.words // + mark(ptr) // + global depth-- // + +Al analizar los resultados de de esta modificación, se observa una mejoría muy +level, para valores de ``MAX_DEPTH`` mayores a cero (en algunos casos bastante +mayores) y en general para ``MAX_DEPTH`` cero (es decir, usando el algoritmo +de forma completamente iterativa) los resultados son peores, dado que se paga +el trabajo extra sin ganancia alguna. En la figura :vref:`fig:sol-mark-rec` se +puede ver, por ejemplo, el tiempo total de ejecución de Dil_ al generar la +documentación completa del código de Tango_, según varía el valor de +``MAX_DEPTH``. + +.. fig:: fig:sol-mark-rec + + Análisis de tiempo total de ejecución en función del valor de + ``MAX_DEPTH``. + + Tiempo total de ejecución de Dil_ al generar la documentación completa del + código de Tango_ en función del valor de ``MAX_DEPTH``. El rombo no + pertenece a ningún nivel de recursividad, representa el tiempo de ejecución + del algoritmo original (puramente iterativo). + + .. image:: sol-mark-rec-dil.pdf + + +Dado que aumentar el nivel máximo de recursividad significa un uso mayor del +*stack*, y que esto puede impactar en el usuario (si el usuario tuviera un +programa que esté al borde de consumir todo el *stack*, el recolector podría +hacer fallar al programa de una forma inesperada para el usuario, problema que +sería muy difícil de depurar para éste), y que los resultados obtenidos no son +rotundamente superiores a los resultados sin esta modificación, se opta por no +incluir este cambio. Tampoco vale la pena incluirlo como una opción con valor +por omisión 0 porque, como se ha dicho, para este caso el resultado es incluso +peor que sin la modificación. + +Esta modificación mantiene la corrección del recolector dado que tampoco +modifica el algoritmo sino su implementación. Además ambos casos extremos son +correctos (si ``MAX_DEPTH`` es 0, el algoritmo es puramente iterativo y si +pudiera ser infinito resultaría en el algoritmo puramente recursivo). + + +.. _sol_stats: + +Recolección de estadísticas +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Un requerimiento importante, tanto para evaluar los resultados de este trabajo +como para analizar el comportamiento de los programas estudiados, es la +recolección de estadísticas. Hay muchos aspectos que pueden ser analizados +a la hora de evaluar un recolector, y es por esto que se busca que la +recolección de datos sea lo más completa posible. + +Con este objetivo, se decide recolectar datos sobre lo que, probablemente, +sean las operaciones más importantes del recolector: asignación de memoria +y recolección. + +Todos los datos recolectados son almacenados en archivos que se especifican +a través de opciones del recolector (ver :ref:`sol_config_spec`). Los archivos +especificados debe poder ser escritos (y creados de ser necesario) por el +recolector (de otra forma se ignora la opción). El conjunto de datos +recolectados son almacenados en formato :term:`CSV` en el archivo, comenzando +con una cabecera que indica el significado de cada columna. + +Los datos recolectados tienen en general 4 tipos de valores diferentes: + +Tiempo + Se guarda en segundos como número de punto flotante (por ejemplo ``0.12``). + +Puntero + Se guarda en forma hexadecimal (por ejemplo ``0xa1b2c3d4``). + +Tamaño + Se guarda como un número decimal, expresado en bytes (por ejemplo ``32``). + +Indicador + Se guarda como el número ``0`` si es falso o ``1`` si es verdadero. + +Esta modificación mantiene la corrección del recolector dado que no hay cambio +algorítmico alguno. + +Asignación de memoria +^^^^^^^^^^^^^^^^^^^^^ +La recolección de datos sobre asignación de memoria se activa asignando un +nombre de archivo a la opción ``malloc_stats_file``. Por cada asignación de +memoria pedida por el programa (es decir, por cada llamada a la función +``gc_malloc()``) se guarda una fila con los siguientes datos: + +1. Cantidad de segundos que pasaron desde que empezó el programa (*timestamp*). +2. Tiempo total que tomó la asignación de memoria. +3. Valor del puntero devuelto por la asignación. +4. Tamaño de la memoria pedida por el programa. +5. Si esta petición de memoria disparó una recolección o no. +6. Si debe ejecutarse un *finalizador* sobre el objeto (almacenado en la + memoria pedida) cuando ésta no sea más alcanzable (cuando sea barrido). +7. Si objeto carece de punteros (es decir, no debe ser escaneada). +8. Si objeto no debe ser movido por el recolector. +9. Puntero a la información sobre la ubicación de los punteros del objeto. +10. Tamaño del tipo del objeto. +11. Primera palabra con los bits que indican que palabras del tipo deben ser + escaneados punteros y cuales no (en hexadecimal). +12. Primera palabra con los bits que indican que palabras del tipo son + punteros garantizados (en hexadecimal). + +Como puede apreciarse, la mayor parte de esta información sirve más para +analizar el programa que el recolector. Probablemente solo el punto 2 sea de +interés para analizar como se comporta el recolector. + +El punto 8 es completamente inútil, ya que el compilador nunca provee esta +información, pero se la deja por si en algún momento comienza a hacerlo. Los +puntos 9 a 12 provee información sobre el tipo del objeto almacenado, útil +para un marcado preciso (ver :ref:`sol_precise`). + +El punto 6 indica, indirectamente, cuales de los objetos asignados son +*pesados*, ya que éstos son los únicos que pueden tener un *finalizador*. +Además, a través de los puntos 4 y 10 es posible inferir si lo que va +almacenarse es un objeto solo o un arreglo de objetos. + +Recolección de basura +^^^^^^^^^^^^^^^^^^^^^ +Los datos sobre las recolecciones realizadas se guardan al asignar un nombre +de archivo a la opción ``collect_stats_file``. Cada vez que se dispara una +recolección [#solcollect]_ (es decir, cada vez que se llama a la función +``fullcollect()``) se guarda una fila con los siguientes datos: + +1. Cantidad de segundos que pasaron desde que empezó el programa (*timestamp*). +2. Tiempo total que tomó la asignación de memoria que disparó la recolección. +3. Tiempo total que tomó la recolección. +4. Tiempo total que deben pausarse todos los hilos (tiempo de + *stop-the-world*). +5. Cantidad de memoria usada antes de la recolección. +6. Cantidad de memoria libre antes de la recolección. +7. Cantidad de memoria desperdiciada antes de la recolección. +8. Cantidad de memoria utilizada por el mismo recolector antes de la + recolección (para sus estructuras internas). +9. Cantidad de memoria usada después de la recolección. +10. Cantidad de memoria libre después de la recolección. +11. Cantidad de memoria desperdiciada [#solwaste]_ después de la recolección. +12. Cantidad de memoria utilizada por el mismo recolector después de la + recolección. + +Si bien el punto 4 parece ser el más importante para un programa que necesita +baja latencia, dado el *lock* global del recolector, el punto 2 es +probablemente el valor más significativo en este aspecto, dado que, a menos +que el programa en cuestión utilice muy poco el recolector en distintos hilos, +los hilos se verán pausados de todas formas cuando necesiten utilizar el +recolector. + +.. [#solcollect] Esto es en el sentido más amplio posible. Por ejemplo, cuando + se utiliza marcado concurrente (ver :ref:`sol_fork`), se guarda esta + información incluso si ya hay una recolección activa, pero el tiempo de + pausa de los hilos será -1 para indicar que en realidad nunca fueron + pausados. + +.. [#solwaste] Memoria *desperdiciada* se refiere a memoria que directamente + no puede utilizarse debido a la fragmentación. Si por ejemplo, se piden 65 + bytes de memoria, dada la organización del *heap* en bloques (ver + :ref:`dgc_org`), el recolector asignará un bloque de 128 bytes, por lo + tanto 63 bytes quedarán desperdiciados. + + +.. _sol_precise: + +Marcado preciso +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +En paralelo con este trabajo, David Simcha comienza a explorar la posibilidad +de agregar precisión parcial al recolector, generando información sobre la +ubicación de los punteros para cada tipo [DBZ3463]_. Su trabajo se limita +a una implementación a nivel biblioteca de usuario y sobre `D 2.0`_. +Desafortunadamente su trabajo pasa desapercibido por un buen tiempo. + +Luego Vincent Lang (mejor conocido como *wm4* en la comunidad de D_), retoma +este trabajo, pero modificando el compilador DMD_ y trabajando con `D 1.0`_ +y Tango_, al igual que este trabajo. Dado el objetivo y entorno común, se abre +la posibilidad de adaptar los cambios de Vincent Lang a este trabajo, +utilizando una versión modificada de DMD_ (dado que los cambios aún no son +integrados al compilador oficial). + +Información de tipos provista por el compilador +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Con éstas modificaciones, el compilador en cada asignación le pasa al +recolector información sobre los punteros del tipo para el cual se pide la +memoria. Esta información se pasa como un puntero a un arreglo de palabras con +la estructura mostrada en la figura :vref:`fig:sol-ptrmap` y que se describe +a continuación. + +.. fig:: fig:sol-ptrmap + + Estructura de la información de tipos provista por el compilador. + + .. aafig:: + :scale: 110 + + /----- ptrmap + | + V + +-------------+----------------------------+----------------------------+ + | "Tamaño en" | "Bits indicando si la" | "Bits indicando si" | + | "cantidad" | "palabra en una posición" | "la palabra en una" | + | "de" | "debe escanearse como" | "posición es" | + | "palabras" | "si fuera un puntero" | "un puntero" | + +-------------+----------------------------+----------------------------+ + + | | | | + +----- 1 -----+------- ceil(N/BPW) --------+------- ceil(N/BPW) --------+ + | | | | + +* La primera palabra indica el tamaño, en **cantidad de palabras**, del tipo + para el cual se pide la memoria (:math:`N`). +* Las siguientes :math:`ceil(\frac{N}{BPW})` palabras indican, + como un conjunto de bits, qué palabras deben ser escaneadas por el + recolector como si fueran punteros (donde :math:`BPW` indica la cantidad de + bits por palabra, por ejemplo 32 para x86). +* Las siguientes :math:`ceil(\frac{N}{BPW})` palabras son otro conjunto de + bits indicando qué palabras son realmente punteros. + +Los conjuntos de bits guardan la información sobre la primera palabra en el +bit menos significativo. Dada la complejidad de la representación, se ilustra +con un ejemplo. Dada la estructura:: + + union U { + ubyte ub; + void* ptr; + } + + struct S + { + void* begin1; // 1 word + byte[size_t.sizeof * 14 + 1] bytes; // 15 words + // el compilador agrega bytes de "padding" para alinear + void* middle; // 1 word + size_t[14] ints; // 14 words + void* end1; // 1 words + // hasta acá se almacenan los bits en la primera palabra + void* begin2; // 1 words + int i; // 1 word + U u; // 1 word + S* s; // 1 word + } + +El compilador genera la estructura que se muestra en la figura +:vref:`fig:sol-ptrmap-example` (asumiendo una arquitectura de 32 bits). Como +puede apreciarse, el miembro ``u``, al ser una unión entre un puntero y un +dato común, el compilador no puede asegurar que lo que se guarda en esa +palabra sea realmente un puntero, pero indica que debe ser escaneado. El +recolector debe debe ser conservativo en este caso, y escanear esa palabra +como si fuera un puntero. + +.. fig:: fig:sol-ptrmap-example + + Ejemplo de estructura de información de tipos generada para el tipo ``S``. + + .. aafig:: + :textual: + :aspect: 55 + :scale: 110 + + /---- "bit de 'end1'" + | + | /---- "bit de 'middle'" + | | + | "bits de" | "bits de" /---- "bit de 'begin1'" + | "'ints'" | "'bytes'" | + |/------------\|/-------------\| + V| |V| |V + +----------------------------------+ + | 00000000000000000000000000100100 | "Tamaño en cantidad de palabras (36)" + +==================================+ --\ + | 10000000000000010000000000000001 | | "Bits que indican si hay que" + +----------------------------------+ | "escanear una palabra según" + | 00000000000000000000000000001101 | | "su posición" + +==================================+ --+ + | 10000000000000010000000000000001 | | "Bits que indican si hay un" + +----------------------------------+ | "puntero en la palabra según" + | 00000000000000000000000000001001 | | "su posición" + +----------------------------------+ --/ + | |AAAA + \--------------------------/|||| + "bits de relleno" |||| + |||| + "bit de 's'" |||| + | |||| + \---------------/||\---- "bit de 'begin2'" + || + /---------------/\---- "bit de 'i'" + | + "bit de 'u'" + +Si una implementación quisiera mover memoria (ver :ref:`gc_moving`), debería +mantener inmóvil a cualquier objeto que sea apuntado por una palabra de estas +características, ya que no es seguro actualizar la palabra con la nueva +posición el objeto movido. Es por esta razón que se provee desglosada la +información sobre lo que hay que escanear, y lo que es realmente un puntero +(que puede ser actualizado de forma segura por el recolector de ser +necesario). + +Implementación en el recolector +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +La implementación está basada en la idea original de David Simcha, pero +partiendo de la implementación de Vincent Lang (que está basada en Tango_) +y consiste en almacenar el puntero a la estructura con la descripción del tipo +generada por el compilador al final del bloque de datos. Este puntero solo se +almacena si el bloque solicitado no tiene el atributo ``NO_SCAN``, dado que en +ese caso no hace falta directamente escanear ninguna palabra del bloque. + +En la figura :vref:`fig:sol-ptrmap-blk` se puede ver, como continuación del +ejemplo anterior, como se almacenaría en memoria un objeto del tipo ``S``. + +.. fig:: fig:sol-ptrmap-blk + + Ejemplo de bloque que almacena un objeto de tipo ``S`` con información de + tipo. + + .. aafig:: + :scale: 110 + + | | + +------------------------ 256 bytes -----------------------------+ + | | + + +----------------------------------+-----------------------+-----+ + | | | | + | Objeto | Desperdicio | Ptr | + | | | | + +----------------------------------+-----------------------+-----+ + + | | | | + +------------ 144 bytes -----------+------ 108 bytes ------+- 4 -+ + | | | | + +Un problema evidente de este esquema es que si el tamaño de un objeto se +aproxima mucho al tamaño de bloque (difiere en menos de una palabra), el +objeto ocupará el doble de memoria. + +El algoritmo de marcado se cambia de la siguiente forma:: + + // Agregado + global conservative_scan = [1, 1, 0] + + // Agregado + function must_scan_word(pos, bits) is + return bits[pos / BITS_PER_WORD] & (1 << (pos % BITS_PER_WORD)) + + function mark_range(begin, end, ptrmap) is // Modificado + number_of_words_in_type = ptrmap[0] // Agregado + size_t* scan_bits = ptrmap + 1 // Agregado + pointer = begin + while pointer < end + foreach word_pos in 0..number_of_words_in_type // + if not must_scan_word(n, scan_bits) // Agregado + continue // + [pool, page, block] = find_block(pointer) + if block is not null and block.mark is false + block.mark = true + if block.noscan is false + block.scan = true + global more_to_scan = true + pointer += number_of_words_in_type // Modificado + + function mark_heap() is + while global more_to_scan + global more_to_scan = false + foreach pool in heap + foreach page in pool + if page.block_size <= PAGE // saltea FREE y CONTINUATION + foreach block in page + if block.scan is true + block.scan = false + if page.block_size is PAGE // obj grande // + begin = cast(byte*) page // + end = find_big_object_end(pool, page) // + else // objeto pequeño // + begin = block.begin // + end = block.end // Modificado + ptrmap = global conservative_scan // + if NO_SCAN not in block.attrs // + end -= size_t.sizeof // + ptrmap = cast(size_t*) *end // + mark_range(begin, end, ptrmap) // + + function mark_static_data() is + mark_range(static_data.begin, static_data.end, + global conservative_scan) // Agregado + + function mark_stacks() is + foreach thread in threads + mark_range(thread.stack.begin, thread.stack.end, + global conservative_scan) // Agregado + + function mark_user_roots() is + foreach root_range in user_roots + mark_range(root_range.begin, root_range.end, + global conservative_scan) // Agregado + +Las funciones de asignación de memoria se modifican de forma similar, para +guardar el puntero a la información de tipos. Esta implementación utiliza solo +la información sobre que palabras hay que tratar como punteros (deben ser +escaneadas); la información sobre qué palabras son efectivamente punteros no +se utiliza ya que no se mueven celdas. + +El algoritmo sigue siendo correcto, puesto que solamente se dejan de escanear +palabras que el compilador sabe que no pueden ser punteros. Si bien el +lenguaje permite almacenar punteros en una variable que no lo sea, esto es +comportamiento indefinido por lo tanto un programa que lo hace no es +considerado correcto, por lo cual el recolector tampoco debe ser correcto en +esas circunstancias. + +Cabe destacar que la información de tipos solo se provee para objetos +almacenados en el *heap*, el área de memoria estática, registros del +procesador y la pila de todos los hilos siguen siendo escaneados de forma +completamente conservativa. Se puede forzar el escaneo puramente conservativo +utilizando la opción ``conservative`` (ver :ref:`sol_config_spec`). + + +.. _sol_fork: + +Marcado concurrente +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Finalmente se procede al objetivo primario de este trabajo, hacer que la fase +más costosa del recolector (el marcado) pueda correr de manera concurrente con +el *mutator*, con el objeto principal de disminuir el tiempo de pausa. + +Cabe aclarar, una vez más, que si bien los recolectores concurrentes buscan +disminuir solo el tiempo de *stop-the-world*, en este caso es también +fundamental disminuir el tiempo máximo que está tomado el *lock* global, dado +que ese tiempo puede convertirse en una pausa para todos los threads que +requieran servicios del recolector. + +Se decide basar la implementación en el *paper* "Non-intrusive Cloning Garbage +Collector with Stock Operating System Support" [RODR97]_ por las siguientes +razones principales: + +* Su implementación encaja de forma bastante natural con el diseño del + recolector actual, por lo que requiere pocos cambios, lo que hace más + factible su aceptación. +* Está basado en la llamada al sistema :manpage:`fork(2)`, que no solo está + muy bien soportada (y de manera muy eficiente) en Linux_, debe estar + soportada en cualquier sistema operativo :term:`POSIX`. +* No necesita instrumentar el código incluyendo barreras de memoria para + informar al recolector cuando cambia el grafo de conectividad. Este es un + aspecto fundamental, dada la filosofía de D_ de no pagar el precio de cosas + que no se usan. La penalización en la eficiencia solo se paga cuando corre + el recolector. Este aspecto también es crítico a la hora de evaluar la + aceptación de la solución por parte de la comunidad. +* Dada su sencillez general, no es difícil ofrecer el algoritmo concurrente + como una opción, de manera que el usuario pueda optar por usarlo o no. + +Llamada al sistema *fork* +^^^^^^^^^^^^^^^^^^^^^^^^^ +El término *fork* proviene del inglés y significa *tenedor* de manera textual, +pero se lo utiliza como analogía de una bifurcación. La operación crea una +copia (llamada *hijo*) del proceso que la ejecuta (llamado *padre*). + +El punto más importante es que se crea un espacio de direcciones de memoria +separado para el proceso hijo y una copia exacta de todos los segmentos de +memoria del proceso padre. Es por esto que cualquier modificación que se haga +en el proceso padre, no se refleja en el proceso hijo (y viceversa), a menos +que la memoria sea compartida entre los procesos de forma explícita. + +Esto, sin embargo, no significa que la memoria física sea realmente duplicada; +en general todos los sistemas operativos modernos (como Linux_) utilizan una +técnica llamada *copy-on-write* (*copiar-al-escribir* en castellano) que +retrasa la copia de memoria hasta que alguno de los dos procesos escribe en un +segmento. Recién en ese momento el sistema operativo realiza la copia de **ese +segmento solamente**. Es por esto que la operación puede ser muy eficiente, +y la copia de memoria es proporcional a la cantidad de cambios que hayan. + +:manpage:`fork(2)` tiene otra propiedad importante de mencionar: detiene todos +los hilos de ejecución en el proceso hijo. Es decir, el proceso hijo se crear +con un solo hilo (el hilo que ejecutó la operación de :manpage:`fork(2)`). + +Algoritmo +^^^^^^^^^ +Lo que propone el algoritmo es muy sencillo, utilizar la llamada al sistema +:manpage:`fork(2)` para crear una *fotografía* de la memoria del proceso en un +nuevo proceso. En el proceso padre sigue corriendo el *mutator* y en el +proceso hijo se corre la fase de marcado. El *mutator* puede modificar el +grafo de conectividad pero los cambios quedan aislados el hijo (el marcado), +que tiene una visión consistente e inmutable de la memoria. El sistema +operativo duplica las páginas que modifica el padre bajo demanda, por lo tanto +la cantidad de memoria física realmente copiada es proporcional a la cantidad +y dispersión de los cambios que haga el *mutator*. + +La corrección del algoritmo se mantiene gracias a que la siguiente invariante +se preserva: + + Cuando una celda se convierte en basura, permanece como basura hasta ser + reciclada por el recolector. + +Es decir, el *mutator* no puede *resucitar* una celda *muerta* y esta +invariante se mantiene al correr la fase de marcado sobre una vista inmutable +de la memoria. El único efecto introducido es que el algoritmo toma una +aproximación más conservativa. Es decir, lo que sí puede pasar es que una +celda que pasó a estar *muerta* una vez que la fase de marcado se inició, pero +antes de que ésta termine, la celda no se reciclará hasta la próxima +recolección, dado que este algoritmo no incluye una comunicación entre +*mutator* y recolector para notificar cambios en el grafo de conectividad. +Pero esto no afecta la corrección del algoritmo, ya que un recolector es +correcto cuando nunca recicla celdas *vivas*. + +La única comunicación necesaria entre el *mutator* y el recolector son los +bits de marcado (ver :ref:`dgc_impl`), dado que la fase de barrido debe correr +en el proceso padre. No es necesaria ningún tipo de sincronización entre +*mutator* y recolector más allá de que uno espera a que el otro finalice. + +Además de almacenar el conjunto de bits ``mark`` en memoria compartida entre +el proceso padre e hijo (necesario para la fase de barrido), las +modificaciones necesarias para hacer la fase de marcado concurrente son las +siguientes [#solforkerr]_:: + + function collect() is + stop_the_world() + child_pid = fork() + fflush(null) // evita que se duplique la salida de los FILE* abiertos + if child_pid is 0 // proceso hijo + mark_phase() + exit(0) // termina el proceso hijo + // proceso padre + start_the_world() + wait(child_pid) + sweep() + + function mark_phase() is + global more_to_scan = false + // Borrado: stop_the_world() + clear_mark_scan_bits() + mark_free_lists() + mark_static_data() + push_registers_into_stack() + thread_self.stack.end = get_stack_top() + mark_stacks() + pop_registers_from_stack() + mark_user_roots() + mark_heap() + // Borrado: start_the_world() + +Como se puede observar, el cambio es extremadamente sencillo. Sigue siendo +necesario un tiempo mínimo de pausa (básicamente el tiempo que tarda la +llamada al sistema operativo :manpage:`fork(2)`) para guardar una vista +consistente de los registros del CPU y *stacks* de los hilos. Si bien el +conjunto de bits ``mark`` es compartido por el proceso padre e hijo dado que +es necesario para *comunicar* las fases de marcado y barrido, cabe notar que +nunca son utilizados de forma concurrente (la fase de barrido espera que la +fase de marcado termine antes de usar dichos bits), por lo tanto no necesitan +ningún tipo de sincronización y nunca habrá más de una recolección en proceso +debido al *lock* global del recolector. + +A pesar de que con estos cambios el recolector técnicamente corre de forma +concurrente, se puede apreciar que para un programa con un solo hilo el +tiempo máximo de pausa seguirá siendo muy grande, incluso más grande que antes +dado el trabajo extra que impone crear un nuevo proceso y duplicar las páginas +de memoria modificadas. Lo mismo le pasará a cualquier hilo que necesite hacer +uso del recolector mientras hay una recolección en proceso, debido al *lock* +global. + +Para bajar este tiempo de pausa se experimenta con dos nuevas mejoras, que se +describen a continuación, cuyo objetivo es correr la fase de marcado de forma +concurrente a **todos** los hilos, incluyendo el hilo que la disparó. + +.. [#solforkerr] Se omite el manejo de errores y la activación/desactivación + del marcado concurrente a través de opciones del recolector para facilitar + la comprensión del algoritmo y los cambios realizados. Si devuelve con + error la llamada a ``fork()`` o ``waitpid()``, se vuelve al esquema + *stop-the-world* como si se hubiera desactivado el marcado concurrente + utilizando la opción del recolector ``fork=0``. + + +.. _sol_eager_alloc: + +Creación ansiosa de *pools* +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Esta mejora, que puede ser controlada a través de la opción ``eager_alloc`` +(ver :ref:`sol_config_spec`), consiste en crear un nuevo *pool* cuando un +pedido de memoria no puede ser satisfecho, justo después de lanzar la +recolección. Esto permite al recolector satisfacer la petición de memoria +inmediatamente, corriendo la fase de marcado de forma realmente concurrente, +incluso para programas con un solo hilo o programas cuyos hilos usan +frecuentemente servicios del recolector. El precio a pagar es un mayor uso de +memoria de forma temporal (y el trabajo extra de crear y eliminar *pools* más +frecuentemente), pero es esperable que el tiempo máximo de pausa **real** se +vea drásticamente disminuido. + +A simple vista las modificaciones necesarias para su implementación parecieran +ser las siguientes:: + + // Agregado + global mark_pid = 0 + + // Agregado + function mark_is_running() is + return global mark_pid != 0 + + function collect() is + if mark_is_running() // + finished = try_wait(global mark_pid) // + if finished // Agregado + mark_pid = 0 // + sweep() // + return // + stop_the_world() + child_pid = fork() + fflush(null) + if child_pid is 0 // proceso hijo + mark_phase() + exit(0) + // proceso padre + start_the_world() + // Borrado: wait(child_pid) + global mark_pid = child_pid + +Sin embargo con sólo estas modificaciones el algoritmo deja de ser correcto, +ya que tres cosas problemáticas pueden suceder: + +1. Puede llamarse a la función ``minimize()`` mientras hay una fase de marcado + corriendo en paralelo. Esto puede provocar que se libere un *pool* mientras + se lo está usando en la fase de marcado, lo que no sería un problema + (porque el proceso de marcado tiene una copia) si no fuera porque los bits + de marcado, que son compartidos por los procesos, se liberan con el *pool*. +2. Si un bloque libre es asignado después de que la fase de marcado comienza, + pero antes de que termine, ese bloque será barrido dado la función + ``rebuild_free_lists()`` puede reciclar páginas si todos sus bloques tienen + el bit ``freebits`` activo (ver :ref:`dgc_algo_sweep`). +3. El *pool* creado ansiosamente, tendrá sus bits de marcado sin activar, por + lo que en la fase de barrido será interpretado como memoria libre, incluso + cuando puedan estar siendo utilizados por el *mutator*. + +El punto 1 sencillamente hace que el programa finalice con una violación de +segmento (en el mejor caso) y 2 y 3 pueden desembocar en la liberación de una +celda alcanzable por el *mutator*. + +El punto 1 se resuelve a través de la siguiente modificación:: + + function minimize() is + if mark_is_running() // Agregado + return // + for pool in heap + all_free = true + for page in pool + if page.block_size is not FREE + all_free = false + break + if all_free is true + free(pool.pages) + free(pool) + heap.remove(pool) + +La resolución del punto 2 es un poco más laboriosa, ya que hay que mantener +actualizado los ``freebits``, de forma que las celdas asignadas después de +empezar la fase de marcado no sean barridas por tener ese bit activo:: + + function new_big(size) is + number_of_pages = ceil(size / PAGE_SIZE) + pages = find_pages(number_of_pages) + if pages is null + collect() + pages = find_pages(number_of_pages) + if pages is null + minimize() + pool = new_pool(number_of_pages) + if pool is null + return null + pages = assign_pages(pool, number_of_pages) + pages[0].block.free = true // Agregado + pages[0].block_size = PAGE + foreach page in pages[1..end] + page.block_size = CONTINUATION + return pages[0] + + function assign_page(block_size) is + foreach pool in heap + foreach page in pool + if page.block_size is FREE + page.block_size = block_size + foreach block in page + block.free = true // Agregado + free_lists[page.block_size].link(block) + + function mark_phase() is + global more_to_scan = false + // Borrado: clear_mark_scan_bits() + // Borrado: mark_free_lists() + clear_scan_bits() // Agregado + mark_free() // + mark_static_data() + push_registers_into_stack() + thread_self.stack.end = get_stack_top() + mark_stacks() + pop_registers_from_stack() + mark_user_roots() + mark_heap() + + // Agregado + function clear_scan_bits() is + // La implementación real limpia los bits en bloques de forma eficiente + foreach pool in heap + foreach page in pool + foreach block in page + block.scan = false + + // Agregado + function mark_free() is + // La implementación real copia los bits en bloques de forma eficiente + foreach pool in heap + foreach page in pool + foreach block in page + block.mark = block.free + + function free_big_object(pool, page) is + pool_end = cast(byte*) pool.pages + (PAGE_SIZE * pool.number_of_pages) + do + page.block_size = FREE + page.block.free = true // Agregado + page = cast(byte*) page + PAGE_SIZE + while page < pool_end and page.block_size is CONTINUATION + + function new(size, attrs) is + block_size = find_block_size(size) + if block_size < PAGE + block = new_small(block_size) + else + block = new_big(size) + if block is null + throw out_of_memory + if final in attrs + block.final = true + if noscan in attrs + block.noscan = true + block.free = false // Agregado + return cast(void*) block + + funciones new_pool(number_of_pages = 1) is + pool = alloc(pool.sizeof) + if pool is null + return null + pool.number_of_pages = number_of_pages + pool.pages = alloc(number_of_pages * PAGE_SIZE) + if pool.pages is null + free(pool) + return null + heap.add(pool) + foreach page in pool + page.block_size = FREE + foreach block in page // + block.free = true // Agregado + block.mark = true // + return pool + +Finalmente, el punto número tres puede ser solucionado con el siguiente +pequeño cambio:: + + funciones new_pool(number_of_pages = 1) is + pool = alloc(pool.sizeof) + if pool is null + return null + pool.number_of_pages = number_of_pages + pool.pages = alloc(number_of_pages * PAGE_SIZE) + if pool.pages is null + free(pool) + return null + heap.add(pool) + foreach page in pool + page.block_size = FREE + foreach block in page // Agregado + block.mark = true // + return pool + +La solución es conservativa porque, por un lado evita la liberación de *pools* +mientras haya una recolección en curso (lo que puede hacer que el consumo de +memoria sea un poco mayor al requerido) y por otro asegura que, como se +mencionó anteriormente, los cambios hechos al grafo de conectividad luego de +iniciar la fase de marcado y antes de que ésta termine, no serán detectados +por el recolector hasta la próxima recolección (marcar todos los bloques de +un nuevo *pool* como el bit ``mark`` asegura que que la memoria no sea +recolectada por la fase de barrido cuando termine el marcado). + +Estas modificaciones son las que hacen que el algoritmo siga siendo correcto, +asegurando que no se van a liberar celdas *vivas* (a expensas de diferir la +liberación de algunas celdas *muertas* por algún tiempo). + + +.. _sol_early_collect: + +Recolección temprana +^^^^^^^^^^^^^^^^^^^^ +Esta mejora, que puede ser controlada a través de la opción ``early_collect`` +(ver :ref:`sol_config_spec`), consiste en lanzar una recolección preventiva, +antes de que una petición de memoria falle. El momento en que se lanza la +recolección es controlado por la opción ``min_free`` (ver :ref:`sol_ocup`). + +De esta forma también puede correr de forma realmente concurrente el *mutator* +y el recolector, al menos hasta que se acabe la memoria, en cuyo caso, a menos +que la opción ``eager_alloc`` (ver :ref:`sol_eager_alloc`) también esté +activada, se deberá esperar a que la fase de marcado termine para recuperar +memoria en la fase de barrido. + +Para facilitar la comprensión de esta mejora se muestran sólo los cambios +necesarios si no se utiliza la opción ``eager_alloc``:: + + function collect(early = false) is // Modificado + if mark_is_running() + finished = try_wait(global mark_pid) + if finished + mark_pid = 0 + sweep() + return // + else if early // Agregado + return // + stop_the_world() + child_pid = fork() + fflush(null) + if child_pid is 0 // proceso hijo + mark_phase() + exit(0) + // proceso padre + start_the_world() + if early // + global mark_pid = child_pid // + else // Agregado + wait(child_pid) // + sweep() // + + // Agregado + function early_collect() is + if not collect_in_progress() and (percent_free < min_free) + collect(true) + + function new(size, attrs) is + block_size = find_block_size(size) + if block_size < PAGE + block = new_small(block_size) + else + block = new_big(size) + if block is null + throw out_of_memory + if final in attrs + block.final = true + if noscan in attrs + block.noscan = true + early_collect() // Agregado + return cast(void*) block + +Es de esperarse que cuando no está activa la opción ``eager_alloc`` por un +lado el tiempo de pausa máximo no sea tan chico como cuando sí lo está (dado +que si la recolección no se lanza de forma suficientemente temprana se va +a tener que esperar que la fase de marcado termine), y por otro que se hagan +más recolecciones de lo necesario (cuando pasa lo contrario, se recolecta más +temprano de lo que se debería). Sin embargo, también es de esperarse que el +consumo de memoria sea un poco menor que al usar la opción ``eager_alloc``. + +En cuanto a la corrección del algoritmo, éste solamente presenta los problemas +número 1 y 2 mencionados en :ref:`sol_eager_alloc`, dado que jamás se crean +nuevos *pools* y la solución es la ya presentada, por lo tanto el algoritmo +sigue siendo correcto con los cuidados pertinentes. -- 2.43.0