From 362e98a3352eda76c83ba855b5bee64611cee236 Mon Sep 17 00:00:00 2001 From: Libby Shoop Date: Tue, 10 Jul 2012 10:15:10 -0500 Subject: [PATCH] Initial add of madules and images --- images/CSInParallel.png | Bin 0 -> 24184 bytes images/CSInParallel100wide.png | Bin 0 -> 7664 bytes images/CSInParallel130wide.png | Bin 0 -> 10780 bytes images/CSInParallel200wide.png | Bin 0 -> 17959 bytes images/CSInParallel65wide.png | Bin 0 -> 4326 bytes images/CSInParallel80wide.png | Bin 0 -> 5739 bytes images/CSInParallel90wide.png | Bin 0 -> 6725 bytes modules/CDS_cpp/Makefile | 153 ++ .../introduction_stl_containers.html | 1715 +++++++++++++++++ .../introduction_stl_containers.rst | 240 +++ .../source/WebCrawler/CDS_crawler_lab_cpp.rst | 383 ++++ modules/CDS_cpp/source/WebCrawler/cds.tar.gz | Bin 0 -> 5426 bytes modules/CDS_cpp/source/conf.py | 242 +++ modules/CDS_cpp/source/index.rst | 27 + modules/MulticoreProgramming/Makefile | 153 ++ modules/MulticoreProgramming/source/conf.py | 248 +++ .../correctOpenMP/FixingOurOpenMPcode.rst | 112 ++ .../OpenMPNextstepsFixingourOpenMPcode.html | 1 + .../OpenMPNextstepsFixingourOpenMPcode.zip | Bin 0 -> 2560 bytes .../source/correctOpenMP/trap-omp-working.C | 46 + modules/MulticoreProgramming/source/index.rst | 26 + .../introOpenMP/GettingstartedwithOpenMP.rst | 221 +++ .../source/introOpenMP/images/image00.png | Bin 0 -> 2557 bytes .../source/introOpenMP/trap-omp-notworking.C | 46 + .../source/introOpenMP/trap-omp-unfinished.C | 44 + .../source/introOpenMP/trap-omp.C | 46 + .../OpenMPtimingonMTLandscalability.zip | Bin 0 -> 4760 bytes .../TimingonMTLandscalability.html | 1 + .../TimingOnMTLandscalability.rst | 300 +++ .../timingAndScalability/trap-omp-timed.C | 60 + 30 files changed, 4064 insertions(+) create mode 100644 images/CSInParallel.png create mode 100644 images/CSInParallel100wide.png create mode 100644 images/CSInParallel130wide.png create mode 100644 images/CSInParallel200wide.png create mode 100644 images/CSInParallel65wide.png create mode 100644 images/CSInParallel80wide.png create mode 100644 images/CSInParallel90wide.png create mode 100644 modules/CDS_cpp/Makefile create mode 100644 modules/CDS_cpp/source/IntroSTLContainers/introduction_stl_containers.html create mode 100644 modules/CDS_cpp/source/IntroSTLContainers/introduction_stl_containers.rst create mode 100644 modules/CDS_cpp/source/WebCrawler/CDS_crawler_lab_cpp.rst create mode 100644 modules/CDS_cpp/source/WebCrawler/cds.tar.gz create mode 100644 modules/CDS_cpp/source/conf.py create mode 100644 modules/CDS_cpp/source/index.rst create mode 100644 modules/MulticoreProgramming/Makefile create mode 100644 modules/MulticoreProgramming/source/conf.py create mode 100644 modules/MulticoreProgramming/source/correctOpenMP/FixingOurOpenMPcode.rst create mode 100755 modules/MulticoreProgramming/source/correctOpenMP/originalHTMLFromGoogleDoc/OpenMPNextstepsFixingourOpenMPcode.html create mode 100644 modules/MulticoreProgramming/source/correctOpenMP/originalHTMLFromGoogleDoc/OpenMPNextstepsFixingourOpenMPcode.zip create mode 100644 modules/MulticoreProgramming/source/correctOpenMP/trap-omp-working.C create mode 100644 modules/MulticoreProgramming/source/index.rst create mode 100644 modules/MulticoreProgramming/source/introOpenMP/GettingstartedwithOpenMP.rst create mode 100755 modules/MulticoreProgramming/source/introOpenMP/images/image00.png create mode 100644 modules/MulticoreProgramming/source/introOpenMP/trap-omp-notworking.C create mode 100644 modules/MulticoreProgramming/source/introOpenMP/trap-omp-unfinished.C create mode 100644 modules/MulticoreProgramming/source/introOpenMP/trap-omp.C create mode 100644 modules/MulticoreProgramming/source/timingAndScalability/OriginalHTMLFromGoogleDoc/OpenMPtimingonMTLandscalability.zip create mode 100755 modules/MulticoreProgramming/source/timingAndScalability/OriginalHTMLFromGoogleDoc/TimingonMTLandscalability.html create mode 100644 modules/MulticoreProgramming/source/timingAndScalability/TimingOnMTLandscalability.rst create mode 100644 modules/MulticoreProgramming/source/timingAndScalability/trap-omp-timed.C diff --git a/images/CSInParallel.png b/images/CSInParallel.png new file mode 100644 index 0000000000000000000000000000000000000000..93d824b9386f8e7b2bf94c1980d74be944e152f9 GIT binary patch literal 24184 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 z%0A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 JvP#?}_&*O>+6Djs literal 0 HcmV?d00001 diff --git a/images/CSInParallel200wide.png b/images/CSInParallel200wide.png new file mode 100644 index 0000000000000000000000000000000000000000..be5b934fac09990cd2be23de647f60ee4ffb6855 GIT binary patch literal 17959 zcmXt;=R zp8z6V4rO5)ut#5Qf{)tK<6)}C6DB2zIB9R`Cymu;8Z6k{9@AQDo6fNAE~ZMar#FG) znZ|U4;1{t&+4N<`nhX486y0y{AT0)c!KMQJ_k5qTUpSyiL)TS4^vlxia^7M*EM#x# z&60l@ta4>&>_*^C)WH)c((@Nk3gO#v0QFNBzbzwj1J#e|`A1TTtB#iMrR^xb$jKB*GsmL&(C9-;c{xl1#fd@*W6h#H$u}7Uo>}kmR|mS zHTub{`U{sq+iI;+!_ntM2X(+VzZKy~{d5DHvIRX{e7lM{Hsy<@^4==V+R-xCiE@V? z@AqW5A0s(xC4~^=+i6@Q@dxbEv+xlZ3Hu83je9lg@>G)p5QOo0u zwa&z+nue^s{IXc`G??T!er>SwG?#va^6>Ih5$JJdaWn~s zp#S?L_iiByky7e4gYNg69kqO4c93^$p#=YiHC5+xo( z?p?|KD;FqO72+5Z8z&Mkj>AnOPvnZWS5k#J>?9i4kmTjSqfV9b>&B|-pEGVr4~5UQ ze`hR=^Ky59GzwKvxs(VFR>4jCk7|BIc{qEf+~-+1&mnRohrYrLj1Ndv zPJjI6MGR9 zdG|+90@xo^NI~j7t+fS{8gv{gEg1qyC^Th8J$^?Q0@CgVLnOpTq|9d9I7T=S{jpsI zTr)z2jpB|Iqm6mGi!AtE+D4k}ntOgj#ERz_Y7=S9tpwZQRbeP73^l~yV}c#;mAffY zqoT3j*Gow+(9pQUUQpY#!$9CE@?(M|wz)Vm4Zsw{VvH48!TA|VGqCX6d4q!sqC}y! zE52E!0|R@ZB{?dD7>VNQMjS}e3G&SL6I#7G#{M1f_I<75;lzAzDpS_RC3&I464zbR z>u*sM!4PH{SfL*LT;d$Dh`7Nu`I$I*sA;<6hw+g2aLJ|fEbGmys1k^I9v+?PGwP=C z#x>Yi!X=*=I*-d51P5d0k$0V$TaabM5Ggm|pUDG+31JpS?{VVh)VhCn1#_amM;kz< zmkKT{H4oU*^o~gWJIfwjkQ>g92*M|i$XkKIp&(!Q5

