Analízis és módszerek a generatív metaprogramozás támogatására nagyméretű C++ projektekben
A doktori értekezés tézisei 2014
Mihalicza József
[email protected]
Témavezető: Dr. Porkoláb Zoltán, docens Eötvös Loránd Tudományegyetem, Informatika Kar, 1117 Budapest, Pázmány Péter sétány 1/C ELTE IK Doktori Iskola Doktori program: Az informatika alapjai és módszertana A doktori iskola vezetője: Dr. Benczúr András akadémikus A doktori program vezetője: Dr. Demetrovics János akadémikus
Tartalomjegyzék Tartalomjegyzék
ii
1 Bevezető
1
2 Célok
2
3 A unity build elemzése
3
4 Template metaprogram diagnosztika
4
5 Típusmegőrző heap profilozó C++-hoz
7
6 Kihatás
8
Hivatkozások
8
ii
1
Bevezető
Generatív programrendszerek A generatív programozás egy megközelítés, melyben gondosan általánosított szoftvermodulok készülnek, majd ezen általánosított modulok konkrét variációit kombináljuk különböző helyzetek speficikus igényeinek kielégítésére. Egy programrendszer generatív, ha generatív programozást használ. Az átmenet az általánosított változatból a konkrét változatba lehet futási idejű, betöltési idejű, összeszerkesztési idejű, fordítási idejű, vagy ezek valamilyen keveréke. Ez az értekezés a fordítási idejű változatra korlátozódik, ahol a programkód részeit a produkciós eszközlánc állítja elő. A generatív programozás gyakori felhasználásai közé tartozik a DRY elv alkalmazása és a generikus szerkezetek. A DRY elv a “do not repeat yourself” (“ne ismételd önmagad”) rövidítése. Itt a lényeg a konzisztencia. Akármikor is többször szerepel ugyanaz az információ, a konzisztensen tartásuk egy implicit feladat, máskülönben hibák lépnek fel. A DRY nem csak egy változás konzisztens végigvitelének feladatát spórolja meg nekünk. Sok esetben, különösen amire a generatív programozás leginkább jó, egy hosszú, fárasztó, vagy akár emberileg gyakorlatilag kivitelezhetetlen folyamat automatizálható. A generikusok a DRY elv alkalmazásának egy nagyon közvetlen formája. Lehetővé teszik a programozó számára, hogy ugyanazt az algoritmust, vagy adatszerkezetet, vagy nagy programkomponenst egyszer írjon meg egy általános alakban, ami később konkrét helyzetekben újrahasznosítható, a szabad változókat aktuális értékekkel helyettesítve. Azokat a programokat, melyek más programokon dolgoznak, metaprogramoknak hívjuk. Generatív metaprogramozásról akkor beszélünk, ha a generatív programozást metaprogramok valósítják meg. Nagyméretű programrendszerek Objektív definíció nincs arra, hogy mit jelent a nagyméretű. Az általam adott definíció a következő: egy programrendszer nagyméretű, ha egy egyedüli programozó számára nem kivitelezhető, hogy a megvalósítás minden részletét ismerje. A zlib tömörítési könyvtár 13 ezer kódsorból áll 2 szerzővel. A Linux kernel 3.2 15 millió kódsor 1316 fejlesztő bevonásával. Daniel Chapman ezt írja [16]: “A kernel projekt hosszú ideje akkora méretre nőtt, hogy egyetlen fejlesztő sem lenne képes segítség nélkül megvizsgálni és kijelölni minden egyes változtatást.” A fordítási idő egy fontos szempont. Nem ritka, hogy egy nagyméretű rendszer fordítási ideje több óra. A fordítási idő a nemfunkcionális követelmények egy példája. A nemfunkcionális követelmények egy másik kategóriája a futási idejű teljesítmény: sebesség, reszponzivitás, processzorés/vagy memóriaterhelés, kizárólagos erőforrások használata stb. A telje1
sítményproblémák rendszerint alattomosak. A teljesítménymutatók ritkán romlanak el drasztikusan. Inkább jellemző rájuk, hogy lassú tendenciával fokozatosan romlanak. A kód megértése szintén egyre fontosabb, ahogy a méret nő. Ez a szükséglet minden absztrakciós szinten jelentkezik: jó nevezéktan a forráskód elemeire, design átnézés, főbb architekturális döntések kommunikációja. Ha a kommunikáció nem elégséges, hasonló problémákra szerteágazó megoldások születnek. Ez nem csak inkonzisztens designhoz vezet, hanem tovább növeli a karbantartandó kódbázist, ennek mindazon hosszú távú következményével, melyeket ez a szakasz felsorol.
2
Célok
Láttuk, hogy a nagyméretű rendszereknél számos új kérdés merül fel a kisebbekhez képest. Ez természetesen a nagyméretű generatív rendszerekre szintén igaz. A téziseim egyes területekre fókuszálnak. Általános kérdésekre általános válaszok adása helyett a célom készen alkalmazható módszerek, megközelítések nyújtása volt néhány kiválasztott mindennapos ipari problémára. A teljes fordítási idő jelentősen csökkenthető unity builddel, ami egy, a szokásostól nagyon eltérő fordítási megközelítés, ahol az eredetileg külön fordított egységek együtt fordulnak. A célom a unity build nagy C++ projekteken történő alkalmazásának kiértékelése volt, valamint a technika alapjainak megteremtése részletes analízissel. A unity build elég hatékonyan csökkentie a fordítási időt, hogy ez kárpótolja a szokványostól eltérő, ezáltal hibára hajlamos használatát a nyelvnek? A unity build alkalmazása milyen lehetséges mellékhatásokkal jár? Hogyan kerülhetjük el a unity build nemkívánt mellékhatásait? Két évtizede volt, amikor az első template metaprogramot publikálták. Kiderült, hogy a template metaprogramozás (TMP) C++-ban valójában egy Turing-teljes beágyazott funkcionális nyelv. Míg a diagnosztikai funkciók természetes részei a legtöbb programozási környezetnek, ez nem volt igaz a C++ TMP-ra. Még sok év, és a széleskörű használat után sem volt kényelmes eszközkészlet, ami támogatná a TMP-t. A célom módszerek megtalálása volt template metaprogramok ugyanolyan természetes módon történő nyomkövetésére és profilozására, mint a futási idejű programoknál. Létezik megbízható módja információ kinyerésének futó template metaprogram lépéseiről a fordító módosítása nélkül? Milyen módszerek tudnák ugyanazokat a nyomkövető funkcionalitásokat nyújtani TMP-ra, amiket megszoktunk a futási idejű programoknál (lépésenkénti végrehajtás, töréspontok, hívási ve2
rem)? Milyen módszerek tudnának memóriahasználat és futási idő profilozást nyújtani TMP-ra? A memóriaviselkedés hatékony elemzéséhez egy típusos heap profilozó elengedhetetlen. Más OO nyelvekkel ellentétben a C++ közvetlenül nem támogatja a lefoglalt objektumok típusának követését. A célom teljes megoldás nyújtása volt nagyméretű C++ programok heap profilozására. A fordító módosítása nélkül van-e megbízható mód az allokációk típusinformációinak megőrzésére, hogy azokat C++ heap profilozóban használjuk? Ha részletes információkkal rendelkezünk az alkalmazásunk allokációiról — beleértve az objektumtípusokat — hogyan tudjuk ezt az információt a heap viselkedés részletes elemzésére használni? Milyen algoritmusok tudják biztosítani egy elemző alkalmazás gördülékenységét és reszponzivitását, ha az elemzett heap viselkedés adatot hosszú programfutásból gyűjtöttük, ezért hatalmas?
3
A unity build elemzése
A nyílt forrású könyvtárakon végzett méréseim szerint a feldolgozott forráskód mérete jelentősen nagyobb az érdemi kód méreténél: #include ... : #include ... érdemi kód
+2% 100%
preprocesszor
ÝÝÝÝÝÝÝÑ +29000% 100%
«
előfeldolgozott include fájlok érdemi kód
A unity build több, eredetileg különálló egység egyetlen fordítási egységként történő fordításával kiküszöböli ugyanazon fejlécfájlok különböző forrásegységekbeli többszörös feldolgozását: forrás1 : forrásn
, / .
script, vagy
ÝÝÝÝÝÝÑ / -
manuálisan
#include "forrás1 " összeszerkesztő ˝ fordító : ÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÑ cél ˝ preprocesszor #include "forrásn "
A méréseim megmutatták, hogy ez az egyesítés még akkor is képes jelentősen csökkenteni a fordítási időt, ha a projekt már előfordított fejléceket használ a gyorsabb fordításért. Következésképpen a módszer releváns nagy rendszereknél. Felsoroltam a lehetséges mellékhatásokat, melyeket az eredetileg különálló fordítási egységek egybe #include-olása okoz. Például az eredetileg lokális (static) szimbólumok hirtelen láthatóvá válnak nem kapcsolódó egységekben, vagy lokálisan bevezetett preprocesszor makróknak mellékhatásai lehetnek idegen egységekben. 3
Ekvivalenciakritériumok egy halmazát fogalmaztam meg annak meghatásozására, hogy a unity builddel fordított kódnak van-e bármilyen észrevétlen mellékhatása az egyenként fordított változathoz képest: EQ :“ EQu ^ EQt ^ EQa Az EQu az egység (unit) szintű konzisztenciát ellenőrzi, biztosítja, hogy ugyanazon egységek forduljanak. EQt teszteli, hogy a megfelelő modulok tokensorozatai pontosan egyeznek-e a két fordítási változatban. EQa , a legerősebb ellenőrzés, absztrakt binding fák részstruktúráit hasonlítja, hogy elkerülje az érvényben maradt using deklarációk okozta mellékhatásokat, vagy bármilyen más, alig észrevehető szemantikus különbséget. Tézis 1 (A unity build elemzése). Elemeztem a unity build technikát, és megmértem a hatékonyságát a fordítási idő csökkentését illetően, elsősorban a teljes fordítási időre fókuszálva. Három nyíltforrású könyvtáron végzett esettanulmány alapján meghatároztam a unity build létező projektekre alkalmazásának előnyeit és hátrányait, valamint ajánlott megközelítéseket vezettem le a megvalósítására. Egy kritériumhalmazt definiáltam, mellyel az egyesítés okozta nemkívánt szemantikus változások automatikusan detektálhatók. Levezettem továbbá egy algoritmust ezen ekvivalenciaellenőrzés megvalósítására.
4
Template metaprogram diagnosztika
A TMP diagnosztika támogatásához valahogy információt kell kapnunk a fordítótól a template példányosításokról a fordítás során. Unruh prímszámgenerátora egy template metaprogram, ami fordítási időben írja ki a prímszámokat figyelmeztetések formájában. Ez azt jelenti, hogy van egy módszerünk egy template metaprogramból információ kiíratására anélkül, hogy leállítanánk a végrehajtását. Részletesen kidolgoztam ezt a módszert, és egy teljes munkafolyamatot készítettem a fordítás TMP eseményeit leíró pozícióhelyes trace fájl kinyerésére. Az 1. ábra az általam Templightnak elnevezett keretrendszer struktúráját mutatja. A folyamat a forráskódbeli template szerkezetek automatikus azonosításával kezdődik (annotáció). Aztán figyelmeztetést kibocsátó kódrészleteket instrumentálunk minden egyes template definíció elejére és végére. Amikor a fordító példányosít egy template-et, ezek a kódrészletek figyelmeztetést eredményeznek a fordítási kimeneten, ezzel jelezve a template esemény megtörténtét. Ezeket a template eseményeket rekonstruáljuk a fordítási kimenetből, és kiírjuk a trace fájlba. 4
eredeti forrás
preprocesszor előfeldolgozott forrás
/
#line szűrő
fájl/sor leképezés
annotáló
annotáció
o
változatlan előfeldolgozott forrás #line direktívák nélkül
/
/
instrumentáló
instrumentált kód
sor leképezés
fordító
fordítási üzenetek '
figyelmeztetéselemző
trace fájl
v
pozícióigazítás
x
eredeti fordítófüggetlen fordítófüggő
pozícióhelyes trace fájl 1. ábra. Templight vezérlésfolyam. A szaggatott vonal opcionális kapcsolatot jelöl. Tézis 2 (Template metaprogram instrumentálás). Kifejlesztettem egy módszert C++ template metaprogramok instrumentálására. A módszer alapkoncepciója a template szerkezetek mintaalapú felismerése és figyelmeztetést generáló kódrészletek beszúrása. A módszer alapján megvalósítottam egy prototípus keretrendszert, aminek kimenete egy olyan pozícióhelyes trace fájl, mely tartalmaz minden szükséges információt a teljes TMP futás rekonstruálásához. A keretrendszer könnyen adaptálható új fordítási környezetekre. 5
Amikor egy ideális TMP nyomkövetőn gondolkodtam, először felsoroltam a futási idejű programok gyakran használt hibakeresési műveleteit. Analógiát találtam a TMP végrehajtás és a futási idejű végrehajtás között, és ezeket a hibakeresési műveleteket leképeztem a TMP-ra. Ezen analógiának megfelelően meghatároztam egy TMP nyomkövető műveleteit. A TMP tulajdonságai alapján (például a hivatkozási átlátszóság, ami abból ered, hogy a TMP egy funkcionális nyelv, ahogyan ez már a 2. szakaszban szerepelt) levezettem, hogy a visszafelé nyomkövetés és közvetlen időugrás műveletek megvalósíthatók egy TMP nyomkövetőben. A hordozhatóbb, 2-es Tézisbeli instrumentálás alapú módszer mellett a teljesebb, de a fordító módosításával járó változatot is tárgyaltam. Három különböző TMP nyomkövető alkalmazást mutattam be: egy IDE kiegészítést [8], egy különálló TMP IDE-t [3], és egy különálló TMP nyomkövetőt [14]. Tézis 3 (Módszerek template metaprogram hibakeresésre). Két módszert fejlesztettem ki C++ template metaprogram nyomkövető megvalósítására. Mindkét módszer trace fájlon alapul, ami leírja a template eseményeket. A nemintruzív adatkinyeréses módszer mellett, ami az 1-es Tézisben szereplő instrumentáló technikát alkalmazza, bemutattam egy másikat, ami a fordító módosításán alapszik, és néhány előnnyel rendelkezik a nemintruzív változattal szemben. Három alkalmazást ismertettem, melyek bemutatják, hogyan alkalmazhatóak a módszereim template hibakereső funkciók megvalósítására. A kutatásomon alapuló egyik megvalósítás de facto sztenderddé válik template metaprogramok hibakeresésére [14, 17, 18, 19, 20]. TMP profilozásra egy létező és három új módszert azonosítottam: FC teljes fordítási idő mérése WO figyelmeztetésgeneráló kódrészletek instrumentálása és folyamaton kívüli időmérés WI figyelmeztetésgeneráló kódrészletek instrumentálása és folyamaton belüli időmérés BI a profilozás beépített támogatása Az FC esetén elemeztem a külön preprocesszálás, mint lehetséges továbbfejlesztés hatásait. Leírtam a BI módszer részleteit, a minimális torzítás elérésére irányuló implementációs megfontolásokkal. Ezután BI módszerrel végzett méréseket mutattam be a WI és WO módszerek különböző szempontjainak, valamint a hibakereső és termékkiadási mód fordítási idejének elemzése céljából.
6
Tézis 4 (Módszerek template metaprogram profilozásra). Egy létező és három új módszert azonosítottam C++ template metaprogram profilozók megvalósítására, melyek különböző erősségekkel rendelkeznek. Részletes mérésekkel alátámasztva rámutattam ezen módszerek gyakorlati alkalmazásának fontos aspektusaira. A kutatásomon alapuló egyik megvalósítás de facto sztenderddé válik template metaprogram profilozásra [14, 18, 19, 20].
5
Típusmegőrző heap profilozó C++-hoz
A memóriaprofilozók kulcsfontosságú eszközök a modern objektumorientált programok futási idejű viselkedésének megértéséhez. Bár a C++-hoz hasonló nyelvek erősen építenek a típusokra és osztályokra, a legtöbb C++ memóriaprofilozó nem ad elégséges információt a memóriafoglalások tényleges típusáról. Bemutattam egy típusmergőrző C++ heap profilozó felépítését és adatszerkezeteit. A keretrendszerem a megszokott profilozó funkciókon felül minden heap művelet részletes típusinformációját is szolgáltatja. A fontos implementációs részleteket, valamint a lehetséges csapdákat és azok megoldását egyaránt tárgyaltam. A kód megértését támogatandó egy szűrőgráf lehetőséget ad a felhasználónak tetszőleges lekérdezések meglehetősen kényelmes módon történő összeállítására. A profil eredmény vizsgálatára különböző megjelenítési lehetőségek állnak rendelkezésre. A megoldásom magasan platformfüggetlen, emellett mérsékelt memória- és sebességköltséggel jár. Az idővonal nézet az alkalmazás teljes memóriafoglalását jeleníti meg az idő függvényében. Ez a nézet a gyakorlatban nagyon hasznos, de hatékony algoritmusokat igényel, hogy elbírjon a hatalmas mennyiségű aggregált bejegyzéssel. Valós használati statisztikákat használtam a fő optimalizálási célok meghatározására, és aszimptotikusan optimális algoritmusokat mutattam az idővonal megjelenítésre. A kumulált értékek megjelenítését célzó algoritmusom képes foglalási bejegyzések millióinak, és jócskán 1010 feletti megjelenített bejegyzés kezelésére. Esettanulmányként a keretrendszert használtam egy teljesítményhiba megtalálására és javítására egy számomra új és ismeretlen kódbázisban. Tézis 5 (Típusmegőrző heap profilozó). Kifejlesztettem egy módszert típusinformációk megőrzésére C++ heap profilozókban. A módszer platformfüggetlen, és alacsony memóriaköltséggel jár. Optimális algoritmusokat adtam a teljesítménykritikus idővonal nézet megjelenítésére. Egy nyílt forrású szövegszerkesztő teljesítményhibájának beazonosításával és javításával szemléltettem a megközelítés valós projekteken történő alkalmazhatóságát. 7
tézis neve A unity build elemzése Template metaprogram instrumentálás Módszerek template metaprogram hibakeresésre Módszerek template metaprogram profilozásra Típusmegőrző heap profilozó
6
releváns publikációk [1] [2] [3] [4] [5] [6] [7] [8] [9] [10][11][12] • • • • • • •
• • •
• • • • • • • • • •
• • • •
Kihatás
A kutatásom relevanciáját az akadémiai közleményekben található, és a fejlesztői közösségek általi idézések szemléltetik. A publikációimra 14 független idézést találtam. 8 publikációmra 10 munkában további 18 idézés található a kutatótársaimtól. Mindösszesen 32 idézés hivatkozik 9 publikációmra, lefedve mind az öt tézist. A Templar TMP nyomkövető és profilozó [14], ami a TMP diagnosztikai módszereimen alapul, egyre népszerűbb. [18]-ban elérhető egy gdb-szerű parancssoros felület TMP nyomkövetésre. Ez a nyomkövető a Template Instantiation Observert (Template Példányosítás Figyelő) használja, ami a korábbi Templight implementáció egy általánosítása, a clang egy kiegészítése template példányosítási események monitorozására. A clang megfelelő módosításai (Templight trace generálás [19] és a figyelő [20]) várhatóan a fordító hivatalos funkcióivá válnak, ezáltal a módszert de facto sztenderddé teszik. Egyéb fordítók és IDE-k esetén TMP nyomkövetésre és profilozásra vonatkozó funkciókéréseket találtam, melyek a megfelelő publikációimra hivatkoznak. A heap profilozómat a Hungarian C++ Community (Magyar C++ Közösség) a nyitó meetup-témájának [15] választotta. Egy friss munka [21] szerzői a publikációmat az egyetlen profilozóként idézik, amiről tudják, hogy részletes típusinformációt szolgáltat C-hez.
Hivatkozások [1] J. Mihalicza, “Compile C++ systems in quarter time,” in Proceedings of 10th International Scientific Conference on Informatics (Informatics’2009), 2009, pp. 136–141. 8
[2] J. Mihalicza, “How #includes affect build time in large systems,” in Proceedings of the 8th international conference on applied informatics (ICAI 2010), vol. 2. Eger: BVB Nyomda és Kiadó Kft., 2012, pp. 343–350. [3] Z. Borók-Nagy, V. Májer, J. Mihalicza, N. Pataki, and Z. Porkoláb, “Visualization of C++ template metaprograms,” in Proceedings of the 2010 10th IEEE Working Conference on Source Code Analysis and Manipulation, ser. SCAM ’10. Washington, DC, USA: IEEE Computer Society, 2010, pp. 167–176. [Online]. Available: http://dx.doi.org/10.1109/SCAM.2010.16 [4] Z. Porkoláb, J. Mihalicza, and Á. Sipos, “Debugging C++ template metaprograms,” in Proceedings of the 5th International Conference on Generative Programming and Component Engineering, ser. GPCE ’06. New York, NY, USA: ACM, 2006, pp. 255–264. [Online]. Available: http://doi.acm.org/10.1145/1173706.1173746 [5] J. Mihalicza, “C++ template metaprogramok nyomkövetését segítő programcsomag,” Master’s thesis, Eötvös Loránd Tudományegyetem, 2006. [6] N. Pataki, J. Mihalicza, Z. Szűgyi, V. Májer, and Z. Porkoláb, “Features of C++ template metaprograms,” in Proceedings of the 8th international conference on applied informatics (ICAI 2010), vol. 2. Eger: BVB Nyomda és Kiadó Kft., 2012, pp. 451–451. [7] Z. Porkoláb, J. Mihalicza, Á. Sipos, and N. Pataki, “Templight, a template metaprogram debugger,” 2007, poster at European Conference on Object-Oriented Programming (ECOOP’07). [8] Z. Porkoláb, J. Mihalicza, Á. Sipos, and N. Pataki, “Templight, a template metaprogram debugger,” 2007, demonstration at European Conference on Object-Oriented Programming (ECOOP’07). [9] Z. Prokoláb, J. Mihalicza, N. Pataki, and Á. Sipos, “Towards profiling C++ template metaprograms,” in Proceedings of the 10th Symposium on Programming Languages and Software Tools (SPLST’07). Eötvös University Press, 2007, pp. 96–111. [10] Z. Porkoláb, J. Mihalicza, N. Pataki, and Á. Sipos, “Analysis of profiling techniques for C++ template metaprograms,” Annales Universitatis Scientiarum Budapestinensis de Rolando Eötvös Nominatae. Sectio Computatorica, vol. 30, pp. 97–115, 2009. 9
[11] J. Mihalicza, N. Pataki, and Z. Prokoláb, “Compiler support for profiling C++ template metaprograms,” in Proceedings of the 12th Symposium on Programming Languages and Software Tools (SPLST’11), oct 2011, pp. 32–43. [12] J. Mihalicza, Z. Porkoláb, and Á. Gábor, “Type-preserving heap profiler for C++,” in Proceedings of the 2011 27th IEEE International Conference on Software Maintenance, ser. ICSM ’11. Washington, DC, USA: IEEE Computer Society, 2011, pp. 457–466. [Online]. Available: http://dx.doi.org/10.1109/ICSM.2011.6080813 [13] Z. Prokoláb, Z. Borók-Nagy, and J. Mihalicza, “Debugging and profiling C++ template metaprograms,” http://github.com/boostcon/cppnow_ presentations_2013/blob/master/thu/tmpdebug_cppnow13.pdf?raw“ true, May 2013, presentation at C++Now Conference. [14] Z. Prokoláb, J. Mihalicza, and Z. Borók-Nagy, “Templight: A C++ template metaprogram debugger and profiler,” http://plc.inf.elte.hu/ templight/, 2014. [15] J. Mihalicza, “Type-preserving heap profiler for C++,” http: //www.meetup.com/Hungarian-Cpp-Community/events/205167032/, Budapest, Hungary, September 2014, presentation at Hungarian C++ Community Meetup. [16] D. Chapman, “How to participate in the linux community,” http://www. linuxfoundation.org/content/23-how-patches-get-kernel, May 2011. [17] M. Schulze, “Templar: Visualization tool for Templight C++ template debugger traces,” http://github.com/schulmar/Templar, Feb 2014. [18] S. M. Persson, “Templight 2.0: Template instantiation profiler and debugger,” http://github.com/mikael-s-persson/templight, October 2014. [19] M. Clow, “Template tracing patch from Zoltan Porkolab,” http:// reviews.llvm.org/D809, May 2013. [20] S. M. Persson, “Template Instantiation Observer + a few other templight-related changes,” http://reviews.llvm.org/D5767, October 2014. [21] K. Saur, M. Hicks, and J. S. Foster, “C-Strider: Type-aware heap traversal for C,” May 2014.
10