dpBW{r{sl3h&e^K(E)>3# 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 UWwnY?Z2$lO07*qoM6N<$f;i6|ZU6uP literal 0 HcmV?d00001 diff --git a/images/CSInParallel80wide.png b/images/CSInParallel80wide.png new file mode 100644 index 0000000000000000000000000000000000000000..96f87afab482ac57c4b6318b4e061a90a0e2d437 GIT binary patch literal 5739 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 literal 0 HcmV?d00001 diff --git a/images/CSInParallel90wide.png b/images/CSInParallel90wide.png new file mode 100644 index 0000000000000000000000000000000000000000..e236afb9710f0a4c99bb3ee3653da7b7c5c09d2a GIT binary patch literal 6725 zcmV-L8oK3)P)4Tx04R}-l+Q~PVHn51vyB)iL11Qkh@nGBs7w}RMCGo(*tBg~f0Po$?CiU{ zle4qa%26U(f|ru*d1uDm5$(qdGxK?$AK&MB z-{F0M8d)(-JtP5n!?BWmQR({Vm{j`_Asj&+!l+YJ+l(X<1E9fUv1@F;hrqupt$X|b zI_lE4ng@jaKh%lTb}(f=ak3uu6-!kMZ8FSKs7BM z|C+c%%_(W1MkH>@24YeH&g(_h@8=*r^~@L^r0;R+=`OQ-d=_TXN_RhT8}a8f>+a#2 z#Pb=gH%8n{&sxUn9rjo_p*gW3k3%Dd9v|?z$wwEp4JbjhwM#!rpOV)g4O?IHuKzA8qHe6NWHQ4?5 zW?+>pP^lFuS83d>RC0Yby(58Yf7(5YU7B8Os@7T+7jCT7FHP^*tHQ2TvC67;&Z}|3 zd?p?hPl_?ILp%pbyd-vt7sYOxofiix8N;NaVZv3ip-`5AN%ERI{+SmG@2t6_p^q8N z(o~6`|AbY@XgUiV(SNxAbMSk`qp>3J!UhhXuG%(|?j0gDE>~^N72v`*x)0t~ZOwEZ z4m|-D&ZtGJ}8;WLk96sqPn3I;4%{fxLET5GkbR{t#`+_*~}`j zbQ|otQ(oz5b7aB=KK~#90?VNj@RMnO5zx$jz_2}v@nts#JO(TyEc2H0e z#YY2TkQkL*1B@}b8gaZvGx<0lCT6^o&jjQNRbAcnJg3f{KlZ8WYM>itCQyIOtk36sy1HxceST~0wbx#It+iW7Dfw@@ z`lJp2l@K8GYX{{DNC+1~h+Mw8EnXBO$WhTS4TJ}{0hqC5iO_|({6JToga`n$ z6L6jSHMf|A5JG%k)FN3%CzFMuVKEK&#dW~SREnQ0UrsGx2q9co1@V>B9N?G0U#%0O z6on~w!d`)n82`|f;MH%284$~$7Xqt?Yl1aMGoa$=+^1k~R19)@EmIa^Gkv>4E9E&{7gz>B_%9=zQL16p~8~k}}l>|4%}26pa5m%mn;^ z=Rys-MtG!@GDd_on8eBB3)FIhgUQIoG@_s%c|>+6lWYJOKc1VXO=ApdX!7OM(}!$mK?_~LNhia>21V}Y7@JU$^7i&X)m*R5MO>_q+@95&$-0s2gsZKyH4 zA(^XaEFp=?l6s$->r67DQu znr++VQcA<^cK7SL-t^2f&+IvP@Zg~tGiHQwl)wQ|3*2Sf_7q^W+wB%Weq*OexbekG8kBfBV^I zpDhNA6)RR~Qp!0}%I#9hq;1=_X_`^XvJz5CY1?+nG|hvJjg3o7N=ga<_l20cpk9KR zgn>VUZvvB1%T-*CpR4uo7yLPYgXFt36W~6S@_EzWUv?8Ya)h7|v;dTsQ-N~v+<S7DnwmaNCX-m=}S{kp3V^8)PfcXeJ=3TK?%xq7$|d;8`Ak4 z3yF)-lv3#6(bIPNvZmvyr=*!mi4K5LHGmgzuUN6d1w0Sj4$3ARJ$iJ{+i$7e%8{v#6-(E5HH6FiM3Gv(n>tbaXWT^rt`F+}751{)HD_m^Ed} zlpCVaDDik)c)i{_)DpCW5UI2oi9<+2P=n$zWSBBgHgSllxn`822Z@QmM?vPyp`#{W zUg@zq0U$(%88KtVj1r(e-5?f=ga8yXtIM~@!u zGz=sE`0?Y(d+)tBsA<|5fQpKW+UK8ten~VMZ3cQzpFX{B=gysNCr+FQ?%ut-lR*Jy zC<>NG|=GfBc*7?(R{0_U!RF{rb0V-5Of9Y}vo$<>h@#*Y!fb-#>cNq)D@E+n!-r zR&Oj8YYPMdO@Tn*!_})-pF?dZ7RpqCdZ{KH>=u{~LmIg>9VQ+OT-cbHLofkXBxoU| zWHdlD$^eRC>2|x_WS#3R%Zeuwi6jt4c>#T1uQ$}#*cf6^>&$oFc_-4|-u@2-1qF$` zyu3MHuXm!`?GEU=?s2=_6>hh?A`l4NSXEV3lSm}aJonslXSu)}c$E88Ab~E3;w;bq zxRm|`)heHMb;_$?I*~KH1XJ()QJ_el_&=+ix3HRaK#P-g)Q$mzI`x=(=9z_xmf0ii*Zo zR8&kZC@2^sgm8}|Vm+pM_?Ah}`3s3}5P*70(jc%Du?qD8)efdT)@l`DV0YuB#cS+i!9E?l_q?)v)r zXEaT70r>s?F9PMLiHqR~+6_|xZwxv8GE5V9uO5=8ZSrI3W-S)JiEI5JHp*AtnzD z3>56zwdIi(aR* zxnjkN)UsvE)|Ql%Ebw~0w+JD`q)C%*t*oppGYliF>-sn$L>WLl9uIHaxbc(EKmWXV z%a$!i9)JAtPQx&20LG3TyJ*?6W#yWt_2lR0-+*KHBaukxz4zWb$c3An5P2Hh3D5*b z!-}B<%CzK0qf3~I#$+tww9tT>_=NKlxNOu*6a#YsPM+j&cQTqehLo z*X#Ax8ir8_7^Z2Op-`yzz<~p&UVr`dgAEN0T|nsc>C>S%-gslpf&~lu{C@wPZnwKi z(=@*jLL`&PL|vEL)yhtr>sRlAPh&=LX*Q(G@GewN#Z-A zn0y`rpqoAP(C^47PSyr3hQ}t{z^{OBo;$}!cih1%O-+PQ%$=7~CLxm~&Ab2p`>See zYbW^q{t79jFBA%;jvqhXzjNo#zC}YBtj?$dW?(dKUVjDJWXx*@3Ll0`>@;YNnr)?Y+U5lWW zxdi&xtXXry0hA6arfsq1yeERvW)&$E;z3|y0=oVg_M%o;zccxe%dGvwopQs zP9Mu@n{dWW#|2=}i!f2P*+z)5z!QMc3_I3Bhx5Mv3z@oRlwtvH?#3d_Nj4KDePD|+ zR2hfW=vlx+Qj*xdoj+{bMjuL$VJ^Xx^Scy6NR)sCgF?$V?b-dD&nah;k_|mr5~%3y<&P^@ zve&Y(Psib*;^Y_}`)! zcoKn^UUu?F8c-7-@n1ilW4S+vjL8n9M0Mdg%|h|AX;15zjJ5C z-h_cfF0}c27zf-g!P*9UBZ`p$oF#fe^8(a?rpU4^FJQ*w@lYrfIyY|IxZYu2Q3{k> zmNm-da=9$aO4_y^I(zo)xtf}qP&$ZpL7zKtQAV7z9HGI%5k{@}VJAM@>!<=|NJ(IDalkRWs%F}@<=I7zuzBnyWOXP!QjSw z@4fev{rmT)^Nu>II&~p1-sN(Ql2RJFt|x^Mp~}k2#_sO!w}FF$0Yj%U>3hsYM-b*1 zZSWmoELD5TaE| z`4~>9HIYaJw{G3~uhD2Udecoe&8n%Xxm5^J;C8zosIIQw4g4CVv2k3CK`T!D3jBqEZ@q}JNn8bB2bsY%wEfS>@AAZIx5{C!B=c{p+boH~Vj;zY&) zw71j2U{EJ_FhdfV&k>u*1P z{CKpjtu1u;@L>S2X_{x#g7$j7qaS*9arO{=8$Aq54gRWE3!G;h**tR_} zU9g!-rR=R+x1K^R?ot>Es zZYJxT3ra$<8$<#kX9qJVCPPC2zO2uwa5x-C3Az{P>FMdQjvYG|Hcc~KGG+q+>f$OXDRHCBOAO`L zj*3Jg59qqydi3bgwpFWEb(WQtB^NJV?ECF+f4h0&#EJd+`T1WcDk`cTJ$iI?Sy|cm z;^N{7LI}<4^^U8pt$k$n?Ae>PY}umX@HTz`tc;A(m@Z2iKq8STsj`zvB)LxA1Hwtt zroc{+!8BxrS(bG?{aM#_ZO)uI*BOQZPGwUnm74GO`+r~<#t)}YpZ?cRKKbOthaY~p zs-~vqx#i23e|z4%dH1AJsjlyQ=R2>z^2#g!chja#zlz7>o#`<>9#2hSVc`UzKnS6s zWlqxRh@;J5De{Qq0*J?v9uFxYFf=VwD45fN6zSML0h&jZ7F-lgjYJ}w1A)Mw3n4}U z%$_~_?zi50t7+`mv87-9;un3ou0IP*)^&ZdX_`0f+_`goUS3|PuCDHBpU)TY`F!qq z^X5(I>FGK3?z`_cH#IfIrId;E_|a%IcIwnAFUn8Wu~kxZ|6MF=HZ2mo7b$pPxS;AU{8U=AuQ5p1c44`%ihj-mzYZTrZP zBl{W~8)Ky1U_l=MM&>ry70bv45Q`BjDxzOXN~@|UaK6jw%~T6|b&wwL*MPT0{dq_c z<1M$`65hXm|IfycA0H?vD7eezauxag{%W8)-6oMp479YgG`#Z4D}QKeYSNmTn&O8J z9s0%e>C*!qk7t^$>%QXR;sv&C*K3;QmQspDBGK2>)O7r{*IxTD7K^1*bQUQ(k${^* zT)44RA>%MHks;&)IC+vtWhLE0a9wpZWeldGG;-v5SkU2tm>UYF!RU_$@nnQ8rIbR5 z!|T_t|4CI<)rmkLaD%Sv#V(i2ZQHgTi^ZbN&CPAU|NZX|{QB3wK8ch1div?7KYac5 z*F|Ar;R3hYJxSBF0wIJhrL-cENPkO9OWV42>khv8=9{16gk+_ZQv9W8c8bkQDW!mO zS6;#D0+=&L#!XWkF$`{srSI-pTj3yI>%`(TD0iXs;a8y zY15`v`h31JA%s63k853BUDk;cC*r|iFdb9x27-qVA5PwO+ie@~zWeU|#l^*Ag%Cxy zZF`f+B%Pg|$&)8f#>3%o6eZ+t=O?82j1)oK6f=d1oa5*)m@cABBhzHRVemv*8MU|F zM#;w?)6Nwxi>pEJLjQC)2uz)-ZpnIZt}BqW%A}OG5F**u*4ES3))qx&VA6_$(>96n zuOm1iUm=9tzI}TqO3U*YTA`ngt3;hX>cG;$S*mHo$L&;c1MnV$8UGPienw-1L3T?? zZy-R$6Hjn`7GpUn?*5^ac2pJ1Fb<|IQn6_dS@%NH>3E`)jTd8;O7#B|dg_IHWSH2>ny}hiJ60^FRn}7I2?npD1i(9Dx zo1irT?`WXi2KPS-6CF$=+Qm!7rT=8@O%9XfJ#-Al@i5bv!l2n1@g+m<6h@aPAz0f&zV-AhQqz^3kmT!xZ>(R8su{cTYb%_fxYze3u2t) z45uM;EpS*{ebEwp|9!S468zx(_hHKxf~c8tk)sPaUxwrwSUnY@ufdsCg~LZK4^(?d z;O95MA^u1ogV*nd^*$~t+Z96S%a=3ym%pT_zaI~3=j|e^1oc{z5W;u@N>?e((Qv|` zhxe}cAE#s(=' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ConcurrentDataStructuresinC.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ConcurrentDataStructuresinC.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/ConcurrentDataStructuresinC" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ConcurrentDataStructuresinC" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/modules/CDS_cpp/source/IntroSTLContainers/introduction_stl_containers.html b/modules/CDS_cpp/source/IntroSTLContainers/introduction_stl_containers.html new file mode 100644 index 00000000..d9487d1e --- /dev/null +++ b/modules/CDS_cpp/source/IntroSTLContainers/introduction_stl_containers.html @@ -0,0 +1,1715 @@ + + + + + + + + + + + + + + + + + + + +

+ +

Introduction to STL containers

+ +

 

+ +

CSinParallel Project

+ +

Note: This +reading refers to topics that we will not pursue in homework, but which may +arise in general C++ coding. Such topics will be marked(Extra).

+ +

Introduction

+ +

      +The C++ Standard Template Library(STL) is a +software library of algorithms, data structures, and other features that can be +used with any pre-defined C++ type and with user-defined types (that provide +members such as a copy constructor and an assignment operator).

+ +

      +The STL uses a C++ feature called the template +to substitute types throughout a class or function definition, which enables a +programmer to avoid rewriting definitions that differ in the type being used.

+ +

      +For example, our IntArray class for homework provides +a class structure around an array of int, with error checking, default +initialization for array elements, whole-array assignment, etc. To define a +class LongArray or FloatArray, we would only need to replace the type name int +by a different type name long or float, etc. Templates make it possible to +define a class Array<T> in which a type name T is used thrroughout the +definition of that class, so that the notation Array<int> would specify a +class that behaves just like IntArray, Array<float> would specify +FloatArray, etc. Here, the angle brackets <...> are the syntax for +invoking a template, and they enclose a type name to use throughout a +definition.

+ +

      +A programmer can define her/his own templated classes. +For example, here is a complete program with a definition of a templated +structPair<T> that represents an group of two elements of the same type, +together with an example use of that template.

+ +

      +   #include <iostream>
+    using namespace std;
+   
+    template <class +T>
+    struct Pair {
+      T x;
+      T y;
+   
+      Pair(T +val1, T val2) { x = val1;  y = val2; +}
+    };
+   
+   
+    int main() {
+      +Pair<float> p(3.14, -1.0);
+   
+      cout +<< p.x << ", " << p.y << endl;
+    }
+
+

+ +

      +The STL containers are templated classes +defined in the STL that are designed to hold objects in some relationship. +Examples::

+ +

      +The array classes we have implemented in homework are +examples of containers, although STL's array-like container vector<T> has +some added useful features such as an ability to resize incrementally that our +array classes did not include.

+ +

      +Another STL container is called a queue<T>, and +contains a linear list of elements that are added at one end and removed at +another, comparable to a check-out line of customers at a grocery store. Unlike +a vector, the elements of a queue do not necessarily appear consecutively in +memory.

+ +

      +(Extra) Another example is the +map<K,T> container, which includes an operator[] that accepts +"indices" of a type K (not necessarily an integer type) and +associates values of type T with those "index" values.

+ +

      +This is not a complete list of STL containers, but +illustrates some commonly used ones. See www.cplusplus.com/reference/stl/ for +documentation of all STL containers

+ +

The STL container vector

+ +

      +STL's vector<T> is a templated class that +behaves like an "elastic array," in that its size can be changed +incrementally. For example, consider the following example program:

+ +

      +   #include <iostream>
+    using namespace std;
+    #include +<vector>
+
+    int main() {
+      +vector<int> vec(4);
+      vec[1] = +11;
+      vec.at(2) += 22;
+      +vec.push_back(33);
+      for (int i += 0;  i < vec.size(); i++)
+        +cout << vec.at(i) << endl;
+    }
+
+

+ +

      +The preprocessor directive

+ +

      +   #include <vector>
+
+

+ +

      +tells the compiler what it needs to know to compile +with vectors

+ +

      +The first statement is a variable definition that +defines vec to be a vector of four integers. Each integer location is +initialized at 0 by default.

+ +

      +The third statement uses the at() method of vector, +and behaves exactly like operator[] except for a different style of error +handling (at throws "exceptions" instead of crashing the program).

+ +

      +The call to vector's push_back() method appends a new element +33 to the end of vec

+ +

      +The resulting vector vec contains five int elements: +0, 11, 22, 0, and 33. Those values will be printed by the final loop.

+ +

      +A similar exercise could have been programmed with +float, Dog, or another type.

+ +

      +(Extra) In the "exception" style +of error handling used by methods such as at(), a runtime error (i.e., while +the program is running, such as index out of bounds for a vector object) +creates an object called an exception that includes information about +that error. We say that the error condition throws the exception object. +C++ provides an optional try/catch feature for capturing thrown exceptions and +taking action on them; otherwise, throwing an exception causes a program to +crash.

+ +

      +The vector method push_back() enables a programmer to +extend the length of a vector object by one element, as shown above. That new +element is added at the "back" or highest-indexed end of that vector.

+ +

      +Another vector method pop_back() enables a programmer +to delete the last element of a vector, thus decreasing its length by 1. The +methodpop_back() requires no argument and returns no value.

+ +

      +The vector index operator [ ] and the method at() both +provide immediate access to any element in a vector.

+ +

      +The method size() returns the number of elements +currently in a vector object.

+ +

      +The method back() returns the last element of a +non-empty vector. Thus, vec.back() returns the same value as vec[vec.size()-1] +orvec.at(vec.size()-1) .

+ +

      +The vector container provides methods for inserting or +removing values at locations other than the back of a vector object, called +insert()and erase(). However, these methods are not as efficient as push_back() +and pop_back(). This is because vector elements are stored consecutively in +memory, so inserting or removing an element at a position other than the back +requires copying all element values from that position through the back value.

+ +

      +(Extra) Note that the methods insert() +and erase() require iterator objects to specify position within a vector +object. An iterator is an object that contains a pointer and has methods +for certain pointer operations, such as a method next() for advancing to the +next element in an array or vector.

+ +

      +See www.cplusplus.com/reference/stl/vector for a +reference on vectors.

+ +

The STL container queue

+ +

      +STL's queue<T> is a templated class that is a FIFO +(first-in first-out) container. This means that it is capable of holding an +indeterminate number of elements of a particular type, organized in an ordered +list, with each element added at one end of the list (the back) and +removed at the other end (the front).

+ +

      +Whereas STL's vector templated class has many methods, +a queue has only six specified methods (seewww.cplusplus.com/reference/stl/queue:

+ +

      +push(), which adds an element at the end of a queue,

+ +

      +pop(), which removes an element from the beginning of +a queue,

+ +

      +front(), which returns the element at the front of a +non-empty queue (next to be popped),

+ +

      +back(), which returns the element at the back of a +non-empty queue (most recent to be pushed),

+ +

      +empty(), which returns Boolean True if there are no +elements in a queue and False otherwise, and

+ +

      +size(), which returns the number of elements currently +in a queue.

+ +

      +Here is a code example of using a queue.

+ +

      +   #include <iostream>
+    using namespace std;
+    #include <queue>
+
+    int main() {
+   
+      +queue<float> q;
+      +q.push(1.23);
+      +q.push(4.56);
+      +q.push(7.89);
+      while +(!q.empty()) {
+        +cout << q.front() << endl;
+        +q.pop();
+      }
+    }
+
+

+ +

      +The output from this code should be the numbers 1.23 then +4.56 then 7.89, one per line.

+ +

      +An STL vector could be used in a situation where a queue +would be appropriate (e.g., simulating a process comparable to a grocery-store checkout +line), using the vector methods push_back(), front(), and erase() (to remove +the front element). But a queue can be implemented more efficiently than a +vector for this purpose, avoiding the copying of elements that are needed for +vector's erase() method.

+ +

      +(Extra) The underlying data structure for a +queue can be specified when that queue is created, using a second template +parameter.

+ +

      +On the other hand, a queue provides no index or at() +operator for accessing an element other than the front or back elements. The +ability to access arbitrary element locations (e.g., via indices) is called random +access, and if random access is needed, a vector may be more desirable than +a queue.

+ +

      +As with vector, the templated container class queue +can accept a user-defined type for its elements. Thus

+ +

      +   queue<Dog> ,  queue<const Dog*> ,
+
+

+ +

      +and other types may be used.

+ +

 

+ +

 

+ +
+ +

 

+ +
+ +

 

+ +

rab@stolaf.edu, April +27, 2011

+ +

Disclaimer

+ +

 

+ +
+ + + + diff --git a/modules/CDS_cpp/source/IntroSTLContainers/introduction_stl_containers.rst b/modules/CDS_cpp/source/IntroSTLContainers/introduction_stl_containers.rst new file mode 100644 index 00000000..9f96b015 --- /dev/null +++ b/modules/CDS_cpp/source/IntroSTLContainers/introduction_stl_containers.rst @@ -0,0 +1,240 @@ +Introduction to STL containers +=============================== + + + +*Note:* This reading refers to topics that we will not pursue in +homework, but which may arise in general C++ coding. Such topics will be +marked\ **(Extra)**. + +Introduction +------------ + +The C++ +`Standard Template Library `_\ *(STL)* +is a software library of algorithms, data structures, and other features +that can be used with any pre-defined C++ type and with user-defined +types (that provide members such as a copy constructor and an assignment +operator). + +The STL uses a C++ feature called the *template* to substitute +types throughout a class or function definition, which enables a +programmer to avoid rewriting definitions that differ in the type being +used. + +For example, our IntArray class for homework provides a class +structure around an array of int, with error checking, default +initialization for array elements, whole-array assignment, etc. To +define a class LongArray or FloatArray, we would only need to replace +the type name int by a different type name long or float, etc. Templates +make it possible to define a class Array in which a type name T is +used thrroughout the definition of that class, so that the notation +Array would specify a class that behaves just like IntArray, +Array would specify FloatArray, etc. Here, the angle brackets +<...> are the syntax for invoking a template, and they enclose a type +name to use throughout a definition. + +A programmer can define her/his own templated classes. For +example, here is a complete program with a definition of a templated +structPair that represents an group of two elements of the same type, +together with an example use of that template. + +:: + + #include +     using namespace std; +     +     template +     struct Pair { +       T x; +       T y; +     +       Pair(T val1, T val2) { x = val1;  y = val2; } +     }; +     +     +     int main() { +       Pair p(3.14, -1.0); +     +       cout << p.x << ", " << p.y << endl; +     } + + +The STL *containers* are templated classes defined in the STL +that are designed to hold objects in some relationship. Examples: + +* The array classes we have implemented in homework are examples of containers, although STL's array-like container vector has some added useful features such as an ability to resize incrementally that our array classes did not include. + +* Another STL container is called a queue, and contains a linear list of elements that are added at one end and removed at another, comparable to a check-out line of customers at a grocery store. Unlike a vector, the elements of a queue do not necessarily appear consecutively in memory. + +* **(Extra)** Another example is the map container, which includes an operator[] that accepts "indices" of a type K (not necessarily an integer type) and associates values of type T with those "index" values. + +This is not a complete list of STL containers, but illustrates +some commonly used ones. See +`www.cplusplus.com.reference.stl `_ +for documentation of all STL containers + + +The STL container vector +------------------------ + +STL's vector is a templated class that behaves like an +"elastic array," in that its size can be changed incrementally. For +example, consider the following example program: + +:: +    #include +    using namespace std; +    #include +    int main() { +      vector vec(4); +      vec[1] = 11; +      vec.at(2) = 22; +      vec.push\_back(33); +      for (int i = 0;  i < vec.size(); i++) +        cout << vec.at(i) << endl; +    } + +The preprocessor directive + +:: + + #include + +tells the compiler what it needs to know to compile with vectors + +The first statement is a variable definition that defines vec to +be a vector of four integers. Each integer location is initialized at 0 +by default. + +The third statement uses the at() method of vector, and behaves +exactly like operator[] except for a different style of error handling +(at throws "exceptions" instead of crashing the program). + +The call to vector's push\_back() method appends a new element 33 +to the end of vec + +The resulting vector vec contains five int elements: 0, 11, 22, +0, and 33. Those values will be printed by the final loop. + +A similar exercise could have been programmed with float, Dog, or +another type. + +**(Extra)** In the "exception" style of error handling used by +methods such as at(), a runtime error (i.e., while the program is +running, such as index out of bounds for a vector object) creates an +object called an *exception* that includes information about that error. +We say that the error condition *throws* the exception object. C++ +provides an optional try/catch feature for capturing thrown exceptions +and taking action on them; otherwise, throwing an exception causes a +program to crash. + +The vector method push\_back() enables a programmer to extend the +length of a vector object by one element, as shown above. That new +element is added at the "back" or highest-indexed end of that vector. + +Another vector method pop\_back() enables a programmer to delete +the last element of a vector, thus decreasing its length by 1. The +methodpop\_back() requires no argument and returns no value. + +The vector index operator [ ] and the method at() both provide +immediate access to any element in a vector. + +The method size() returns the number of elements currently in a +vector object. + +The method back() returns the last element of a non-empty vector. +Thus, vec.back() returns the same value as vec[vec.size()-1] +orvec.at(vec.size()-1) . + +The vector container provides methods for inserting or removing +values at locations other than the back of a vector object, called +insert()and erase(). However, these methods are not as efficient as +push\_back() and pop\_back(). This is because vector elements are stored +consecutively in memory, so inserting or removing an element at a +position other than the back requires copying all element values from +that position through the back value. + +**(Extra)** Note that the methods *insert()* and *erase()* +require iterator objects to specify position within a vector object. An +*iterator* is an object that contains a pointer and has methods for +certain pointer operations, such as a method next() for advancing to the +next element in an array or vector. + +.. seealso:: + `www.cplusplus.com/reference/stl/vector `_ for a reference on vectors. + +The STL container queue +----------------------- + +STL's queue is a templated class that is a *FIFO (first-in +first-out)* container. This means that it is capable of holding an +indeterminate number of elements of a particular type, organized in an +ordered list, with each element added at one end of the list (the +*back*) and removed at the other end (the *front*). + +Whereas STL's vector templated class has many methods, a queue +has only six specified methods +(see `www.cplusplus.com/reference/stl/queue `_): + +* push(), which adds an element at the end of a queue, + +* pop(), which removes an element from the beginning of a queue, + +* front(), which returns the element at the front of a non-empty queue (next to be popped), + +* back(), which returns the element at the back of a non-empty queue (most recent to be pushed), + +* empty(), which returns Boolean True if there are no elements in a queue and False otherwise, and + +* size(), which returns the number of elements currently in a queue. + +Here is a code example of using a queue. +:: + + #include +     using namespace std; + #include + int main() { +     +       queue q; +       q.push(1.23); +       q.push(4.56); +       q.push(7.89); +       while (!q.empty()) { +         cout << q.front() << endl; +         q.pop(); +       } + } + +The output from this code should be the numbers 1.23 *then* 4.56 +*then* 7.89, one per line. + +An STL vector could be used in a situation where a *queue* would +be appropriate (e.g., simulating a process comparable to a grocery-store +checkout line), using the vector methods push\_back(), front(), and +erase() (to remove the front element). But a queue can be implemented +more efficiently than a vector for this purpose, avoiding the copying of +elements that are needed for vector's erase() method. + +**(Extra)** The underlying data structure for a queue can be +specified when that queue is created, using a second template +parameter. + +On the other hand, a queue provides no index or at() operator for +accessing an element other than the front or back elements. The ability +to access arbitrary element locations (e.g., via indices) is called +*random access*, and if random access is needed, a vector may be more +desirable than a queue. + +As with vector, the templated container class queue can accept a +user-defined type for its elements. Thus + +:: + + queue ,  queue , + +and other types may be used. + + +  diff --git a/modules/CDS_cpp/source/WebCrawler/CDS_crawler_lab_cpp.rst b/modules/CDS_cpp/source/WebCrawler/CDS_crawler_lab_cpp.rst new file mode 100644 index 00000000..1b27f951 --- /dev/null +++ b/modules/CDS_cpp/source/WebCrawler/CDS_crawler_lab_cpp.rst @@ -0,0 +1,383 @@ +Concurrent Data Structures in C++: Web crawler lab +=================================================== + +In this lab you will complete some code provided to you that will 'crawl' the web from a beginning page to a given depth by following every linked page and scraping it for more links to follow. The links found on each page are kept in a data structure until they are processed. + +Your goals +---------- + +The goals for this lab are: + +* complete and test a web crawler application, which fetches web pages then visits the links contained in those web pages, using STL containers; + +* experiment with an example of threads programming, a type of multicore parallel programming; + +* to complete a correct multi-threaded web crawler application that uses threaded building block (TBB) containers. + + +Source Code +----------- + +.. topic:: Still need + + The work on this lab requires a "tarball" named ``cds.tar.gz``. + + Instructors, please contact us for the complete code. + +Packages needed +---------------- + +- C++ compiler + +- Standard Template Library STL + +- CURL library for web access + +- Boost library, for threads and mutexes + +- Intel's Threading Building Blocks (TBB) + +- Make program + +Preparation +------------ + +Copy the tarball into your directory on a multicore linux machine. Then 'unxip' and 'untar' it like this: + + + :: + + % tar xfz cds.tar.gz + + This will create a directory ``cds`` that contains the code. Change + to that directory. + + :: + + % cd cds + +To Do +----- + +#. The directory ``serial`` contains several subdirectories, and is + organized in a structure suitable for a software project that is + capable of growing very large. + + Examine the code for this program. Observe the following: + + - The source files (``.cpp`` implementation and driver modules) are + contained in a subdirectory named ``src``, and the header files + (interface modules) are named with ``.hcc`` and are stored in a + subdirectory named ``include``. + + - Some of the state variables in classes within ``serial`` are STL + containers, as described in `a class + handout `_. + + - Three classes are defined: + + - ``spider`` is the main class, with methods for crawling from + page to page and for processing each page, and state variables + for recording the work to be done (i.e., web addresses or + *URLs* of pages to visit), the finished work (URLs already + processes), and a ``vector`` of ``page`` objects. + + - ``page`` contains state variables for the URL of a particular + web page (as a ``string``) and a ``vector`` of URLs found in + that web page (which are candidates for future processing). + ``page`` also contains a method for scanning a web page and + filling that ``vector`` with URLs that are contained in that + page. + + - ``raw_page`` has helper methods for fetching pages from the web + and for delivering the HTML code from a fetched web page. + + - The main program is in the file ``spider_driver.cpp``. It obtains + two values from the command line, namely the starting URL to crawl + from, and the maximum number of URLs to visit (which must be + *parsed* from a string to an integer). Then, the program performs + the crawl and prints results. + + - The ``Makefile`` uses ``make`` variables to make it easier to + change items such as compilation flags. Source files in the + ``src`` subdirectory are compiled to produce object (``.o``) files + in the ``build`` subdirectory, and those object files are linked + to creat an executable named ``bin/spider``. + + **DO THIS:** Write down other observations or comments about the + ``serial`` code. Feel free to include opinions you may have about the + code or its organization. + +#. Comments in ``spider.cpp`` indicate that two parts of the code need + to be filled in for the crawler program to work. Before actually + fillin that code in, we will see if we can compile and run the code + successfully. + + First, insert output statements in those two locations, to indicate + whether those sections of the code are reached. One message might + state "processing the page x" where x is the URL of the page being + processed, and the other might state "crawling the page x". + + Then ``cd`` to the ``serial`` directory, and issue a ``make`` command + in that directory ``serial``. This should compile the code and create + an executable ``bin/spider`` + +#. Now fill in the missing code for the ``spider::crawl()`` method. + *Notes:* + + - You will have to use appropriate methods for fetching a web page, + processing that page, and adding that page to the finished + ``queue``, then adding 1 to the variable named ``processed``. + + *Note:* The method for fetching requires a C-style string + (null-terminated array of characters) for its argument, but the + URL you are crawling is stored in a ``string`` object. Use the + online documentation for ``string`` to look for a method of + ``string`` that returns a C-style string with the same characters + as that ``string`` object. + + - Do you get the expected output, given that ``spider::process()`` + is still only printing a message? + +#. Finally, complete the implementation of the ``spider::process()`` + method, compile, and test. *Note:* + + - The method ``spider::is_image()`` currently always returns + ``false``. In a more sophisticated crawler, this method could + examine file extensions in order to determine whether a URL is an + image (no need to crawl) or not. + +#. Change directory to the ``cds/threads`` directory within your lab1 + directory. + + :: + + % cd ~/lab1/cds/threads + +#. First, look at the sequential program ``primes.cpp`` in that + directory. It computes a table of primes up to a maximum number, then + counts the number of primes produced. + + ``primes.cpp`` does *not* use threads, but is an ordinary program + with multiple loops. + + Then, compile and run the program, using the Makefile provided + (``make primes``). Feel free to experiment. *Note:* Be prepared to + wait... + +#. Now, examine the multi-threaded program ``threads/primes2.cpp``. This + program uses the same algorithm for determining primes, but it + divides the range 2..\ ``MAX`` up into ``threadct`` non-overlapping + subranges or "chunks", and has a separate ``boost`` thread determine + the primes within each chunk. Observe the following: + + - The loop for determining primes from 2 to ``MAX``, which was + located in ``main()`` in the program ``primes.cpp``, has been + relocated to a separate function ``work()``. That function has two + arguments ``min`` and ``max``, and the loop has been modified to + check only the subrange ``min``..\ ``max`` instead of the entire + range 2..\ ``MAX``. + + Each thread will execute ``work()`` as if it were a "main program" + for that thread. + + - ``pool`` is an array of pointers to ``boost`` threads. + + - Each ``boost`` thread is initialized with a unique subrange of + 2..\ ``MAX``. Threads other than the first and the last receive + subranges of length ``segsize``. The first and last subranges are + treated specially, to insure that the complete computation covers + exactly the range 2..\ ``MAX``. + + - To construct a ``boost`` thread running ``work()`` with arguments + ``a`` and ``b``, the following constructor call would be used: + + :: + + thread(boost::bind(work, a, b)) + + - The call above constructs a thread, but that thread doesn't begin + executing until its ``join()`` method is called. Thus, there is a + separate loop that calls ``join()`` for all the threads in the + array ``pool``, which starts up all the threads. + +#. Now, compile and run the program ``threads/primes2.cpp``, using the + ``Makefile`` provided (``make primes2``). This program takes an + optional positive integer argument, for the thread count (default is + 1 thread). + +#. You can time how long a program runs at the Linux or Macintosh + command line by preceding the command with the word ``time``. For + example, the two commands + + :: + + % time primes + % time primes2 4 + + will report the timing for running both the sequential ``primes`` + program, and the multi-threaded ``primes2`` program with four + threads. + + Perform time tests of these two programs, for at least one run of + ``primes``, one run of ``primes2`` with one thread, and one run of + ``primes2`` with 4 threads. Feel free to experiment further. + +#. Examine the code for the program ``parallel``. Observe the following: + + - The same four-directory structure is used as for the ``serial`` + directory in the previous lab. + + - The header files ``serial/include/page.hpp`` and + ``parallel/include/page.hpp`` are identical. You can use the + following ``diff`` command to verify this: + + :: + + $ cd ~/SD/lab1 + $ diff serial/include/page.hpp parallel/include/page.hpp + + If the ``diff`` command finds any differences between its two file + arguments, it will report those differences; if there are no + differences, ``diff`` will print nothing. + + **DO THIS:** Use the ``diff`` command to compare ``raw_page.hpp`` and + ``spider.hpp`` for these two versions ``serial`` and ``parallel``. + *Note:* The ``diff`` program will report differences by printing + lines that appear differently in those files. Lines that appear only + in the first file argument to ``diff`` will be prefixed by ``<``, and + lines that appear only in the second file will be prefixed by ``>``. + + Here are differences between ``serial/include/spider.hpp`` and + ``parallel/include/spider.hpp``. + + - A different selection of ``#include`` directives appears in the + two files. In particular, + + - ``serial/include/spider.hpp`` includes ````, for the STL + queue container. + + - ``parallel/include/spider.hpp`` includes three other files + instead of ````. One refers to a new file + ``atomic_counter.hpp`` that is part of this program (in the + directory ``parallel/include``). The others provide two *TBB + containers*, named ``tbb::concurrent_queue`` and + ``tbb::concurrent_vector`` + + TBB stands for *Intel Threading Building Blocks*, which + provides an alternative implementation of some common + containers. TBB also provides various parallel algorithms, but + we will not use those algorithm features in this lab. + + - The state variables ``m_work``, ``m_finished``, and ``m_pages`` + use the TBB container types ``tbb::concurrent_queue`` or + ``tbb::concurrent_vector`` instead of the STL containers + ``std::queue`` and ``std::vector``. + + - ``parallel/include/spider.hpp`` has two new state variables: + + - ``m_processed``, which has type ``atomic_counter`` (defined in + ``atomic_counter.hpp``) + + - ``m_threadCount`` of type ``size_t``, which is an integer type + + - In ``parallel``, one of the constructors for ``spider`` has a + second argument of type ``size_t``. + + - There are two new methods, named ``work()`` and ``work_once()``. + + **Note:** TBB containers are used instead of STL containers because + TBB containers are *thread safe*. This means that multiple threads + can safely interact with a TBB container at the same time without + causing errors. STL containers are *not* thread-safe: with STL + containers, it is possible for two threads to interact with a + container in a way that produces incorrect results. + + When the correct behavior of a program depends on timing, we say that + program has a *race condition*. The parallel version of the program + uses TBB containers in order to avoid race conditions. (Race + conditions are discussed in other + `CSinParallel `_ modules.) + + Likewise, the state variable ``m_processed`` has the type + ``atomic_counter`` instead of ``int`` or ``long`` because the + ``atomic_counter`` type is thread-safe, enabling us to avoid a race + condition that may arise if multiple threads interact with an integer + variable at about the same time. + +#. The files ``serial/src/spider.cpp`` and ``parallel/src/spider.cpp`` + contain the main differences between these programs -- the + ``parallel`` version uses multiple threads. Running ``diff`` shows + these differences: + + - In ``parallel``, one of the constructors for ``spider`` has a + second argument, to specify the number of threads to use. + + - The counter ``processed`` is a local variable in ``spider::crawl`` + in ``serial``. This local variable is replaced by a *state + variable* ``m_processed`` in ``parallel``. + + - The main work of ``crawl`` is moved into a method ``work()`` for + the multithreaded version ``parallel``, and that version creates + ``threadCount`` threads to carry that work out. Note that + ``work()`` has one argument, an integer index of a thread in the + array ``threads[]``. + + - The method ``spider::process()`` and the rest of the code in + ``spider.cpp`` are identical (except for missing code). The + comment within ``process()`` indicates that the same algorithm can + be used for ``parallel`` as for ``serial``. Why can the same + algorithm be used in the multi-threaded version as in the + sequential version? + +#. Fill in the code indicated in two locations for the ``parallel`` + version of ``spider.cpp``, working from the code you wrote for the + ``serial`` version, as indicated by comments in the ``parallel`` + code. Then compile and run your program. + + *Note:* This version of the program requires three command-line + arguments: the maximum number of URLs; the number of threads to use + (this arg is new); and the starting URL to crawl. + + Run the program with multiple threads (say, 4 threads). What do you + observe about the run? + + - You can examine the beginning of the output using the ``more`` + program, e.g., + + :: + + % bin/spider 100 4 www.stolaf.edu | more + + Each thread is programmed to print a message such as + + :: + + Thread 2 finished after processing 29 URLs + + when it completes. + + - You will probably find that a small number of threads processed + all of the URLs, and that the other threads finished early without + doing any work. How many threads processed URLs in your run? Can + you think of a reason why the others finished early without + processing any URLs? (*Hint:* Think about the work queue near the + beginning of the program, just as the threads are starting their + work.) + +#. To spread the computational work out better among the threads, + observe that the method ``spider::crawl()`` includes a call to a + method ``work_once()`` that has been commented out. + + :: + + /* work_once(); */ + + Remove those comments, in order to enable that call to + ``work_once();`` . This will cause the program to process one web + page before beginning multi-thread processing. If that first + processed page includes several links, they will be added to the + queue ``m_work``, so that several threads can retrieve web pages to + process when they first begin. + + diff --git a/modules/CDS_cpp/source/WebCrawler/cds.tar.gz b/modules/CDS_cpp/source/WebCrawler/cds.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..de840d5d80e00cba99ece8dd29688e1fa9c4ce01 GIT binary patch literal 5426 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 c>7Iv@3x_RsE&#{?0BfGdz5oCK literal 0 HcmV?d00001 diff --git a/modules/CDS_cpp/source/conf.py b/modules/CDS_cpp/source/conf.py new file mode 100644 index 00000000..78e05bdb --- /dev/null +++ b/modules/CDS_cpp/source/conf.py @@ -0,0 +1,242 @@ +# -*- coding: utf-8 -*- +# +# Concurrent Data Structures in C++ documentation build configuration file, created by +# sphinx-quickstart on Tue Jul 3 12:05:29 2012. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.pngmath'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'Concurrent Data Structures in C++' +copyright = u'2012, Richard Brown' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '1.0' +# The full version, including alpha/beta/rc tags. +release = '1.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +html_title = 'Concurrent Data Structures in C++' + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'ConcurrentDataStructuresinCdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'ConcurrentDataStructuresinC.tex', u'Concurrent Data Structures in C++ Documentation', + u'Richard Brown', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'concurrentdatastructuresinc', u'Concurrent Data Structures in C++ Documentation', + [u'Richard Brown'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'ConcurrentDataStructuresinC', u'Concurrent Data Structures in C++ Documentation', + u'Richard Brown', 'ConcurrentDataStructuresinC', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' diff --git a/modules/CDS_cpp/source/index.rst b/modules/CDS_cpp/source/index.rst new file mode 100644 index 00000000..514d938b --- /dev/null +++ b/modules/CDS_cpp/source/index.rst @@ -0,0 +1,27 @@ +.. Concurrent Data Structures in C++ documentation master file, created by + sphinx-quickstart on Tue Jul 3 12:05:29 2012. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Concurrent Data Structures in C++ +================================== + +Contents: + +.. toctree:: + :maxdepth: 2 + + WebCrawler/CDS_crawler_lab_cpp + IntroSTLContainers/introduction_stl_containers + + +.. comment for now + + Indices and tables + ================== + + * :ref:`genindex` + * :ref:`modindex` + * :ref:`search` + +.. diff --git a/modules/MulticoreProgramming/Makefile b/modules/MulticoreProgramming/Makefile new file mode 100644 index 00000000..49749e3e --- /dev/null +++ b/modules/MulticoreProgramming/Makefile @@ -0,0 +1,153 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ProgrammingwithMultipleCores.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ProgrammingwithMultipleCores.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/ProgrammingwithMultipleCores" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ProgrammingwithMultipleCores" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/modules/MulticoreProgramming/source/conf.py b/modules/MulticoreProgramming/source/conf.py new file mode 100644 index 00000000..2f41dc6c --- /dev/null +++ b/modules/MulticoreProgramming/source/conf.py @@ -0,0 +1,248 @@ +# -*- coding: utf-8 -*- +# +# Programming with Multiple Cores documentation build configuration file, created by +# sphinx-quickstart on Tue Jun 26 16:07:18 2012. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.pngmath'] + +#!!!! these paths work on Macs with a TeXLive install +pngmath_latex = '/usr/local/texlive/2011/bin/x86_64-darwin/latex' +pngmath_dvipng = '/usr/local/texlive/2011/bin/x86_64-darwin/dvipng' + +#extensions = ['sphinx.ext.jsmath'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'Programming with Multiple Cores' +copyright = u'2012, Richard Brown and Libby Shoop' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +#version = '1.0' +# The full version, including alpha/beta/rc tags. +#release = '1.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +html_title = 'Programming with Multiple Cores' + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'ProgrammingwithMultipleCoresdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'ProgrammingwithMultipleCores.tex', u'Programming with Multiple Cores', + u'Richard Brown and Libby Shoop', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'programmingwithmultiplecores', u'Programming with Multiple Cores', + [u'Richard Brown and Libby Shoop'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'ProgrammingwithMultipleCores', u'Programming with Multiple Cores', + u'Richard Brown and Libby Shoop', 'ProgrammingwithMultipleCores', 'CSInParallel project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' diff --git a/modules/MulticoreProgramming/source/correctOpenMP/FixingOurOpenMPcode.rst b/modules/MulticoreProgramming/source/correctOpenMP/FixingOurOpenMPcode.rst new file mode 100644 index 00000000..f0445133 --- /dev/null +++ b/modules/MulticoreProgramming/source/correctOpenMP/FixingOurOpenMPcode.rst @@ -0,0 +1,112 @@ +Creating a correct threaded version +==================================== + +Race Conditions +--------------- + +A program has a race condition if the correct behavior of that program +depends on the timing of its execution. With 2 or more threads, the +program trap-omp.C has a race condition concerning the shared variable +``integral``, which is the accumulator for the summation performed by that +program's for loop. + +When threadct == 1, the single thread of execution updates the shared variable +``integral``\ on every iteration, by reading the prior value of the memory location for +``integral``, computing and adding the value f(a+i\*h), then storing the result into that memory location. (Recall that a variable is a named location in main memory.) + +But when threadct > 1, there are at least two independent threads, executed on separate physical cores, that are reading then writing the memory location for +``integral``. The incorrect answer results when the reads and writes of that memory location get out of order. Here is one example of how unfortunate ordering can happen with two threads: + +========================= =========================== =========================== +\ Thread 1 Thread 2 +========================= =========================== =========================== +source code: | integral += f(a+i\*h); | integral += f(a+i\*h); +execution of binary code: | | + | 1. read value of integral | + | 2. add  f(a+i\*h) | 1. read value of integral + | 2. add  f(a+i\*h) + | 3. write sum to  integral | + | 3. write sum to  integral +========================= =========================== =========================== + + +In this example, during one poorly timed iteration for each thread, +Thread 2 reads the value of the memory location integral before Thread 1 +can write its sum back to integral. The consequence is that Thread 2 +replaces (overwrites) Thread 1's value of integral, so the amount added +by Thread 1 is omitted from the final value of the accumulator integral. + +.. topic:: To Do + + Can you think of other situations where unfortunate ordering of thread + operations leads to an incorrect value of integral? Write down at least + one other bad timing scenario. Other scenarios will work (you may have seen some of these during testing of your code). Write down one of these. + +.. note:: Thousands of occurrences of bad timing lead to the computed answer for integral being off by often 25% or more. + +Avoiding Race Conditions +------------------------ + +One approach to avoiding this program's race condition is to use a separate local variable integral for each thread instead of a global variable that is shared by all the threads. But declaring integral to be private instead of shared in the pragma will only generate threadct partial sums in those local variables named integral -- the partial sums in those temporary local variables will not be added to the program's variable integral. In fact, the value in those temporary local variables will be discarded when each thread finishes its work for the parallel for if we simply make integral private instead of shared. + +.. topic:: To Do + + Can you re-explain this situation in your own words? + +Fortunately, OpenMP provides a convenient and effective solution to this problem. The OpenMP clause +:: + + reduction(+: integral)    + +will + +#. cause the variable integral to be private (local) during the + execution of each thread, and +#. add the results of all those private variables, then finally +#. store that sum of private variables in the global variable named + integral. + + +Reduction +---------- + +This code example contains a very common *pattern* found in +parallel programs: **reducing several values down to one**. This is so common that the designers of OpenMP chose to make it simple to declare that a variable was involved on a reduction. The code here represents one way that reduction takes place: during a step-wise calculation of smaller pieces of a larger problem. The other common type of reduction is to accumulate all of the values stored in an array. + +.. seealso:: You can use other arithmetic operators besdides plus in the reduction clause-- see `the OpenMP tutorial section on reduction `_ + +According to the OpenMP Tutorial, here is how the reduction is being done inside the compiled OpenMP code (similar to how we described it above, but generalized to any reduction operator): + + "A private copy for each listed variable is created for each thread. At the end of the reduction, the reduction operation is applied to all private copies of the shared variable, and the final result is written to the global shared variable." + +Thus, a variable such as ``integral`` that is decalred in a reduction clause is both private to each thread and ultimately a shared variable. + + +.. topic:: To Do + + Add the above reduction clause to your OpenMP pragma and remove integral from the list of shared variables, so that the pragma in your code appears as shown below. Then recompile and test your program many times with different numbers of threads. You should now see the correct answer of 2.0 every time you execute it with multiple threads -- a correct multi-core program! + +.. literalinclude:: trap-omp-working.C + :language: c++ + :lines: 33-37 + +.. topic:: Tricky Business + + Note that with the incorrect version, you sometimes got lucky (perhaps even many times) as you tried running it over and over. This is one of the most perplexing and difficult aspects of parallel programming: **it can be difficult to find your bugs because many times your program will apear to be correct**. Remember this: you need to be diligent about thinking through your solutions and questioning whether you have coded it correctly. + +Thread-safe Code +---------------- + +A code segment is said to be *thread-safe* if it remains correct when +executed by multiple independent threads. The body of this loop is +not thread-safe; we were able to make it so by indicating that a *reduction* +was taking place on the variable ``integral``. + + +Some C/C++ libraries are identified as thread-safe, meaning that each function +in that library is thread-safe. Of course, calling a thread-safe +function doesn't insure that the code with that function call is +thread-safe. For example, the function f() in our example, is +thread-safe, but the body of that loop is not thread-safe. + + diff --git a/modules/MulticoreProgramming/source/correctOpenMP/originalHTMLFromGoogleDoc/OpenMPNextstepsFixingourOpenMPcode.html b/modules/MulticoreProgramming/source/correctOpenMP/originalHTMLFromGoogleDoc/OpenMPNextstepsFixingourOpenMPcode.html new file mode 100755 index 00000000..a4327a17 --- /dev/null +++ b/modules/MulticoreProgramming/source/correctOpenMP/originalHTMLFromGoogleDoc/OpenMPNextstepsFixingourOpenMPcode.html @@ -0,0 +1 @@ +OpenMP Next steps: Fixing our OpenMP code

Creating a correct threaded version

A program has a race condition if the correct behavior of that program depends on the timing of its execution. With 2 or more threads, the program trap-omp.C has a race condition concerning the shared variable integral, which is the accumulator for the summation performed by that program's for loop.

  1. When threadct == 1, the single thread of execution updates the shared variable integral on every iteration, by reading the prior value of the memory location integral, computing and adding the value f(a+i*h), then storing the result into that memory location integral. (Recall that a variable is a named location in main memory.)
  2. But when threadct > 1, there are at least two independent threads, executed on separate physical cores, that are reading then writing the memory location integral. The incorrect answer results when the reads and writes of that memory location get out of order. Here is one example of how unfortunate ordering can happen with two threads:

Thread 1                           |  Thread 2                      

                                   |                                        

code:   integral += f(a+i*h);      |  code:   integral += f(a+i*h);        

                                   |                                        

exec:   1. read value of  integral |  exec:  

        2. add  f(a+i*h)           |          1. read value of  integral

                                   |          2. add  f(a+i*h)      

        3. write sum to  integral  |          

                                   |          3. write sum to  integral

In this example, during one poorly timed iteration for each thread, Thread 2 reads the value of the memory location integral before Thread 1 can write its sum back to integral. The consequence is that Thread 2 replaces (overwrites) Thread 1's value of integral, so the amount added by Thread 1 is omitted from the final value of the accumulator integral.

Can you think of other situations where unfortunate ordering of thread operations leads to an incorrect value of integral? Write down at least one other bad timing scenario.

Note: Thousands of occurrences of bad timing lead to the computed answer for integral being off by often 25% or more.

  1. One approach to avoiding this program's race condition is to use a separate local variable integral for each thread instead of a global variable that is shared by all the threads. But declaring integral to be private instead of shared in the pragma will only generate threadct partial sums in those local variables named integral -- the partial sums in those temporary local variables will not be added to the program's variable integral. In fact, the value in those temporary local variables will be discarded when each thread finishes its work for the parallel for if we simply make integral private instead of shared.
  2. Can you re-explain this situation in your own words?
  1. Fortunately, OpenMP provides a convenient and effective solution to this problem.
  1. The OpenMP clause   reduction(+: integral)   will
  1. cause the variable integral to be private (local) during the execution of each thread, and
  2. add the results of all those private variables, then finally
  3. store that sum of private variables in the global variable named integral.
  1. Add this clause to your OpenMP pragma, and remove the variable integral from the shared clause, then recompile and test your program. You should now see the correct answer 2.0 when computing with multiple threads -- a correct multi-core program!

A code segment is said to be thread-safe if it remains correct when executed by multiple independent threads. The body of this loop is not thread-safe.

Some libraries are identified as thread-safe, meaning that each function in that library is thread-safe. Of course, calling a thread-safe function doesn't insure that the code with that function call is thread-safe. For example, the function f() in our example, is thread-safe, but the body of that loop is not thread-safe.

\ No newline at end of file diff --git a/modules/MulticoreProgramming/source/correctOpenMP/originalHTMLFromGoogleDoc/OpenMPNextstepsFixingourOpenMPcode.zip b/modules/MulticoreProgramming/source/correctOpenMP/originalHTMLFromGoogleDoc/OpenMPNextstepsFixingourOpenMPcode.zip new file mode 100644 index 0000000000000000000000000000000000000000..769880ec8e81ce518898021a724d60478bb913e6 GIT binary patch literal 2560 zcma);XE+-Q8-`=mR#K}lo#$eR zQpr+J-l|>Q(`1>n^B)4oF*k`6!?j>l8bryu`t*hubbYshuloZ{5_-mD%?Lz}+I|u96P-WPlb`&wZa)h81&k zP6_-W*{JeeshCyZi^t9-t86-=+ zw#&aEqcmC45uqozl}F?}XDe&?#<^JL9@iQE8B%Xo!|J&F2{I)w33*R%I0BMcZKk0G z60vNm9K?nZqi=uV+Ua`z+~av<6nch#K`3kvvN%<9$#0)GV2FMo<2hT^lBpO#)}IXK zgFY^QRp6P1o?W4qQ|<*o`$m!iS8MIcCP-?VvQ4NcmqovD6+=lbM|mE_qG z@9Sc)>VzXpjBK!}tzfXC{fA1_#!Z#!Z~?sQdi`VHxe;$;5xzLfP9fs89D1Fxd38y_ zqu+MRB^_=@ouYo`zO|BRZbSP*bxYKOZ|QUi?X4U?pzwvi9}v$%BAJKZY)ZPGQdp3H-Z1IQ!>cMDYk~+tpb|?HBN6e3O)T6Tch6}#`Gl90w0vh-j2tl4PzPU>#Vc25uzpV?M73XD~J zN2*yc%bGsjp(=$jO_mWm=wC<_cek0FtIb5yYpmhHYFnf-H-MSy7DcKmn^|e84-AhI zg)8?z*Y9LGYp!_>D&jIEt3u6@3&-zXvy+aVx%bbSbjE5w2dJP6=1pHb^yX zY};89?HWMWlALC6Njcu?*$t4v&B8L<$0Wf{IoCl8+Yd*?gvSsP;j#_vU!?-ib1_T$*#oYQ##I#qhfm)A2`! zVi(U(wyb6dEi-j5mBDKQQi-kaq+%F+1&(qi5}eNoy99z_=;!g9v?5HzGi1Tj((Ztm z>_$`fhH+FJtNz(bWWS8>i>;4zV`q15wVl%3(@s6tX%cHEjMmlQHR0r+uiWB;8&wBF zt--Cv8j45mgYHc-$|<_6Dp^6u;8fYNULh~r{t@!z!jxAc4E(79uPehq82M8@{y2x7 z?p3B%LY+87<tS8A5vNO(@Dv5i8V#e=oj>3p|L)uf3${;DxE?X0Em1 zuGHSHPWb&Q4w7_7llSE+7U>Y5H4|C@TJdzAciQwb`?9M0%dTz)QiUk|73gc8&pyNP zW}b){H=#g&?Qtq|#9K9p_-IP+_~fAh^zsp` zl%6g_znUCobLSR=&3>`uhJNg_bm*3GzbT)C^leFv2)SH4uuBBhX~0~%{+DpPSIjkcA{^xXm)N{PLG?bavoX;EL0q_B-x zwkY#y2UJ{cvj|q%Aj$f3{F~x=v#}cuNw^Wt`KUgwK9`eneMJE}@{=r@z<-}c1qxF2 zzZD(-PUxBJRf&*xA>8?%V6Xf*2YywVRvWpJYMR6e^RB6v?r!-mamSo>ffnq2XeoS@ z&N0G%aiV?s7LS1voA+<8sE2ek)b`fn1pA5CL1Pqj?dlIKaAqtychry(*T9Thw}s& z$Ap=(Wfz=yU@N}gRWDNoe72<83QDd7SK#OaW5AK1_S^Da#sBt^aPA?y`97#(1dzzNRkdoX*&{kcC?;#gmC7 zTji+#N$U)yxz7`fiCKg26bin9f)#+APoQoXzMXcF#Z`FyLWtCY>hoZ!WE~{__msk& z_xPL5aL3#qR{nk5KRQR(5f#0sL2tB)P@6wL+axd3v!#BqmJ6$`47cfQ`?N^50K^kW zEN|iYc05bR0ux&^KM7`{gjI}5zhmwKmORN`EEsZR)0va6*MEL0q>lo*7{JGhw z^UbRUXOChi2v5{+%rWAkS(Hb#l_YQ?9%wQhgAeC7SZo^Z)?4G%=bkf z(^b7J4qDB<*1Nj~Ut-w1TuO|xt0fb7RY#!1oO}q02vt7M!;YKsM-Z-d-cxM^oS7wL ykAGU96#%#*2LK@8bo9J{|63Ha literal 0 HcmV?d00001 diff --git a/modules/MulticoreProgramming/source/correctOpenMP/trap-omp-working.C b/modules/MulticoreProgramming/source/correctOpenMP/trap-omp-working.C new file mode 100644 index 00000000..370b7cf7 --- /dev/null +++ b/modules/MulticoreProgramming/source/correctOpenMP/trap-omp-working.C @@ -0,0 +1,46 @@ +#include +#include +#include +using namespace std; + +/* Demo program for OpenMP: computes trapezoidal approximation to an integral*/ + +const double pi = 3.141592653589793238462643383079; + +int main(int argc, char** argv) { + /* Variables */ + double a = 0.0, b = pi; /* limits of integration */; + int n = 1048576; /* number of subdivisions = 2^20 */ + double h = (b - a) / n; /* width of subdivision */ + double integral; /* accumulates answer */ + int threadct = 1; /* number of threads to use */ + + /* parse command-line arg for number of threads */ + if (argc > 1) + threadct = atoi(argv[1]); + + double f(double x); + +#ifdef _OPENMP + cout << "OMP defined, threadct = " << threadct << endl; +#else + cout << "OMP not defined" << endl; +#endif + + integral = (f(a) + f(b))/2.0; + int i; + +#pragma omp parallel for num_threads(threadct) \ + shared (a, n, h) reduction(+: integral) private(i) + for(i = 1; i < n; i++) { + integral += f(a+i*h); + } + + integral = integral * h; + cout << "With n = " << n << " trapezoids, our estimate of the integral" << + " from " << a << " to " << b << " is " << integral << endl; +} + +double f(double x) { + return sin(x); +} diff --git a/modules/MulticoreProgramming/source/index.rst b/modules/MulticoreProgramming/source/index.rst new file mode 100644 index 00000000..92d014f3 --- /dev/null +++ b/modules/MulticoreProgramming/source/index.rst @@ -0,0 +1,26 @@ +.. Programming with Multiple Cores documentation master file, created by + sphinx-quickstart on Tue Jun 26 16:07:18 2012. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Programming with multiple cores +=============================== + +Contents: + +.. toctree:: + :maxdepth: 1 + + introOpenMP/GettingstartedwithOpenMP + correctOpenMP/FixingOurOpenMPcode + timingAndScalability/TimingOnMTLandscalability + + +.. commented out for now + Indices and tables + ================== + + * :ref:`genindex` + * :ref:`modindex` + * :ref:`search` +.. diff --git a/modules/MulticoreProgramming/source/introOpenMP/GettingstartedwithOpenMP.rst b/modules/MulticoreProgramming/source/introOpenMP/GettingstartedwithOpenMP.rst new file mode 100644 index 00000000..76a7b31d --- /dev/null +++ b/modules/MulticoreProgramming/source/introOpenMP/GettingstartedwithOpenMP.rst @@ -0,0 +1,221 @@ +Getting Started with Multicore Programming using OpenMP +======================================================= + +Notes about this document +-------------------------- +This is designed to be a lab activity that you will perform on linux machines and/or on the Intel Manycore Testing Lab (MTL). + +:Comments: + + Sections labeled like this are important explanations to pay attention to. + +.. topic:: Dig Deeper: + + Comments in this format indicate possible avenues of exploration for people seeking more challenge or depth of knowledge. + + +Multicore machines and shared memory +------------------------------------- + +Multicore CPUs have more than one ‘core’ processor that can execute +instructions at the same time.   The cores share main memory.  In the +next few activities, we will learn how to use a library called OpenMP to +enable us to write programs that can use multicore processors and shared +memory to write programs that can complete a task faster by taking +advantage of using many cores.  These programs are said to work “in +parallel”.  We will start with our own single machines, and then +eventually use a machine with a large number of cores provided by Intel +Corporation, called the Manycore Testing Lab (MTL). + +Parallel programs use multiple ‘threads’ executing instructions +simultaneously to accomplish a task in a shorter amount of time than a +single-threaded version.  A process is an execution of a program. A +thread is an independent execution of (some of) a process's code that +shares (some) memory and/or other resources with that process. When +designing a parallel program, you need to determine what portions could +benefit from having many threads executing instructions at once.  In +this lab, we will see how we can use “task parallelism” to execute the +same task on different parts of a desired computation in threads and +gather the results when each task is finished. + +Getting started with OpenMP +---------------------------- + +We will use a standard system for parallel programming +called\ `  `_\ `OpenMP `_, +which enables a C or C++ programmer to take advantage of multi-core +parallelism primarily through preprocessor pragmas. These are directives +that enable the compiler to add and change code (in this case to add +code for executing sections of it in parallel). + +.. seealso:: More resources about OpenMP can be found here: `http://openmp.org/wp/resources/ `_. + +         + + + +We will begin with a short C++ program, parallelize it using OpenMP, and +improve the parallelized version. This initial development work can be +carried out on a linux machine.  Working this time with C++ will not be +too difficult, as we will not be using the object-oriented features of +the language, but will be taking advantage of easier printing of output. + +The following program computes a Calculus value, the "trapezoidal +approximation of   + +.. math:: + + \int_0^x \sin(x) {d}{x} + + +using :math:`2^{20}` equal subdivisions.”   The exact answer from this computation +should be 2.0. + +.. literalinclude:: trap-omp-unfinished.C + :language: c++ + :linenos: + + + +:Comments: + + * If a command line argument is given, the code segment below converts that argument to an integer and assigns that value to the variable threadct, overriding the default value of 1. This uses the two arguments of the function main(), namely argc and argv. This demo program makes no attempt to check whether a first command line argument argv[1] is actually an integer, so make sure it is (or omit it). + + .. literalinclude:: trap-omp-unfinished.C + :language: c++ + :lines: 19-20 + + * The variable *threadct* will be used later to control the number of threads to be used. Recall that a process is an execution of a program. A **thread** is an independent execution of (some of) a process's code that shares (some) memory and/or other resources with that process. We will modify this program to use multiple threads, which can be executed in parallel on a multi-core computer. + + * The preprocessor macro \_OPENMP is defined for C++ compilations that include support for OpenMP. Thus, the code segment below provides a way to check whether OpenMP is in use. + + .. literalinclude:: trap-omp-unfinished.C + :language: c++ + :lines: 24-28 + + + * The above code also shows the convenient way to print to stdout in C++. + + + * The following lines contain the actual computation of the trapezoidal approximation: + + .. literalinclude:: trap-omp-unfinished.C + :language: c++ + :lines: 30-37 +          + + Since n == :math:`2^{20}`, the for loop adds over 1 million values. Later in this + lab, we will parallelize that loop, using multiple cores which + will each perform part of this summation, and look for speedup in the + program's performance. + +To Do: +------ + +On a linux machine, create a file named trap-omp.C containing the program above or grab it and save it from the following link: + +:download:`download trap-omp.C ` (on most browsers, right-click, save link as) + +To compile your file, you can enter the command: +:: + + %  g++ -o trap-omp trap-omp.C -lm -fopenmp + + +.. note:: Here, % represents your shell prompt, which is usually a machine name followed by either % or $. + + +First, try running the program without a command-line argument, like this: +:: + + %  ./trap-omp + +This should print a line "\_OPENMP defined, threadct = 1", followed by a +line indicating the computation with an answer of 2.0. Next, try +:: + + %  ./trap-omp 2 + +This should indicate a different thread count, but otherwise produce the +same output. Finally, try recompiling your program omitting +the -fopenmp flag. This should report \_OPENMP not defined, but give the +same answer 2.0. + +Note that the program above is actually using only a single core, whether +or not a command-line argument is given. It is an ordinary C++ program +in every respect, and OpenMP does not magically change ordinary C++ +programs; in particular, the variable *threadct* is just an ordinary local +variable with no special computational meaning. + +To request a parallel computation, add the following pragma preprocessor +directive, just before the for loop. + +.. literalinclude:: trap-omp-notworking.C + :language: c++ + :lines: 33-34 + + +The resulting code will have this format: + +.. literalinclude:: trap-omp-notworking.C + :language: c++ + :lines: 31-37 + + +:Comments: + + * Make sure no characters follow the backslash character before the end of the first line. This causes the two lines to be treated as a single pragma (useful to avoid long lines). + + * The phrase **omp parallel for** indicates that this is an OpenMP pragma for parallelizing the for loop that follows immediately. The OpenMP system will divide the :math:`2^{20}` iterations of that loop up into *threadct* segments, each of which can be executed in parallel on multiple cores. + + * The OpenMP clause **num\_threads(threadct)** specifies the number of threads to use in the parallelization. + + * The clauses in the second line indicate whether the variables that appear in the for loop should be shared with the other threads, or should be local private variables used only by a single thread. Here, four of those variables are globally shared by all the threads, and only the loop control variable i is local to each particular thread. + + +Enter the above code change (add the pragma preprocessor directive), then compile and test the resulting executable with one thread, then more than one thread, like this: +:: + + %  g++ -o trap-omp trap-omp.C -lm -fopenmp + %  ./trap-omp + %  ./trap-omp 2 + %  ./trap-omp 3 + etc. + +.. topic:: Dig Deeper: + + * The `OpenMP tutorial `_ contains more information about advanced uses of OpenMP. Note that OpenMP is a combination of libraries and compiler directives that have been defined for both Fortran and C/C++. + + * OpenMP provides other ways to set the number of threads to use, namely the omp\_set\_num\_threads() library function (see `tutorial section on library routines `_), and the OMP\_NUM\_THREADS linux/unix environment variable (see `tutorial section on environment variables `_). + + + * OpenMP provides several other clauses for managing variable locality, initialization, etc. Examples: default, firstprivate, lastprivate, copyprivate. You could investigate this further in the `tutorial section pertaining to clauses `_. + +What's happening? +------------------ + +After inserting the parallel for pragma, observe that for threadct == 1 (e.g., no +command-line argument), the program runs +and produces the correct result of 2.0, but that +:: + + % ./trap-omp 2 + +which sets threadct == 2, sometimes produces an incorrect answer (perhaps about +1.5). What happens with repeated runs with that and other (positive) +thread counts? Can you explain why? + +**Note:** Try reasoning out why the computed answer is correct for one +thread but incorrect for two or more threads. Hint: All of the values +being added in this particular loop are positive values, and the +erroneous answer is too low. + +If you figure out the cause, think about how to fix the problem. You may +use the\ `  `_\ `OpenMP +website `_ or other resources to research your +solution, if desired. + + + + + diff --git a/modules/MulticoreProgramming/source/introOpenMP/images/image00.png b/modules/MulticoreProgramming/source/introOpenMP/images/image00.png new file mode 100755 index 0000000000000000000000000000000000000000..1c399caea3840cc0001aa63312da83f7917c4822 GIT binary patch literal 2557 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 T?YD=T00000NkvXXu0mjfXI%8z literal 0 HcmV?d00001 diff --git a/modules/MulticoreProgramming/source/introOpenMP/trap-omp-notworking.C b/modules/MulticoreProgramming/source/introOpenMP/trap-omp-notworking.C new file mode 100644 index 00000000..4326bedc --- /dev/null +++ b/modules/MulticoreProgramming/source/introOpenMP/trap-omp-notworking.C @@ -0,0 +1,46 @@ +#include +#include +#include +using namespace std; + +/* Demo program for OpenMP: computes trapezoidal approximation to an integral*/ + +const double pi = 3.141592653589793238462643383079; + +int main(int argc, char** argv) { + /* Variables */ + double a = 0.0, b = pi; /* limits of integration */; + int n = 1048576; /* number of subdivisions = 2^20 */ + double h = (b - a) / n; /* width of subdivision */ + double integral; /* accumulates answer */ + int threadct = 1; /* number of threads to use */ + + /* parse command-line arg for number of threads */ + if (argc > 1) + threadct = atoi(argv[1]); + + double f(double x); + +#ifdef _OPENMP + cout << "OMP defined, threadct = " << threadct << endl; +#else + cout << "OMP not defined" << endl; +#endif + + integral = (f(a) + f(b))/2.0; + int i; + +#pragma omp parallel for num_threads(threadct) \ + shared (a, n, h, integral) private(i) + for(i = 1; i < n; i++) { + integral += f(a+i*h); + } + + integral = integral * h; + cout << "With n = " << n << " trapezoids, our estimate of the integral" << + " from " << a << " to " << b << " is " << integral << endl; +} + +double f(double x) { + return sin(x); +} diff --git a/modules/MulticoreProgramming/source/introOpenMP/trap-omp-unfinished.C b/modules/MulticoreProgramming/source/introOpenMP/trap-omp-unfinished.C new file mode 100644 index 00000000..dacde9b5 --- /dev/null +++ b/modules/MulticoreProgramming/source/introOpenMP/trap-omp-unfinished.C @@ -0,0 +1,44 @@ +#include +#include +#include +using namespace std; + +/* Demo program for OpenMP: computes trapezoidal approximation to an integral*/ + +const double pi = 3.141592653589793238462643383079; + +int main(int argc, char** argv) { + /* Variables */ + double a = 0.0, b = pi; /* limits of integration */; + int n = 1048576; /* number of subdivisions = 2^20 */ + double h = (b - a) / n; /* width of subdivision */ + double integral; /* accumulates answer */ + int threadct = 1; /* number of threads to use */ + + /* parse command-line arg for number of threads */ + if (argc > 1) + threadct = atoi(argv[1]); + + double f(double x); + +#ifdef _OPENMP + cout << "OMP defined, threadct = " << threadct << endl; +#else + cout << "OMP not defined" << endl; +#endif + + integral = (f(a) + f(b))/2.0; + int i; + + for(i = 1; i < n; i++) { + integral += f(a+i*h); + } + + integral = integral * h; + cout << "With n = " << n << " trapezoids, our estimate of the integral" << + " from " << a << " to " << b << " is " << integral << endl; +} + +double f(double x) { + return sin(x); +} diff --git a/modules/MulticoreProgramming/source/introOpenMP/trap-omp.C b/modules/MulticoreProgramming/source/introOpenMP/trap-omp.C new file mode 100644 index 00000000..4326bedc --- /dev/null +++ b/modules/MulticoreProgramming/source/introOpenMP/trap-omp.C @@ -0,0 +1,46 @@ +#include +#include +#include +using namespace std; + +/* Demo program for OpenMP: computes trapezoidal approximation to an integral*/ + +const double pi = 3.141592653589793238462643383079; + +int main(int argc, char** argv) { + /* Variables */ + double a = 0.0, b = pi; /* limits of integration */; + int n = 1048576; /* number of subdivisions = 2^20 */ + double h = (b - a) / n; /* width of subdivision */ + double integral; /* accumulates answer */ + int threadct = 1; /* number of threads to use */ + + /* parse command-line arg for number of threads */ + if (argc > 1) + threadct = atoi(argv[1]); + + double f(double x); + +#ifdef _OPENMP + cout << "OMP defined, threadct = " << threadct << endl; +#else + cout << "OMP not defined" << endl; +#endif + + integral = (f(a) + f(b))/2.0; + int i; + +#pragma omp parallel for num_threads(threadct) \ + shared (a, n, h, integral) private(i) + for(i = 1; i < n; i++) { + integral += f(a+i*h); + } + + integral = integral * h; + cout << "With n = " << n << " trapezoids, our estimate of the integral" << + " from " << a << " to " << b << " is " << integral << endl; +} + +double f(double x) { + return sin(x); +} diff --git a/modules/MulticoreProgramming/source/timingAndScalability/OriginalHTMLFromGoogleDoc/OpenMPtimingonMTLandscalability.zip b/modules/MulticoreProgramming/source/timingAndScalability/OriginalHTMLFromGoogleDoc/OpenMPtimingonMTLandscalability.zip new file mode 100644 index 0000000000000000000000000000000000000000..3cc6d1c41bf5faa3f326f6e176919d4eefb73743 GIT binary patch literal 4760 zcma)=$f(iXoeBd)QhLBB0i~q71`-lOkWr2hVWZhVDd}#IkZzE2xi2ij`(4r#Xzr^+Lt>7G$>i{-r;El75uZ zl(GUK6mJoYk-WZayL?BBrvc|s<#(6VaZj49KWtk~%hUa*BrR{Hb6T5u+tj>u^yS|R z+(~`P0z)3di;KUcYt9`hqqg$Me>6s|E{3Z`zhw7)*bd|oxvXiJ46!O|Ejh$;eQtD6 zWjTD1pOQ7ihFT#wD2l;fd>G=CV;0kjXo?Q5CARfe4vERa{HeAFrEe!UbJ|~?{#dt) zX#O15_RyCTmZC5c8s^2mb`!Zd$;V78BEfVAFpq3?gjPhlymQuV1_IgpW6N0QeGC7Ag$ zgNX9y+nu9c4LzEkvxQc|^S(aqc!9rvEX^azImHx$Q|=6T3w7KN7hcGTExJs0BL z>6K+#T#hwYUqLr{4L)No;-HG0)2H^nU1=G}dir;7D>R1f#N~3PuIaS{c9kNLS-ogW z*6YRL6^=z#EBWL0!oqQDs6K(6jRkS}Np#y_Ts~`QSanQ1dKq0_Wt>D^I!e9 z98MEm6wOkB>^zaIFXfYDq@Ap+v#)UTVdC!xCmTg!8pIzbuDI6Y;g35AURLd7AzTmK z*_ zmA3TBWkH{*2$E3tjhP#A`Vh^pDrIvJF;)-aNE#VyUejl9)pXAYdzo2&U}b(Hayda7 zh-zinZL%sc`Jf*W-`Q-OZ)sH=Y2dc|kV?RbEQn1E_`v_@PGLg~Lqjz}Co+#68$>|h9Zyo9wXY*9c?$WzP>qecQ} z@c+0sQp_sMYHCxRy~{-l#9e1@W&D&*eJWr`LaO$3J~{Fdx=CPzV@@nP^CVJe;GT*h?=5pQ@vMwGAgHPyj!OM z)mS5HI!x&sl~KY_z+!l{=ESyxnBvm*(ih35b8x35gD-Kx2$~q5TwDZW>ffmY&p_}| zpUs{#(2fnySc%14gZ35p0D-P}TLv;%wnBc?QD%azPwP|d!zXeg4M8Vu8v6^o;L#ON z{<50nD6JcKCX^s%g-_sYvZ=#5hBX%DsD&p4Klw#G6liXJzNn^e=h+Oi@Od@JclKHj zGhM3Q(oIjX79+$Kk6ESK@$VAl6Xn=FwFjiP0rkb|ggxFg;jX*dbbtD3s4;TXw3*~9 zZ?$+B;Hk_M!^tBwPo&wv`bv5FkX)dW3jj0PV{+TlA*zdpCp@(YBUWslZ7fk?)2p*1o$ZDSCTFAWT`nZ)G4jeX*} zSIrHJ0yT&^YV)mu7q=^OXzXI2N6)M~G=viDKy<;r{ob`k;kii$u6>0u`;J)r_;l*y zMQNd6=~M}oHTK_rWthu!U0mL$eijiCKq1nQN3_e`7S^{OIp1od;KDv235n6od*R5RY=$1UIl z+ez!X^zN6BY~;&+wNt6{VvgqA4n4#fbwub8?fq;-<_e4uh*mBBxR)Ef$-q=W#jrK< zscIUSA|oubtrdl55{7$0P5bD>3~iy;YFv>A|5f@3VO57B8LvV(PH=kO`sJK(e%_w- zWYckF3nq5tD;1aQ&82Fr<@a)yT>O)g(43GdYm(Z?p{OQ2kT*;tRggGODb{jRm(l7q zG^n+3xPPRVTeow=E2x#G$G!1P`1cCks!xtfcC$DY9^EWbsNpNosBL@2yLPhVy6KzF z86tK9mfXl_zfbY=V~t3CyQZJSNFR>$__KO<$P80)1|(&1k3Sy3mhN zY;!DX0hsC3UeaNPlT#EA@n)eh`nf26OEKZMfxH@j;xEvd4N1B)>ipet_x#P#nLe3k zkF)iznQfj$b$5ef93K20&g++(60M?8sSRW%VIUqBEQ5$4^ukl@vu2=VcZ9Zz0+@tT z!FOLg!)6%W*~xYF4^1_%jcCukkxK^ig1Nm+FO*Yg@cP;6xfm_5ers-izD$l4m?KgW z%vtj(AQu8}Wam{HDCuY0=Ge$iG{PZNW#OJO?nY{U*idh^$%xNw2%QwjH+V2$=VSdw zxBpK?KV`sPmzao%SI(7F&K1<@MS1YuYOPQ4oytNMNm745-a5&F6*?tG4DAi6;OY)8 ztz8k->IPG6VNi4|@Y&VoqM{zRsK^_TRC#Yp{QL&DV5RXx0RYVB6Z`EDCT_u{ziS#X zKjg65eV^rF zX#rKe#nVdqx6dd)o4;{=#o!~F_^l_&h3lnJ?aW4QT|HDHN#cWU&9Cy;$}K%-IcH1x z{aV9zEY=E!o8xqmAnDHWajCvW7~^?X!z42_-px`F52ETA>zD0Z7}nuaDqAk#kEQ>+V*X6e>`g~vwH^=xaZ_?Wadt*05+n|;WvWKU-wI+3rAWpcS$yNq;ffUo-|ds@ z+Q*W0IQL{7vL4%2vWPu*t}t{t?QkI1Hk=vN~R^gfZ`6i*P6 zIZ3zw(|%cA0;A?&VLo0%B-zC`k4T733QQQS2oapDE-8f+IwueL!O(sI^8Otax67RY zuH-Dv?S6C!J%H`PwKY|IH7~p}>$!`q28oG(NmSt)X%yZutxG+8LdjPnNt8xrc}@N8 zF2PH(QS7tth9A_u;snV>wlDjDeNRj=C5s;vq0)#e~LJ@R^3P)2al&p zyHT!9OQuZXk;dMR`k04Mza^%YNLlJa9*xf`hsJ%@Qy|D*Zt|8V2<$K;n5KZfT1Ojo z%^Y74+i)%^^R|I79qQ9e>nI~e>#`a8QTmat=&>royj!^e^EZ(?VycpY1hay4wtW=7 zHf}rx!zSjo!x2u6Q~73vx#CuU-+OzPp25{gtF87qaUmaRR_;6Hr9C0 zUS+(vM{ACstoIV#Uw9ra-#04AG0fgua~ZW$nlBPV2YrTcbe($OhQbT7Mzf*a*Jd1r zcPi8}J6x6K{5x`GL76nEX(r!$uIi84ebrOhl%sj1#lEr34hJ~l)dAp5Pd-xBG3GvL zI8!Uiq28fvm}|8cZIh8}|GAO|)D~s--&UC+W8dHB4J}RmDd^{`KCqbJ`ozS|2K;ka zExHNb>nUW?uzTUmoo7&-`;>DKD}GOfUV1>0Gqih%h1msQWZH+(DwfGU8V8 z>$^vV=G`5RlN|IBPOE0{m?X$X*&zfs0qP)Cmx9e=OG9_{?F&V{KB!xp#4D>)Ino#L zdEDKp7Ac@avnt!rgTcB=5@xCXHrQl7ML_p~gzk+xh+>7E`h}3%#J+kPC3bMBKlX<# zhr{AgR>3B3JT^T4JD=Fok9R}(_Zl{c)Jn+%#wp}^vu~2v>Spojk4NTw4Yc6D7r#N=RnhDlghC*1wbBZ~zJ^yx#q?e_UF;b@ z2iC1%PT7%_v)c2ob&*i4_yeKSQE^egm3`ynw{XE$grK)d|JbPIX3jI` zk*J$$+@=Qn`^j%(%v*|WNQ-JDl-9DOEq$wW1(EWztW8rtgg4Ld;_oqc4Cs>&&y|jG zxEf9Jf)Iz5^Jc`m`OV-l`GSR-bYDe@^?OosLzBv3+K_lLsaQIMV_&DmMi$~!(S(!0 z_=J5l*RuMJXF<`_MtVM#bW5qwzB~DgJ)->L;c*T%w23^N%!7WkbgwMecI{I)7#b`7 z_3@g|!_Ry~1G#LrNn=~A^jf`K?D$+N@P~`NpHZBw)F3A*1;HO3k@0;igMy^dVX5#p zXH<;!%V1%t^W**PH1<&&8UErbocbey-c*tb;QA?zd4}-0Tje!FbIy&#Q{?%FHViri z@LhgZ+2fZsE+3#|f+!#J#UdN))zo64F7PQ&`*C}lt$th0>W=(#1(G6T}Iv&}BA-7LCT>8-MQDx3f31 z=X^&3fS>$%I%f?7Gw3aaez3lhwrd{Uh|R|T?FoK<;l(65T5WZmaOX%7dK|`c_bPaN ziB(;rMB98bXeHC}J_K$FBm7etowIYv1DT zxqEZhVuhi^*a5)R@|yn*Ikfd3^%|NZ+9G5Wvy|L`NIHa@|B PR*(L*!M`a+`JeVbD4_VV literal 0 HcmV?d00001 diff --git a/modules/MulticoreProgramming/source/timingAndScalability/OriginalHTMLFromGoogleDoc/TimingonMTLandscalability.html b/modules/MulticoreProgramming/source/timingAndScalability/OriginalHTMLFromGoogleDoc/TimingonMTLandscalability.html new file mode 100755 index 00000000..97c20532 --- /dev/null +++ b/modules/MulticoreProgramming/source/timingAndScalability/OriginalHTMLFromGoogleDoc/TimingonMTLandscalability.html @@ -0,0 +1 @@ +OpenMP timing on MTL and scalability

Using OpenMP: Timing and Performance on Intel Manycore testing lab

Timing performance

We would like to know how long it takes to run various versions of our programs so that we can determine if adding additional threads to our computation is worth it.  

There are several different ways that we can obtain the time it takes a program to run (we typically like to get this time in milliseconds or less).

1. Simple, less accurate way: We can obtain the running time for an entire program using the time Linux program. For example, the line

/usr/bin/time -p trap-omp

might display the following output:

OMP defined, threadct = 1

With n = 1048576 trapezoids, our estimate of the integral from 0 to 3.14159 is 2

real 0.04

user 0.04

sys 0.00

Here, we use the full path /usr/bin/time to insure that we are accessing the time program instead of a shell built-in command. The -p flag produces output in a format comparable to what we will see in the MTL.

The real time measures actual time elapsed during the running of your command trap-omp. user measures the amount of time executing user code, and sys measures the time executing in Linux kernel code.

Try the time command using your linux machine, and compare the results for different thread counts. You should find that real time decreases somewhat when changing from 1 thread to 2 threads; user time increases somewhat. Can you think of reasons that might produce these results?

Also, real time and user time increase considerably on some machines when increasing from 2 to 3 or more threads. What might explain that?

2.  Using OpenMP functions for timing code: The for loop in your trap-omp.C code represents the parallel portion of the code.  The other parts are the ‘sequential parts’ where one thread is being used.  Using functions to get current time at points in your program, you can begin to examine how long the sequential port takes in relation to the parallel portion.  You can also use these functions around all the code to determine how long it takes.

We can use an OMP library function whose ‘signature’ looks like this:

#include <omp.h>

double omp_get_wtime( void );

We can use this in the code in the following way:

// Starting the time measurement

double start = omp_get_wtime();

// Computations to be measured

...

// Measuring the elapsed time

double end = omp_get_wtime();

// Time calculation (in seconds)

double time1 = end - start;

//print out the resulting elapsed time

...

}

Try inserting this code and printing out the time it takes to execute portions of your trap-omp.C code.  You will use this for later portions of this activity.

3. You could use basic linux/C/C++ timing functions: See the end of this activity for an explanation of the way in which we can time any C/C++ code, even if you are not using OpenMP library functions or pragmas.

Using the MTL

Let’s try using many more threads and really experiment with multicore programming! You will need to use a ‘terminal’ on Macs or ‘Putty’ on PCs.  If you are off campus, you will want to log into selkie before then logging into the MTL machine at Intel’s headquarters in Oregon.  You can login to the MTL computer, as follows

 ssh accountname@192.55.51.81

Use one of the student account usernames provided to you, together with the password distributed to the class.

Next, copy your program from your laptop or selkie to the MTL machine. One way to do this is to logout (or preferably use another window to keep for copying your code), then enter the following command:

 scp trap-omp.C accountname@192.55.51.81:

After making this copy, login into the MTL machine 192.55.51.81 again.

On the MTL machine, compile and test run your program.

 g++ -o trap-omp trap-omp.C -lm -fopenmp

 ./trap-omp

 ./trap-omp 2

 ./trap-omp 16

Note: Since the current directory . may not be in your default path, you probably need to use the path name ./trap-omp to invoke your program.

Now, try some time trials of your code on the MTL machine. (The full pathname for time and the -p flag are unnecessary.) For example, using time:

 time trap-omp

 time trap-omp 2

 time trap-omp 3

 time trap-omp 4

 time trap-omp 8

 time trap-omp 16

 time trap-omp 32

What patterns do you notice with the real and user times of various runs of trap-omp with various values of threadct?

Also try it without using the time command on the command line and instead using the OpenMP omp_get_wtime() function calls in your code. 

Submitting Batch Jobs for Timing Accurately

To submit a job on MTL and guarantee that you have exclusive access to a nod for timing purposes, you submit your job to a queuing system.  You do this by creating a ‘script’ file that will be read by the queuing system.  Then you use the qsub command to run that script.  Here is an example of the contents of a script file (save it as submit.sh on your MTL account):

#!/bin/sh

#PBS -N LS_trap

#PBS -j oe

#$HOME/240activities/trap-omp 32 10485760

#here is how we can send parameters from job submission on the command line:

$HOME/240activities/trap-omp $p $n

#the job gets submitted like this:

#qsub -l select=1:ncpus=32 -v 'p=32, n=10485760' /home/mcls/240activities/submit.sh

######### end of script file

Here is an example of how you run the script (change the path for your user account):

qsub -l select=1:ncpus=32 -v 'p=32, n=10485760' /home/mcls/240activities/submit.sh

Investigating ‘scalability’

As you keep the same ‘problem size’, i.e. the amount of work being done, and increase the number of processors, you would hope that the time drops proportionally to the number of processors used.  So in your case of the problem size being the number of trapezoids computed, 220, are you able to halve the time as you double the number of threads?  When does this stop being the case, if at all?  When this occurs, your program is exhibiting strong scalability, in that additional resources (threads in this case) help you obtain an answer faster.  To truly determine whether you have strong scalability, you will likely need to try a larger problem size on the MTL.

An interesting set of experiments to try is to increase the problem size by changing the number of trapezoids to values higher than 220. Try this: if you double the problem size and double the number of threads, does the loop take the same amount of time?  In high performance computation, this is known as weak scalability:  you can keep using more processors (to a point) to tackle larger problems.

What happens when you have more threads than cores?

Another interesting investigation is to consider what happens when you ‘oversubscribe’ the cores by using more threads than cores available.  Try this experiment and write down your results and try to explain them.

An Alternative Method for Timing Code (optional; for reference)

The following code snippets can be used in your program to time sections of your program.  This is the traditional linux/C/C++ method, which is most likely what the implementation of the OMP function get_wtime() is using.

/* Put this line at the top of the file: */
#include <sys/time.h>

/* Put this right before the code you want to time: */
struct timeval timer_start, timer_end;
gettimeofday(&timer_start, NULL);

/* Put this right after the code you want to time: */
gettimeofday(&timer_end, NULL);
double timer_spent = timer_end.tv_sec - timer_start.tv_sec + 

                                 (timer_end.tv_usec - timer_start.tv_usec) / 1000000.0;
printf("Time spent: %.6f\n", timer_spent);

The for loop in your trap-omp.C code represents the parallel portion of the code.  The other parts are the ‘sequential parts’ where one processor, or thread is being used.  Using the above code snippets as a guide, you can begin to examine how long the sequential port takes in relation to the parallel portion.

\ No newline at end of file diff --git a/modules/MulticoreProgramming/source/timingAndScalability/TimingOnMTLandscalability.rst b/modules/MulticoreProgramming/source/timingAndScalability/TimingOnMTLandscalability.rst new file mode 100644 index 00000000..dc7b7967 --- /dev/null +++ b/modules/MulticoreProgramming/source/timingAndScalability/TimingOnMTLandscalability.rst @@ -0,0 +1,300 @@ +Timing and Performance on Intel Manycore Testing Lab +===================================================== + +Timing performance +------------------- + +We would like to know how long it takes to run various versions of our +programs so that we can determine if adding additional threads to our +computation is worth it.   + +There are several different ways that we can obtain the time it takes a +program to run (we typically like to get this time in milliseconds or +less). + +Simple, less accurate way: linux **time** program +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +We can obtain the running time for an +entire program using the *time* Linux program. For example, the line +:: + + /usr/bin/time -p trap-omp + +might display the following output: +:: + + OMP defined, threadct = 1 + With n = 1048576 trapezoids, our estimate of the integral from 0 to 3.14159 is 2 + + real 0.04 + + user 0.04 + + sys 0.00 + +Here, we use the full path /usr/bin/time to insure that we are accessing +the time program instead of a shell built-in command. The -p flag +produces output in a format comparable to what we will see in the MTL. + +The real time measures actual time elapsed during the running of your +command trap-omp. user measures the amount of time executing user code, +and sys measures the time executing in Linux kernel code. + +.. topic:: To Do + + Try the time command using your linux machine, and compare the results + for different thread counts. You should find that real time decreases + somewhat when changing from 1 thread to 2 threads; user time increases + somewhat. Can you think of reasons that might produce these results? + + Also, real time and user time increase considerably on some machines + when increasing from 2 to 3 or more threads. What might explain that? + +Additional accuracy: Using OpenMP functions for timing code +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The for loop in your +trap-omp.C code represents the parallel portion of the code.  The other +parts are the ‘sequential parts’ where one thread is being used (these portions of code are quite small in this simple example).  Using +functions to get current time at points in your program, you can begin +to examine how long the sequential port takes in relation to the +parallel portion.  You can also use these functions around all the code +to determine how long it takes with varying numbers of processors. + +We can use an OMP library function whose ‘signature’ looks like this: +:: + + #include + + double omp_get_wtime( void ); + +We can use this in the code in the following way: +:: + + // Starting the time measurement + + double start = omp_get_wtime(); + + // Computations to be measured + + ... + + // Measuring the elapsed time + + double end = omp_get_wtime(); + + // Time calculation (in seconds) + + double time1 = end - start; + + //print out the resulting elapsed time + cout << "Time for paralel computation section: "<< time1 << " milliseconds." << endl; + ... + +.. topic:: To Do + + Try inserting these pieces code and printing out the time it takes to execute + portions of your trap-omp.C code.  You will use this for later portions + of this activity. You can do this on your local linux machine now to test it out and make sure it is working. + + +Basic C/C++ timing functions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You could use basic linux/C/C++ timing functions: See the end of this +activity for an explanation of the way in which we can time any C/C++ +code, even if you are not using OpenMP library functions or pragmas. This will come in handy in cases where you are not using OpenMP (such as CUDA, for example). + +Using the MTL +------------- + +Let’s try using many more threads and really experiment with multicore +programming! You will need to use a ‘terminal’ on Macs or ‘Putty’ on +PCs.  If you are off campus, you will need to ssh into a machine on your campus before +then logging into the MTL machine at Intel’s headquarters in Oregon. + +.. !!!!!!!!!!!!!!!!! LOCAL CHANGE !!!!!!!!!!!!!!!!!!!!!!!!!! +.. note:: Macalester's local machine that you can use is selkie. + + +You can login to the MTL computer, as follows +:: + + ssh accountname@192.55.51.81 + +Use one of the special MTL student account usernames provided to you, together with +the password distributed to the class. + +Next, copy your program from your laptop or local linux machine to the MTL machine. +One way to do this is to use another window (to +keep for copying your code), then enter the following command *from the directory where your code is located*: +:: + + scp trap-omp.C accountname@192.55.51.81: + +After making this copy, login into the MTL machine 192.55.51.81 in another window. + +On the MTL machine, compile and test run your program. +:: + +  g++ -o trap-omp trap-omp.C -lm -fopenmp + +  ./trap-omp + +  ./trap-omp 2 + +  ./trap-omp 16 + +**Note:** Since the current directory . may not be in your default path, you +probably need to use the path name ./trap-omp to invoke your program. + +Now, try some time trials of your code on the MTL machine. (The full +pathname for time and the -p flag are unnecessary.) For example, using +time: +:: + +  time trap-omp + +  time trap-omp 2 + +  time trap-omp 3 + +  time trap-omp 4 + +  time trap-omp 8 + +  time trap-omp 16 + +  time trap-omp 32 + +What patterns do you notice with the real and user times of various runs +of trap-omp with various values of threadct? + +Also try it without using the time command on the command line and +instead using the OpenMP omp\_get\_wtime() function calls in your code.  + +.. topic:: To Do + + It may be useful to change our problems size, n, to see how this affects the time and to observe the range of times that can occur for various problem sizes. We therefore should eliminate 'hard coding' of n. + + Now update your code so that you submit the number of elements to compute as an additional command-line argument. Now the number of trapezoids, ``n`` should be set to the value in argv[2] (at the time that you set threadcnt to the value in argv[1]). **This also involves moving the declaration and assignment of the variable h, also**. The updated segment of code should look like this: + +.. literalinclude:: trap-omp-timed.C + :language: c++ + :lines: 18-25 + +.. note:: The best way to work is to change your code on your local campus machine and copy it to the MTL using scp. That way you have your own copy. + +Submitting Batch Jobs for Timing Accurately +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To submit a job on MTL and guarantee that you have exclusive access to a +nod for timing purposes, you submit your job to a queuing system.  You +do this by creating a ‘script’ file that will be read by the queuing +system.  Then you use the qsub command to run that script.  Here is an +example of the contents of a script file (save it as submit.sh on your +MTL account): +:: + + #!/bin/sh + + #PBS -N LS\_trap + + #PBS -j oe + + #here is how we can send parameters from job submission on the command + line: + + $HOME/240activities/trap-omp $p $n + + # this is a shell script comment + # the job gets submitted like this: + # qsub -l select=1:ncpus=32 -v 'p=32, n=10485760' + + /home/mcls/240activities/submit.sh + + ######### end of script file + +Here is an example of how you run the script (change the path for your +user account): +:: + + qsub -l select=1:ncpus=32 -v 'p=32, n=10485760' + /home/mcls/240activities/submit.sh + +Investigating ‘scalability’ +---------------------------- + +Scalability is the ability of a parallel program to run ingreasingly larger problems. In our simple program, the *problem size* is the number of trapazoids whose area are computed. You will now conduct some investigations of two types of scalability of parallel programs: + +* **stong scalability** +* **weak scalbility** + +Strong Scalability +^^^^^^^^^^^^^^^^^^ + +As you keep the same ‘problem size’, i.e. the amount of work being done, +and increase the number of processors, you would hope that the time +drops proportionally to the number of processors used.  So in your case +of the problem size being the number of trapezoids computed, :math:`2^{20}`, are +you able to halve the time as you double the number of threads?  When +does this stop being the case, if at all?  When this occurs, your +program is exhibiting *strong scalability*, in that additional resources +(threads in this case) help you **obtain an answer faster**.  To truly +determine whether you have *strong scalability*, you will likely need to +try a larger problem size on the MTL. + +Weak Scalability +^^^^^^^^^^^^^^^^ + +Another interesting set of experiments to try is to both increase the problem size +by changing the number of trapezoids to values higher than :math:`2^{20}` and to correspondingly increase the number of threads. Try +this: if you double the problem size and double the number of threads, +does the loop take the same amount of time?  In high performance +computation, this is known as *weak scalability*:  you can keep using more +processors (to a point) to **tackle larger problems**. + +.. note:: Don't try more than the maximum 40 cores on the MTL for the above tests. + +What happens when you have more threads than cores? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Another interesting investigation is to consider what happens when you +‘oversubscribe’ the cores by using more threads than cores available. + Try this experiment and write down your results and try to explain +them. + +An Alternative Method for Timing Code (optional; for reference) +---------------------------------------------------------------- + +The following code snippets can be used in your program to time sections +of your program.  This is the traditional linux/C/C++ method, which is +most likely what the implementation of the OMP function get\_wtime() is +using. +:: + + /* Put this line at the top of the file: */ + #include + +:: + + /* Put this right before the code you want to time: */ + struct timeval timer_start, timer\_end; + gettimeofday(&timer_start, NULL); + +:: + + /* Put this right after the code you want to time: */ + gettimeofday(&timer_end, NULL); + double time_spent = timer_end.tv_sec - timer_start.tv_sec +   + (timer_end.tv_usec - timer_start.tv_usec) / 1000000.0; + printf("Time spent: %.6f\\n", time_spent); + +.. note:: This example uses C printf statements; feel free to use C++ cout syntax, perhaps like this: + +:: + + cout << "Time for paralel computation section: "<< time_spent << " milliseconds." << endl; + + diff --git a/modules/MulticoreProgramming/source/timingAndScalability/trap-omp-timed.C b/modules/MulticoreProgramming/source/timingAndScalability/trap-omp-timed.C new file mode 100644 index 00000000..9f001bfb --- /dev/null +++ b/modules/MulticoreProgramming/source/timingAndScalability/trap-omp-timed.C @@ -0,0 +1,60 @@ +#include +#include +#include +#include +using namespace std; + +/* Demo program for OpenMP: computes trapezoidal approximation to an integral*/ + +const double pi = 3.141592653589793238462643383079; + +int main(int argc, char** argv) { + /* Variables */ + double a = 0.0, b = pi; /* limits of integration */; + int n = 1048576; /* number of subdivisions = 2^20 */ + double integral; /* accumulates answer */ + int threadct = 1; /* number of threads to use */ + + /* parse command-line arg for number of threads, n */ + if (argc == 2) { + threadct = atoi(argv[1]); + } else if (argc == 3) { + threadct = atoi(argv[1]); + n = atoi(argv[2]); + } + double h = (b - a) / n; /* width of subdivision */ + + double f(double x); + +#ifdef _OPENMP + cout << "OMP defined, threadct = " << threadct << endl; +#else + cout << "OMP not defined" << endl; +#endif + + integral = (f(a) + f(b))/2.0; + int i; + +double start = omp_get_wtime(); + +#pragma omp parallel for num_threads(threadct) \ + shared (a, n, h) reduction(+: integral) private(i) + for(i = 1; i < n; i++) { + integral += f(a+i*h); + } + + integral = integral * h; + +// Measuring the elapsed time +double end = omp_get_wtime(); +// Time calculation (in seconds) +double time1 = end - start; + + cout << "With n = " << n << " trapezoids, our estimate of the integral" << + " from " << a << " to " << b << " is " << integral << endl; + cout << "Time for paralel computation section: "<< time1 << " milliseconds." << endl; +} + +double f(double x) { + return sin(x); +}