• No results found

Tolkning och rendering av L-system med beräknings-shaders

N/A
N/A
Protected

Academic year: 2022

Share "Tolkning och rendering av L-system med beräknings-shaders"

Copied!
42
0
0

Loading.... (view fulltext now)

Full text

(1)

TOLKNING OCH RENDERING AV L-SYSTEM MED

BERÄKNINGS-SHADERS

INTERPRETATION AND RENDERING OF L-SYSTEMS WITH COMPUTE

SHADERS

Examensarbete inom huvudområdet Informationsteknologi Grundnivå 30 högskolepoäng

Vårtermin 2020 Arvid Enochsson

Handledare: Mikael Johannesson Examinator: Henrik Engström

(2)

Sammanfattning

I detta arbete utförs en jämförelse av exekveringstid för tolkning och rendering av Lindenmayersystem (L-system), utfört av samma algoritm på CPU’n (eng. central processing unit) och GPU’n (eng. graphics processing unit). Undersökningen fokuserar på exekveringstid och dess utveckling vid olika typer av L-system, längder samt upplösning.

Algoritmen implementerades i spelmotorn Unity både på CPU’n via C# och på GPU’n med hjälp av beräknings-shaders. Experimenten bestod av två L-system. Ett med förgreningar och ett utan. Båda L-systemen testades med olika upplösningar och längder, varpå exekveringstid avlästes.

Experimentens resultat påvisar stor fördel hos CPU-lösningen gällande exekveringstid.

Bristen på GPU-anpassad kod, exempelvis kod utan förgreningar och med bra minneshantering, leder till att GPU’n presterar sämre. Arbetet avslutas med förslag på vidare forskning. Förslagen innefattar utökad testning på olika hårdvara, en mer kontrollerad testmiljö samt implementering av algoritmen i faktisk spelproduktion.

Nyckelord: L-system, Lindenmayersystem, beräknings-shaders, vegetation.

(3)

Innehållsförteckning

1 Introduktion ……….​1

2 Bakgrund………..​2

2.1 Procedurellt genererat material ……….​2

2.2 L-system………....​2

2.2.1 DOL-system ………...​3

2.3 Rendering av L-system ………...​3

2.3.1 Sköldpaddsgrafik ………..​3

2.3.2 Beräknings-shaders………..​4

2.4 Implementerade L-system………..​4

2.4.1 Enkla metoder ………...​5

2.4.2 Avancerade metoder ………....​5

3 Problemformulering………..​6

3.1 Metodbeskrivning ……….​6

3.1.1 Ramverk ……….​7

3.1.2 Experiment ……….​7

3.1.3 Utvärdering……….​8

3.1.4 Metoddiskussion ………...​9

3.1.5 Hypotes ………....​10

4 Genomförande………..​11

4.1 Implementering av algoritm………..​11

4.1.1 Tolkning utförd av CPU’n………...​11

4.1.2 Rendering utförd av CPU’n ………...​12

4.1.3 Tolkning utförd av GPU’n ..………...​12

4.1.4 Rendering utförd av GPU’n ………...​13

4.2 Pilotutvärdering ………..​13

4.2.1 Genomförande av pilotutvärdering ………..​14

4.3 Analys av exekveringstid………..​16

4.3.1 CPU-analys………..​16

4.3.2 GPU-analys ……….​17

5 Utvärdering ………...​19

5.1 Presentation av undersökning ……….​19

5.1.1 Endast tolkning ………...​20

5.1.2 Tolkning och rendering...………...​22

5.1.3 Minnesförbrukning ………..​24

5.2 Analys………..​25

5.2.1 Endast tolkning ………...​25

5.2.2 Tolkning och rendering…...………...​25

5.2.3 Minnesförbrukning ………..​27

5.3 Slutsatser………....​27

(4)

6 Avslutande diskussion………....​29

6.1 Sammanfattning ……….​29

6.2 Diskussion ………..​29

6.2.1 Etiska aspekter ………...​30

6.2.2 Samhälleliga aspekter ………...​30

6.3 Framtida arbete………..​31

Referenser ………..​32

(5)

1 Introduktion

Stora, öppna och levande världar används frekvent i högproduktions-spel. Genom en snabb sökning på de bäst-säljande spelen de senaste åren hittas bland annat The Legend of Zelda:

Breath of the Wild (2017) och Red Dead Redemption 2 (2018) (Grubb, 2020), vilka båda innehåller massiva levande världar. Enligt Vlad Grigore och Sabou (2017) är det dock tidskrävande att skapa material av denna magnitud. En tillgänglig metod för att lösa detta problem är procedurell generering som har möjligheten att generera oändligt med material, med mer eller mindre involvering från spelutvecklarna.

L-system är en metod som kan generera procedurellt material. Den lämpar sig speciellt bra för generering av bland annat gräs då metoden skapades för att just modellera växters topologi (Prusinkiewicz och Lindenmayer, 1990). Metoden grundar sig på omskrivning där ett antal regler bestämmer hur strukturen utvecklas över ett antal iterationer. Problem uppstår dock då beräkningstiden för att generera och rendera stora L-system ofta är lång.

Detta är ett problem som kräver smarta lösningar och optimeringar, speciellt för att kunna köras i realtid.

Problemet som undersökningen hanterar är hur tolkning och rendering av L-system med en enklare algoritm presterar på CPU’n och GPU’n. Undersökningen grundar sig i den metod som bland andra Hamon, Richard, Richard, Boumaza & Ferrier (2012) använt för tolkning, vilken innebär att översätta varje tecken i L-systemet via en omkopplare (eng. switch) till instruktioner för utritning. Metoden implementerades på GPU’n samt CPU’n och jämfördes för att resultera i svar på Anderssons (2016) funderingar kring om tolkning och rendering av L-system kan vara snabbare att göra på GPU’n än på CPU’n.

För att samla mätvärden för undersökningen implementerades ovanstående algoritm både på GPU’n och CPU’n i spelmotorn Unity (2020). Lösningarna utvecklades till att bli lika varandra där renderingen utgör den största skillnaden. CPU-lösningens rendering går iterativt igenom alla pixlar som ska färgläggas. GPU-lösningen exekverar algoritmen parallellt för varje pixel, vilket utnyttjas i form av att varje pixel projiceras på linjerna som ska ritas. Pixeln färgas alltså om avståndet är tillräckligt kort.

(6)

2 Bakgrund

2.1 Procedurellt genererat material

Procedurellt genererat material innebär att spelet, genom algoritmer skapar material. Bland annat landskap, texturer och fiender. Enligt Vlad Grigore och Sabou (2017) kan skapandet ske antingen i förtid eller realtid. Material skapat i förtid innebär att det genereras under spelets utveckling medan generering i realtid sker under spelets exekvering. Det finns för- och nackdelar med båda metoderna. En positiv aspekt när material skapas i förtid är att justeringar kan ske manuellt efter genereringen och kräver därför inte perfektion från start. I realtid då material genereras under spelets körtid har utvecklarna inte möjlighet att rätta till fel som algoritmer kan producera. Material som genererats i realtid kan dock tillverkas i oändlighet på grund av utebliven manuell påverkan.

I realtid kan det dock vara svårt att generera material i fråga om prestanda. Många av dagens spel använder 06 bildrutor-per-sekund som prestandamål. Valet av prestandamål grundar sig till viss del på att få skärmar uppdateras långsammare än 06 gånger per sekund (Bakaus, 2015). Resultatet blir att varje bildruta skall utföras på cirka 6, 71 6 millisekunder. Det kan bli svårt att hinna med genereringen då hela spelet ska hinna beräknas under den tiden, vilket oftast innehåller mycket mer än det genererade materialet.

Vegetation är ett område som kan kräva generering i realtid. Växter, träd och dylikt har ofta liknande egenskaper samtidigt som varje planta kan urskilja sig. Att generera växterna i realtid är av stor betydelse då Prusinkiewicz, Lindenmayer och Hanan (1988) belyser att utvecklingsprocessen som kan återskapas via realtid är viktig för att åstadkomma ett mer realistiskt resultat. För att procedurellt generera realistiskt material lämpar sig L-system bra.

En anledning till detta är då L-systemets ursprungliga syfte var att försöka modellera olika växters topologi (Prusinkiewicz och Lindenmayer, 1990).

2.2 L-system

Prusinkiewicz och Lindenmayers (1990) arbete ligger som grund för många verk som behandlar L-system. I verket förklaras genomgående konceptet kring L-system där det klargörs att omskrivning är det mest centrala begreppet. Det går alltså ut på att över ett antal iterationer skriva om en följd tecken, en sträng, där varje tecken representerar instruktioner för utritning.

För att generera ett L-system används ett axiom samt omskrivningsregler. Axiomet är strängen i sin nuvarande form. Detta axiom ersätts efter varje iteration och representerar därefter hela systemet. Den andra väsentliga delen av L-system är omskrivningsregler.

Reglerna beskriver hur varje tecken skall ersättas, det vill säga vilket tecken eller sträng som kommer att ersätta diverse tecken i axiomet. Genom att iterera och ersätta varje tecken i axiomet med omskrivningsreglerna skapas ett allt mer komplext system för varje iteration.

(7)

Resultatet blir att med relativt enkla axiom och omskrivningsregler kan komplexa L-system skapas på grund av dess rekursiva egenskap.

2.2.1 DOL-system

DOL-system är deterministiska (D) samt kontextfria (O) L-system (Prusinkiewicz och Lindenmayer, 1990). Detta betyder att dessa L-system är av den enklaste typen där det enda som sker genom iterationerna är omskrivningsreglerna. Systemet inkluderar därmed varken slump, kontext beroende av tidigare eller kommande tecken, parametrar eller förgreningar.

Nedan följer ett exempel på hur ett DOL-system utvecklas efter tre iterationer.

Axiom: ​a

Omskrivningsregler: ​a -> ba ​b -> aab Iteration:0 -> a

1 -> ba 2 -> aabba

3 -> babaaabaabba

För att modellera strukturer likt växtlighet behöver L-systemet populeras med ett antal tecken som representerar diverse instruktioner för utritning. Exempel på dessa tecken innefattar bland annat rotation, förgrening eller slump. Ytterligare kan tecken för att avgöra vart annan geometri ska genereras också finnas i ett L-system, exempelvis löv eller frön.

2.3 Rendering av L-system

För att kunna använda L-system i praktiken behöver de renderas, det vill säga ritas ut på skärmen. Ett vanligt tillvägagångssätt kallas för sköldpaddsgrafik vilket går att utföra via flera olika metoder i både två eller tre dimensioner och kan renderas med bland annat instansierade primitiver eller genom shaders på GPU’n.

2.3.1 Sköldpaddsgrafik

Teorin kring sköldpaddsgrafik är relativt enkel. Den innehåller en “sköldpadda” som befinner sig i ett tillstånd som består av en position samt rotation. Sköldpaddan kan ta emot enkla kommandon, till exempel förflyttning, utritning samt rotation (Mackin, 2019). För att sköldpaddan ska agera tolkas strängen som genererats från ett L-system. Varje tecken översätts sedan till kommandon som sköldpaddan ska utföra.

När det gäller L-system med förgreningar utförs dessa genom en metod som sparar sköldpaddans tidigare tillstånd, position och rotation, på en tillståndsstack. Metoden tillåter att backa tillbaka i systemet och börja på en ny förgrening genom tolkningen av hakparenteser, som används enligt nedan.

[ Sparar tillstånd längst upp tillståndsstacken.

] Hämtar och plockar bort det översta tillståndet från tillståndsstacken.

(8)

I Figur 1 finns ett exempel på tecken och hur de kan tolkas samt utritning med sköldpaddsgrafik, vilken illustrerar strängen: ​a[+a]-aa

Figur 1​ Illustration av sköldpaddsgrafik.

2.3.2 Beräknings-shaders

Enligt Eimandar (2013), som arbetar med GPU-programmering och spelmotor-utveckling, är beräknings-shaders (eng. compute shaders) program som exekveras på GPU’n som både kan läsa och skriva till GPU-minnet (VRAM). En av fördelarna med beräknings-shaders är att de körs parallellt med resterande program på GPU’n. Tekniken är således användbar för GPGPU (eng. general-purpose GPU) programmering. Resultatet blir att beräknings-shaders kan användas för annat än grafiska effekter, exempelvis inom fysik eller artificiell intelligens.

En av beräknings-shaders huvudsakliga styrkor är parallelliseringen som finns tillgänglig via en stor mängd trådar. Trådarna delas upp i grupper där varje grupp exekveras på en processor på GPU’n. Storleken på varje grupp definieras av beräknings-shadern. För att starta exekveringen av beräknings-shadern kallas “Dispatch” från CPU’n. Funktionens parametrar bestämmer antalet tråd-grupper som kommer att skapas. När beräknings-shadern är klar skickas resultatet tillbaka från GPU-minnet (VRAM) till CPU-minnet (RAM). Processen kan skapa en flaskhals då CPU’n kommer behöva invänta GPU’n om mängden data är stor eller om beräknings-shaderns exekveringstid är längre än tiden för en bildruta (Eimandar, 2013).

2.4 Implementerade L-system

Lösningar som tagits fram för att tolka och rendera L-system kan både vara enkla eller mer avancerade. Enklare lösningar är intuitiva och ofta lätta att implementera. Avancerade lösningar strävar bort från dessa aspekter och blir mer komplexa, i utbyte mot kortare exekveringstider.

(9)

2.4.1 Enkla metoder

Varianter av den enklare metoden har implementerats på CPU’n av bland andra Hamon m.fl.

(2012), forskare som tidigare arbetat med virtuell modellering och augmenterad verklighet.

Även tidigare examensarbeten, exempelvis Andersson (2016) samt Stenberg (2012) har implementerat liknande metoder som generellt går ut på att tolka varje tecken i L-systemet via en omkopplare. Sköldpaddan agerar därefter enligt strukturen i avsnitt 2.3.1 och eventuella utritnings-instruktioner skickas till grafikkortet som antingen skapar en primitiv, ofta en cylinder, eller ritar ut en linje på en textur. Ett uppenbart problem är att lösningarna exekverar på en kärna. I och med avsaknad parallellism minskar dock komplexiteten, vilket medför att algoritmen blir enklare att implementera och använda.

2.4.2 Avancerade metoder

De mer avancerade lösningarna är inte lika homogena som den enklare typen. Baele och Warzée (2005) har tagit fram en hybrid lösning mellan CPU’n och GPU’n. Hybridlösningen medför att tolkningen utförs på CPU’n med motivationen att processen kräver förgreningar i koden, vilket skulle vara dyrt att göra på GPU’n. När L-systemet renderas skickas inte individuella instruktioner för utritning till GPU’n, utan istället skickas hela topologin. GPU’n bygger sedan upp en modell runt omkring L-systemet.

Ytterligare lösningar har tagits fram av bland andra Lipp, Wonka och Wimmer (2010), Magdics (2009) samt Steinberger, Kenzel, Kainz, Wonka och Schmalstieg (2014), forskare som sedan tidigare arbetat med procedurellt material, grafik och GPU-programmering.

Lösningarna fokuserar på att parallellisera tolkning och rendering, vilket leder till att algoritmerna ökar i komplexitet. Lipp m.fl. (2010) fokuserar på att effektivt dela upp och sortera L-systemet för att kunna tolka och rendera, likt de enklare metoderna, på flera trådar. Metoderna som Magdics (2009) och Steinberger m.fl. (2014) skapat skiljer sig från ovanstående genom att hoppa över själva tolkningen för att istället generera och rendera L-systemet samtidigt. Lösningar av den avancerade typen fokuserar på att kunna exekveras fort i realtid och blir därmed avsevärt mer komplexa än lösningar av den enklare typen.

(10)

3 Problemformulering

Syftet med undersökningen är att problematisera tolkning och rendering av procedurellt genererad vegetation från L-system. Studien utgår från ett spelutvecklings-perspektiv där tidsbudgeten som finns för att rendera mindre vegetation oftast är liten.

Uppsatsen undersöker hur tolkning och rendering av L-system presterar på GPU’n, med en lösning lik den presenterad i avsnitt 2.4.1 jämfört med en implementering på CPU’n.

Frågeställningen blir således: “Hur skiljer exekveringstiden för att tolka och rendera L-system med en enklare algoritm på GPU’n, gentemot att tolka och rendera L-system på CPU’n med liknande algoritm?”.

I avsnitt 2.4.2 presenteras avancerade metoder i relation till enklare CPU-lösningar. Syftet med att avskilja de enklare och mer avancerade metoderna är att de mer avancerade som presenterades redan för 15 år sedan gav lovande resultat i form av exekveringstid.

Lösningarna ökar dock i komplexitet och frågan är om avancerade lösningar likt dessa är nödvändiga eller om de enklare, mer intuitiva lösningarna kan användas med dagens hårdvara med goda resultat. Varken Lipp m.fl. (2010) eller senare verk har hittats där det presenteras en jämförelse kring hur en mer avancerad metod presterar på GPU’n gentemot en mer naiv implementering. Bristen på underlag gör det svårt att uppskatta till vilken grad dessa mer avancerade metoder behövs i dagens datorspel. Det finns alltså anledning att undersöka om GPU’n kräver en mer avancerad lösning för att prestera lika bra eller bättre än CPU’n med liknande enkla algoritmer.

3.1 Metodbeskrivning

För att besvara frågeställningen har en lösning lik den Hamon m.fl. (2012) presenterade, beskriven i avsnitt 2.4.1, implementerats på både CPU’n via C# och på GPU’n med beräknings-shaders. Båda lösningarna tolkar L-system och ritar ut resultatet på en textur.

Enligt avsnitt 2.4.1 tolkar CPU-lösningen varje tecken genom en omkopplare och färgar de pixlar som sköldpaddan färdas över. GPU-lösningen fungerar liknande, med skillnaden att tolkningen av L-systemet körs parallellt för varje pixel. Exempel på resultat från ett renderat L-system visas i Figur 2.

(11)

Figur 2 ​Illustration av ett renderat L-system i två dimensioner (Prusinkiewicz och Lindenmayer, 1990)

För att få en enhetlig bild av hur metoderna, CPU-versionen samt GPU-versionen, presterar utvärderas följande aspekter:

● Texturens upplösning

Upplösning, antal pixlar i texturen som renderas, är av stor vikt då den utritade vegetationen blir mer skarp desto fler pixlar som finns att rita på. Det tar längre tid att beräkna fler pixlar, vilket gör upplösningen intressant att mäta samt undersöka hur exekveringstiden påverkas.

● Olika L-system

I och med en procedurell teknik är det vitalt att materialet varierar i utseende. För att metoden skall kunna användas i praktiken är det av intresse att mäta hur lösningarna presterar på L-system av olika längd samt med eller utan förgreningar.

L-systemen som används i experimenten genereras av en enkel algoritm, beskriven i avsnitt 2.2.1. Genereringen ligger utanför studiens ramar och är därmed inte en del av utvärderingen.

3.1.1 Ramverk

Implementeringen och jämförelsen utfördes i spelmotorn Unity. Anledningen till att spelmotorn använts är då den är populär bland spelstudios då över 0 0004 spel som laddats upp på Itch corp (2020) är gjorda i Unity. Spelmotorn representerar därmed en miljö som kan dra nytta av undersökningen. De delar av spelmotorn som använts i detta arbete är främst dess rendering och analyseringsverktyg, vilka bidrar till att undersökningen kan slutföras inom den givna tidsramen. Det är värt att nämna att olika tredjeparts-applikationer kan ge olika mätvärden för samma experiment, därmed finns det motivering för vidare arbete för att avgöra hur mätvärdena påverkas av olika miljöer.

3.1.2 Experiment

Undersökningen består av ett antal experiment där varje experiment innehåller ett L-system.

Likt Lipp m.fl. (2010) härstammar reglerna för genereringen av L-systemen från

(12)

Prusinkiewicz och Lindenmayer (1990). Motiveringen till valet är att öka möjligheterna för andra att reproducera experimenten.

För att undersöka tidigare nämnda aspekter finns det två typer av L-system som är av intresse, då mätningen endast sker på tolkning samt rendering men ej generering. Typerna av L-system som undersöks är L-system med förgreningar och L-system utan förgreningar, där även L-systemens längd är intressant. Lipp m.fl. (2010) samt Magdics (2009) testade längder mellan cirka 01 − 1 266 864 tecken. I mån av tid och för att antalet experiment inte skall öka för mycket har detta intervall fördelats i sex sektioner. Detta täcker aspekt nummer två. Den kvarvarande aspekten kring texturens upplösning undersöks i intervallet pixlar. Anledningen är att vid lägre upplösning än pixlar försvinner för

2 024

3 2− 1 2 23 2

mycket detalj och högre än 0241 2 pixlar börjar renderingen närma sig Full HD upplösning (1920× 1080 pixlar) vilket skulle täcka upp hela skärmen. Upplösningen testas i sex steg där en fördubbling sker för varje experiment. Under varje experiment testas ett L-system med en justering på en av variablerna för att klargöra vilken inverkan värdets förändring har på prestandan.

Mätvärden från experimenten hämtas med hjälp av Unitys analyseringsverktyg. Datan består av tid och minnesåtgång för tolkning på CPU’n samt GPU’n och även tid och minnesåtgång för rendering på CPU’n samt GPU’n. Anledningen till att även minnesåtgång mäts grundas i en försäkran att algoritmernas minnesåtgång inte stegar iväg för mycket. Fokus ligger dock på exekveringstiden. Mätvärdena för tolkning och rendering inkluderar överföringstider mellan CPU’n och GPU’n samt, i GPU-lösningens fall, tiden för att skapa trådarna som exekverar algoritmen. Sistnämnda mätvärden inkluderas då de bidrar till implementeringens totala prestanda och användarens upplevelse, alltså tidsförbrukningen (eng. elapsed time), enligt ​Suh, Snodgrass, Kececioglu, Downey, Maier och Yi (2017). Forskare som tidigare arbetat med mätning av prestanda. En faktor som spelar stor roll vid mätning av minne är hur ofta datan hämtas. Om mätningen sker vid fel tillfälle riskerar datan att exkludera höga värden av minnesförbrukning, vilket i sin tur ger en felaktig bild av hur mycket minne algoritmen kräver (Beyer, Löwe, och Wendler, 2019). För att lösa problemet kallas

“GetMonoUsedSizeLong” samt “GetAllocatedMemoryForGraphicsDriver” funktionerna, som ser till att Unitys analyseringsverktyg hämtar datan vid rätt tillfälle.

3.1.3 Utvärdering

I och med att det är två algoritmers prestanda som jämförs är en kvantitativ undersökning lämplig. Valet av en kvantitativ metod motiveras av att den bäst passar in på Creswells (2014) beskrivning. Mer exakt handlar det om att undersökningens frågeställning är objektiv, alltså att svaret på frågeställningen grundar sig i mätvärden som påverkas av diverse variabler, tidigare nämnda aspekter. Likt Suh m.fl. (2017) utförs varje experiment gånger för att minska eventuella slumpmässiga faktorers inverkan och höja resultatets 000

1

noggrannhet (eng. accuray) (Suh m.fl., 2017).

Faktorerna som kan påverka resultatet finns det flera av. Exempelvis nämner Suh m.fl.

(2017) att andra program och bakgrundsprocesser som operativsystemet kör i bakgrunden kan påverka resultatet. Suh m.fl. (2017) diskuterar också funktioner inbyggda i vissa

(13)

CPU-modeller som ändrar klockhastighet och volt dynamiskt, vilka medförde större spridning i resultatet och lägre precision. I och med att funktionerna är aktiverade som standard medför det att konsumenter med CPU-modeller som stödjer dynamisk justering riskerar att uppleva algoritmernas prestanda som ojämn. Ett ytterligare exempel på inverkande faktorer kommer från användningen av ett tredjeparts-program. Unitys kodbas är ej tillgänglig, vilket försvårar undersökning av algoritmernas prestanda inom spelmotorn och vad Unity utför i bakgrunden när algoritmerna mäts. För att reducera externa faktorer avslutas därför de bakgrundsprogram som är tillgängliga och som operativsystemet tillåter stängas ned.

Antalet körningar som Suh m.fl. (2017) initialt använde var 0001 . Efter undersökning av standardavvikelse kunde de dock avgöra att för kortare program, under 46 sekunder, förändrades standardavvikelsen marginellt. Suh m.fl. (2017) övergick därmed till 003 körningar per experiment. Denna undersökning gör ej detsamma med anledning att producera stabila resultat.

När testerna körs och exekveringstiden analyseras används medelvärde samt standardavvikelse där standardavvikelse avgör precisionen (Suh m.fl., 2017). Enligt Beyer m.fl. (2019) är endast den högsta mätning av minnesförbrukning av intresse, alltså den minsta möjliga mängd minne som krävs för att utföra algoritmen. Datan för minnesförbrukning består således av högsta mätvärden.

3.1.4 Metoddiskussion

En alternativ metod för att undersöka frågeställningen hade kunnat vara att genomföra den utan en spelmotor. Via en egenutvecklad, mer kontrollerad miljö hade möjligtvis risken för eventuella slumpmässiga faktorers inverkan minskat. Valet att utföra undersökningen från det nämnda perspektivet motiveras av att presentera relevanta svar som kan användas när spel skapas. Resultatet kring metodernas prestanda blir således mer specifikt men också mer korrekt för spel som skapas i Unity. Spelutvecklare behöver alltså inte fundera kring om metoderna kommer prestera annorlunda än resultaten som presenteras i undersökningen, i det fall att spelmotorn Unity används.

Valet att utföra studien empiriskt härstammar från spelutvecklings-perspektivet. Stora O-noteringen är en alternativ metod som ämnar att utföra en teoretisk jämförelse och därmed undvika insamling av mätvärden. Metoden kunde således medfört ett tydligare svar då slumpmässiga faktorer, mätfel och dylikt exkluderas från jämförelsen. En empirisk metod visar inte mer än vad som testas. Om det finns inställningar på variabler som inte testas kan en empirisk metod alltså inte avgöra hur algoritmerna presterar för de värdena (Goldsmith, Aiken och Wilkerson, 2007). På grund av brist på konkreta värden för tid- och minnesförbrukning uppstår dock problem med den teoretiska metoden. En teoretisk jämförelse är inte lika approximativ som en empirisk, men om båda algoritmerna presterar sämre än användarens krav finns chansen att en annan lösning borde väljas. Resultatet från studien måste således presentera konkreta mätvärden som kan sättas i perspektiv till andra algoritmer och hur dessa presterar i spelmotorn. En teoretisk metod kan också vara svår att genomföra i denna studie. Skälet motiveras av att både CPU- och GPU-lösningen är snarlika

(14)

då de implementerar samma övergripande algoritm. Skillnaden mellan algoritmernas prestanda kan därmed bli svår att urskilja utan en empirisk jämförelse.

För att skapa en mer komplett bild över hur algoritmerna presterar kan även olika CPU-modeller och GPU-modeller från olika tillverkare testas. Ett exempel kommer från Danielsson, Jägemar, Behnam och Sjödin (2017) som menar att algoritmers exekveringstid kan påverkas beroende på hårdvarans förmåga att förutspå vilken förgrening i koden som ska exekveras. Experimenten för denna studie utförs dock i mån av tid på endast en modell av CPU samt GPU. Bristen på testning av olika hårdvara resulterar i anledning för vidare arbete för att ta reda på om samma mätvärden återfinns på annan hårdvara.

3.1.5 Hypotes

Hypotesen för frågeställningen är att CPU-lösningen presterar bättre i majoriteten av fallen i form av exekveringstid för tolkning. Hypotesen baseras på Magdics (2009) påstående om att mindre L-system presterar bättre på CPU’n samt Lipps m.fl. (2010) diskussion kring den extra exekveringstiden som går åt att skapa alla trådar på GPU’n. Rendering via CPU-lösningen kan vara långsamt då överföringar måste ske mellan CPU’n och GPU’n.

Möjligheten finns dock att GPU-lösningen skalar bättre med högre upplösning.

CPU-lösningens totala exekveringstid förväntas dock inte vara långsammare än GPU-lösningen på grund av tiden som går åt att skapa trådarna samt föra över L-systemet till GPU’n. Minnesåtgången förväntas bli högre på GPU-lösningen än på CPU-lösningen.

Skälet till hypotesen härstammar från att GPU’n behöver lagra ett stort antal tillstånd för sköldpaddorna som ska köras parallellt för varje pixel.

(15)

4 Genomförande

4.1 Implementering av algoritm

Implementeringen har utförts i spelmotorn Unity. Programmeringsspråken som stöds av spelmotorn är C# för CPU-exekvering och HLSL vid implementering på GPU’n. Koden är översiktligt strukturerad kring en C#-fil som tar hand om start och exekvering av CPU-lösningen, start av GPU-lösningen, insamling av data från experimenten samt generering av L-systemen. Exekveringen av GPU-lösningen utförs via en separat fil som endast innehåller beräknings-shadern. Algoritmen som implementerats både på CPU- och GPU-lösningen är baserad på beskrivningen från avsnitt 2.4.1, alltså bland andra Hamon m.fl. (2012) samt examensarbetet Andersson (2016).

4.1.1 Tolkning utförd av CPU’n

Tolkning av L-system genom CPU-lösningen använder i grunden en datastruktur (eng.

struct) vid namn “Turtle”. Datastrukturen innehåller värden för position och riktning.

Motiveringen till att separera värdena i en datastruktur är för att lättare hantera en stack som sköter förgreningar i L-systemet. Tillståndsstacken, beskriven i avsnitt 2.3.1, består således av en mängd “Turtle”-objekt.

Implementeringen går igenom tecken för tecken i L-systemet, vilka tolkas genom en omkopplare. Datatypen “tecken” (eng. char) finns inte i språket HLSL, som används i GPU-implementeringen, vilket resulterar i att L-systemet konverteras från en sträng till en endimensionell tabell (eng. array) av heltalsvariabler (eng. integer). CPU-lösningen tolkade först L-systemet via en sträng, men för att reducera olikheter mellan CPU- och GPU-lösningen utför båda metoderna tolkning via heltalsvariabler.

Instruktionerna som omkopplaren kan utföra är likt instruktionerna från avsnitt 2.3.1.

Skillnaden är att tecknen konverterats till heltal. Justeringar kring till exempel hur långt framåt en instruktion förflyttar sköldpaddan går att ställa in via diverse variabler.

Existerande instruktioner:

1​ = Rita en linje framåt.

2​ = Rotera vänster.

3​ = Rotera höger.

4​ = Spara sköldpaddans tillstånd på tillståndsstacken.

5​ = Hämta tillstånd från tillståndsstacken.

I omkopplaren är instruktionerna anordnade enligt ovanstående lista. Ordningen som omkopplaren evaluerar instruktionerna är viktig. När en instruktion exekveras kommer omkopplaren inte att evaluera efterföljande instruktioner. För att reducera antalet förgreningar i koden är det därmed viktigt att instruktionen som förekommer oftast ligger först i omkopplaren. Instruktion nummer ett förekommer alltså flest gånger i L-systemen.

(16)

4.1.2 Rendering utförd av CPU’n

Algoritmen innehåller endast en instruktion som ritar ut grafik. Instruktionen flyttar sköldpaddan framåt samtidigt som en linje ritas längs vägen. För att färga rätt pixlar exekveras en loop som färgar varje pixel mellan linjens start och slut. CPU-lösningens tolkning är oberoende av upplösning eftersom tolkningens loop inte ökar beroende på antalet pixlar. När rendering läggs till blir dock lösningen beroende av upplösning då antalet iterationer som renderings-loopen utför ökar linjärt med antalet pixlar som linjen ska färglägga.

Vid tidig testning upptäcktes renderingen exekvera mycket långsamt. Efter mindre felsökning konstaterades det att implementeringen skrev till resultat-texturen vid utritning av varje linje, vilket resulterar i onödigt många överföringar till GPU’n. Lösningen till problemet genomfördes via en tvådimensionell tabell vid samma storlek som resultat-texturen. Tabellen består av Unitys datastruktur “Color” som innehåller fyra flyttal som krävs för varje pixels färg-data. Efter att alla instruktioner i L-systemet har utförts skickas hela tabellen till resultat-texturen. Eftersom skrivning till en array är avsevärt snabbare än till en textur minskade exekveringstiden markant, i utbyte mot dubbel minnesåtgång.

4.1.3 Tolkning utförd av GPU’n

Mellan CPU- och GPU-lösningens implementering av tolkningen finns det ett fåtal skillnader. I GPU-lösningen har datastrukturen separerats till enskilda variabler.

Datastrukturen ersattes då den orsakade stor prestandaförlust i form av exekveringstid.

Därav implementerades datastrukturens innehåll direkt i beräknings-shaderns huvudfunktion. Implementeringen blir således mer utspridd men prestandaförlusten var för stor för att kunna använda en datastruktur likt CPU-lösningen.

Förlusten av datastrukturen leder till att tillståndsstacken delas upp i två tabeller. En tabell innehåller positioner medan den andra innehåller riktningar. Skälet att en stack inte används, likt CPU-lösningen, kommer från att HLSL inte innehåller typen stack. För att likna beteendet används tidigare nämnda tabeller tillsammans med en heltalsvariabel som håller koll på toppen på stacken. Resultatet blir en tillståndsstack representerad av två tabeller och en heltalsvariabel. Problem uppstår dock med tabeller i HLSL då dess storlek inte kan sättas i körtid. Storleken på tabellerna sattes således till det största djup som krävs för att tolka de givna L-systemen. I och med att algoritmernas exekveringstid är mycket lång redan vid

tecken och djupet på de valda L-systemen ej överskrider , används storleken 5 000

2 001 001

på båda tabellerna.

Initialt lagrades L-systemet i en tabell, dock är antalet register som finns tillgängliga i HLSL begränsat till 0964 . L-systemet flyttades således till en strukturerad buffer (eng. structured buffer) för att undgå registrens begränsningar. Tidigare nämnda tillståndsstack, som representeras av tabeller, prövades också att ersättas med en strukturerad buffer. Problem uppstår dock vid skrivning av delat minne då flera trådar skrev till samma tillståndsstack, alltså skrev trådarna över varandras sparade tillstånd vid förgrening i L-systemet.

(17)

4.1.4 Rendering utförd av GPU’n

Eftersom GPU-lösningen utför algoritmen parallellt för varje pixel skiljer sig metoden för rendering. Istället för att iterera över linjen likt CPU-lösningen kan parallelliseringen på GPU’n utnyttjas där varje pixel projiceras på linjen där sköldpaddan färdats. GPU-lösningen påvisade dock mycket längre exekveringstider än CPU-lösningen. Därför testades olika metoder för att reducera tiden som krävs för att beräkna avstånd från pixeln till linjen, genom att till exempel exkludera roten-ur operationer. Skillnaden mellan metoderna var dock marginell, varpå fortsatt sökning efter flaskhalsen genomfördes.

Med Pharr (2005) som grund fanns det misstankar kring att den största faktorn till den långa exekveringstiden var förgreningar i HLSL koden, som genereras av omkopplaren.

Misstankarna resulterade i att förgreningar ersattes med steg-funktioner (eng.

step-function). Användning av steg-funktioner mynnade ut i att alla instruktioner för sköldpaddan utförs men resultatet multipliceras med steg-funktionens värde, vilket var antingen noll eller ett beroende på tolkad instruktion. Metoden resulterade dock i ökad exekveringstid. När koden inspekterades kunde därmed slutsatsen dras att exekveringstiden inte förbättrades när förgrenings-instruktionerna ersattes med steg-funktioner.

GPU-lösningen återgick därmed till att använda förgreningar.

Implementeringen av hela GPU-lösningen har således inte utvecklats mycket från dess första iteration. Det är svårt att göra större förändringar utan att sträva för långt från original-algoritmen som implementeras i CPU-lösningen. Den största kvarvarande faktorn för den långa exekveringstiden har sitt ursprung i antalet instruktioner, alltså längden på L-systemet. För att reducera antalet krävs dock större avvikelse från CPU-lösningen, vilket ligger utanför ramarna på undersökningen och strävar bort från frågeställningen som ämnar att jämföra samma enkla algoritm på CPU’n och GPU’n.

4.2 Pilotutvärdering

Innan experimenten utförs och mätdata hämtas genomfördes två enklare test för att säkerställa att implementeringen är utvärderingsbar. Testerna utfördes på två L-system av olika längd med förgreningar. Mätvärdena som inspekterades efter testet är exekveringstiden för tolkning, rendering samt minnesåtgång för både CPU- och GPU-lösningen. Syftet är att undersöka att mätningen sker korrekt och att algoritmen inte innehåller några drastiska fel.

Exempel på uppenbara fel skulle kunna vara att exekveringstiden inte ökar vid längre L-system, vilket påvisar brister i antingen mätningen eller algoritmen.

Båda lösningar renderade algoritmens resultat på en textur enligt Figur 3. För att säkerställa att algoritmens resultat är visuellt korrekt skickas den renderade texturen till en bild-komponent som finns tillgänglig i Unity. Komponenten sköter sedan utritning till skärmen. Exekveringstiden för utritning till skärm ligger dock utanför undersökningen och exkluderas från mätvärden.

(18)

Alla experiment i undersökningen utfördes på en dator med följande specifikationer:

CPU: Intel Core i7-7700K, 4.2 GHz GPU: NVIDIA GeForce GTX 1070 RAM: 16 GB

OS: Microsoft Windows 10 Home, 10.0.18362

Figur 3 ​Rendering av resultatet från algoritmen.

4.2.1 Genomförande av pilotutvärdering

L-systemen i experimenten genererades enligt följande regler:

Axiom: ​X

Regel 1: ​F -> FF

Regel 2: ​X -> F-[[X]+X]+F[+FX]-X L-system 1: 5 iterationer

L-system 2: 6 iterationer

Resulterande L-system testades 001 gånger med CPU-lösningen respektive GPU-lösningen med en upplösning på 5122 pixlar. Från testerna avlästes sedan genomsnittlig exekveringstid, standardavvikelsen samt högsta minnesåtgång. Två längder på L-systemet testades, vilket styrs med antalet iterationer som genererar L-systemet. Olika längder testades med anledning att kunna verifiera att mätvärdena förändras i olika experiment.

Mätdata från experimenten med rendering inkluderar tiden för tolkningen då det krävs för att rendera L-systemet.

(19)

Från experimenten finns det ett fåtal anmärkningar som kan göras. Mätvärdena i Tabell 1 visar att minnesåtgången inte ökar mycket beroende på L-systemets längd för CPU-lösningen. Skälet är att mätvärdena för minnesåtgång som finns tillgängliga från Unity visar hur mycket minne som är allokerat för objektet som utför algoritmen. Förändring i minnesåtgång kan därmed vara svårt att se om Unity redan allokerat tillräckligt med minne för båda experimenten. Standardavvikelsen för exekveringstiderna är generellt låg redan vid körningar, vilket påvisar att algoritmernas exekveringstid är relativt konsekvent.

00 1

Standardavvikelsen mättes även för minnesåtgången, vilken för samtliga test var under ,0 5 megabyte. Till sist är det värt att påvisa den höga exekveringstiden för GPU-testerna med sex iterationer. Mätvärdena gör det klart att längre L-system än cirka 5 0002 tecken, som sex iterationer producerade, kommer vara svåra att använda i spel. Realtid är helt exkluderat vid den nivån. Generering vid en laddningsskärm är möjlig men kan bli lång om det är många L-system som ska genereras. Visuella resultat från CPU- och GPU-testerna går att finna i Figur 4.

Tabell 1​ Insamlad mätdata från pilottest

Test Genomsnittlig

exekveringstid (millisekunder)

Standardavvikelse (millisekunder)

Minnesåtgång (megabyte)

CPU-Tolkning 5 iterationer

0,74 0,16 10,79

CPU-Rendering 5 iterationer

6,2 0,76 19,44

GPU-Tolkning 5 iterationer

15,17 0,62 6,02

GPU-Rendering 5 iterationer

19,15 0,92 6,19

CPU-Tolkning 6 iterationer

2,90 0,42 10,92

CPU-Rendering 6 iterationer

9,17 0,54 18,85

GPU-Tolkning 6 iterationer

61,4 0,89 6,25

GPU-Rendering 6 iterationer

76,62 0.93 6,25

(20)

Figur 4 ​Resultatet från testerna, CPU-lösningen representeras av den vita växten och GPU-lösningen av den grönfärgade.

4.3 Analys av exekveringstid

För att få en bättre bild av algoritmernas exekveringstid utfördes ett antal tester, vars syfte var att endast exekvera specifika delar av koden. Följande sektioner har undersökts:

● Endast algoritmens loop, utan omkopplaren som tillför kod-förgreningar

● Algoritmens loop tillsammans med omkopplaren

Efterföljande sektioner innefattar algoritmens loop samt omkopplaren:

● L-system förgrening

● Rotation av sköldpadda

● Utritning av linje

Tillsammans utgör ovanstående lista hela algoritmen. Nedbrytningen i Tabell 2 isolerar sektioner av implementeringen och visar tydligare hur algoritmens olika delar presterar. För att utföra enbart delar av koden bestod testerna av samma återkommande L-system-tecken, i följd, med en upplösning på pixlar. Höga inställningar valdes för att 0 000

2 0241 2

förtydliga skillnaderna mellan sektionernas exekveringstid. Eftersom standardavvikelsen är relativt låg genomfördes endast hundra körningar per experiment. Motiveringen grundas i att hålla projektet inom tidsramarna.

4.3.1 CPU-analys

Tabell 2​ Insamlad mätdata från CPU-implementeringens kod-sektion tester

Test Genomsnittlig

exekveringstid (millisekunder)

Standardavvikelse (millisekunder)

Endast loop 17,5 0,7

Loop samt omkopplare 18,92 0,74

L-system förgrening 19,67 0,8

Rotation 19,59 0,95

Utritning av Linje 44,98 0,96

(21)

Mätvärdena från Tabell 2 förtydligar ett antal egenskaper hos implementeringen.

Algoritmens loop utgör en stor del av exekveringstiden, vilket är förväntat givet det stora antalet tecken som loopen går igenom. Faktumet att algoritmen utförs på endast en tråd gör resultatet högst beroende av processorns klockfrekvens, som arbetar i ungefär 4,2 GHz.

Omkopplarens påverkan är förvånansvärt låg men kan förklaras av CPU’ns förmåga att hantera förgrening i kod, exempelvis genom gren-förutsägelse (eng. branch prediction) (Silc, Ungerer och Robic, 2007). Förgreningar i L-system samt rotationer påverkar också exekveringstiden relativt lite. Prestandan är förväntad då kod-sektionerna är korta och enkla.

Rotationen kräver sinus och cosinus beräkningar, vilka testades att genomföras i förväg.

Modifieringen gav dock ingen noterbar förbättring i exekveringstid, därmed sparas ej beräkningarna för att hålla koden enklare.

Den slutgiltiga kod-sektionen är utritning av linje. Kodens exekveringstid är avsevärt längre än de andra sektionerna, vilket beror på att den är direkt beroende av testets upplösning. Om samma experiment utförs med en halverad upplösning ( 125 2 pixlar) sänks exekveringstiden drastiskt till en genomsnittlig tid på 2, 73 4 millisekunder. En multitrådad lösning som undviker eller fördelar renderingens loop skulle kunna öka prestandan. Om renderingen multitrådas ökar dock komplexiteten, vilket ämnas undvikas för att behålla algoritmen enkel.

En parallelliserad lösning är inte heller garanterad att prestera bättre. Vid låga upplösningar finns risken att exekveringstiden för att skapa och hantera trådarna överstiger en enkeltrådad lösning.

Med mätvärdena som utgångspunkt går det att dra slutsatsen att på grund av den stora skillnaden mellan kod-sektionernas prestanda, kommer fördelningen av tecken-typer i L-systemet resultera i varierande exekveringstider.

4.3.2 GPU-analys

Tabell 3​ Insamlad mätdata från GPU-implementeringens kod-sektion tester

Test Genomsnittlig

exekveringstid (millisekunder)

Standardavvikelse (millisekunder)

Endast loop 13,36 0.21

Loop samt omkopplare 109,41 0,72

L-system förgrening 347,49 0,09

Rotation 144,35 0,62

Utritning av Linje 244,58 0,36

Tabell 3 visar att när endast loopen exekveras är algoritmen relativt snabb. Det är först när förgreningar i beräknings-shadern introduceras som exekveringstiden ökar drastiskt. Vid implementeringen av GPU-lösningen diskuterades bland annat förgreningar i koden, som bör varit orsaken till GPU-lösningens långa exekveringstid. Mätdatan från Tabell 3 visar

(22)

dock att flaskhalsarna är flera. När förgreningar introduceras i koden, via omkopplaren, ökar exekveringstiden mycket. Enligt Pharr (2005) kommer trådarna att behöva invänta varandra vid förgreningarna som sker i omkopplaren. GPU-lösningen förväntas därmed inte att skala bra vid högre upplösningar då även om fler trådar skapas kommer trådarna att behöva invänta varandra, vilket begränsar GPU-lösningens möjlighet att parallellisera exekveringen.

L-systemets förgreningar är också kostsamma när det gäller exekveringstid. Flaskhalsen grundas i tillståndsstacken som kräver slumpmässig tillgång (eng. random access) läsning av minnet. Processen är enligt Kashkovsky, Shershnev och Vashchenkov (2017) långsam och resulterar i att L-system med förgreningar är svåra att exekvera med denna GPU-lösning.

För att lösa problemet hade algoritmen behövt större justeringar som eliminerar kravet på en tillståndsstack. Lipp m.fl. (2010) presenterade en lösning som först fördelar L-systemet.

Fördelningen leder till att varje tråd exekverar endast en gren, vilket mynnar ut i att en tillståndsstack inte behövs. Om undersökningen skulle implementera en liknande lösning börjar GPU-lösningen bli mer olik CPU-lösningen, vilket ligger utanför projektets ramar.

Utritning av linje bildar den sista flaskhalsen. Med en projicering som kräver många instruktioner, varav en dyr normalisering ingår, tillsammans med ytterligare en kod-förgrening resulterar sektionen i lång exekveringstid. Försök har gjorts för att reducera båda faktorers inverkan. Alternativa metoder för projiceringen har dock inkluderat ett flertal kod-förgreningar, vilket var långsammare än metoden som används i undersökningen.

Kod-förgreningen som jämför projiceringens längd mot linjen prövades att bytas ut mot en steg-funktion. Metoden resulterade i att skrivning till resultat-texturen utfördes många fler gånger, vilket var långsammare än att behålla kod-förgreningen.

Rotation av sköldpaddan är i sammanhanget billigt, vilket kan förklaras av att varken kod-förgreningar, minnes-avläsningar eller dyra instruktioner behöver utföras. För att behålla lösningen trogen till CPU-lösningen sparas inte sinus och cosinus beräkningar i GPU-lösningen. Modifieringen testades i beräknings-shadern, på samma sätt som i CPU-lösningen, men resulterade i ingen noterbar förbättring i exekveringstid.

(23)

5 Utvärdering

5.1 Presentation av undersökning

Undersökningen har utfört mätning på två L-system. Det första innehåller förgreningar, har renderats i Figur 5 och genereras enligt:

Axiom: ​X

Omskrivningsregler:​ F -> FF

X -> F-[[X]+X]+F[+FX]-X

Figur 5 ​Rendering av L-systemet med förgreningar som undersökts

Det andra L-systemet är utan förgreningar och mer abstrakt då växtlighet sällan genereras utan förgreningar. L-systemet, som renderats i Figur 6, genereras enligt:

Axiom: ​-F

Omskrivningsregler:​ F -> F+F-F-F+F

Figur 6 ​Rendering av L-systemet utan förgreningar som undersökts

(24)

Efter genomfört pilottest drogs slutsatsen att implementeringarna, vid längder av L-system som överstiger 25 000 tecken, tar för lång tid att exekvera för realtid och till viss del även för en laddningsskärm i ett potentiellt spel. Mätningarna stannar därav vid första

genererings-iterationen som överstiger 25 000 tecken, vilket för båda L-systemen sker vid sex iterationer.

Standardavvikelsen för samtliga test är relativt låg med ett intervall på cirka , 010 0 − 1 millisekunder. En genomsnittligt längre exekveringstid resulterade i en standardavvikelse närmare den högre delen av intervallet. Vid längre exekveringstider är dock

standardavvikelsen relativt låg, vilket även gäller för korta exekveringstider då

standardavvikelsen gick närmare 0 0, 01 millisekunder. Minnesåtgångens standardavvikelse mättes också och var konsekvent under 0 5, megabyte.

Graferna som visas i avsnitt 5 illustrerar mätvärden som kan återfinnas i Appendix A.

Eftersom undersökningen är baserad på ett begränsat antal experiment representerar inte grafernas streckade linjer mätdata. Experimenten visar endast hur algoritmerna presterar för specifika inställningar, vilket representeras av grafernas punkter.

5.1.1 Endast tolkning

Tolkning av L-system med förgreningar utfört på CPU’n ökar i exekveringstid endast beroende på L-systemets längd. Grafen i Figur 7 och Figur 8 representerar därmed alla testade upplösningar. I figurerna kan det avläsas att utvecklingen av exekveringstidens medelvärde är relativt linjär och skiljer inte mycket mellan L-system med eller utan förgreningar. Exakta mätvärden kan återfinnas i Appendix A, Tabell 4 och Tabell 8.

Figur 7 ​Medelvärdet av exekveringstid utfört av CPU’n på L-system med förgreningar.

Grafen representerar alla testade upplösningar.

(25)

Figur 8 ​Medelvärdet av exekveringstid utfört av CPU’n på L-system utan förgreningar.

Grafen representerar alla testade upplösningar.

Tolkning utfört av GPU-lösningen är däremot beroende av upplösning. Varje graf i Figur 9 och Figur 10 representerar således en upplösning. Från graferna går det utläsa att även GPU-lösningens exekveringstid är relativt linjär. Högre upplösningar ökar dock exekveringstiden markant. Skillnaden mellan L-systemen med eller utan förgreningar är liten vid korta L-system men växer vid större antal tecken. Exakta mätvärden kan återfinnas i Appendix A, Tabell 5 och Tabell 9.

Figur 9 ​Medelvärdet av exekveringstid utfört av GPU’n på L-system med förgreningar. Varje graf representerar en upplösning.

(26)

Figur 10​ Medelvärdet av exekveringstid utfört av GPU’n på L-system utan förgreningar.

Varje graf representerar en upplösning.

5.1.2 Tolkning och rendering

Tolkning och rendering utfört med CPU-lösningen påvisar relativt korta exekveringstider vid lägre upplösningar. Utvecklingen i Figur 11 och Figur 12 är linjär men de initiala kostnaderna, vad gäller exekveringstid, har stigit drastiskt vilket resulterar i en förskjutning av graferna. Skillnaden mellan L-systemet med eller utan förgreningar är liten, vilket stämmer överens med endast tolkning på CPU’n och mätningarna i avsnitt 4.3.1. Exakta mätvärden kan återfinnas i Appendix A, Tabell 6 och Tabell 10.

Figur 11 ​Medelvärdet av exekveringstid utfört av CPU’n på L-system med förgreningar.

Varje graf representerar en upplösning.

(27)

Figur 12 ​Medelvärdet av exekveringstid utfört av CPU’n på L-system utan förgreningar.

Varje graf representerar en upplösning.

GPU-lösningens tolkning och rendering ökar snabbt vid högre upplösningar. I Figur 13 och Figur 14 kan en linjär utveckling utläsas. För L-system utan förgreningar ökar inte exekveringstiden lika fort som L-system med förgreningar. Exakta mätvärden kan återfinnas i Appendix A, Tabell 7 och Tabell 11.

Figur 13​ Medelvärdet av exekveringstid utfört av GPU’n på L-system med förgreningar.

Varje graf representerar en upplösning.

(28)

Figur 14 ​Medelvärdet av exekveringstid utfört av GPU’n på L-system utan förgreningar.

Varje graf representerar en upplösning.

5.1.3 Minnesförbrukning

Minnesförbrukningen utvecklas olika på CPU- och GPU-lösningen. Figur 15 visar minnesförbrukningen för CPU-lösningen. Förbrukningen ser likadan ut för båda L-system, oavsett L-systemets längd men förändras däremot beroende på upplösning. Exakta mätvärden kan återfinnas i Appendix A, Tabell 12.

Figur 15 ​Genomsnittlig minnesförbrukning för CPU-lösningen.

GPU-lösningens minnesförbrukning går att finna i Figur 16. GPU-lösningens minnesförbrukning ökar beroende på L-systemets längd, till skillnad från CPU-lösningen som ökar beroende på upplösning. Exakta mätvärden kan återfinnas i Appendix A, Tabell 13.

(29)

Figur 16​ Genomsnittlig minnesförbrukning för GPU-lösningen

5.2 Analys

5.2.1 Endast tolkning

CPU-lösningens genomsnittliga exekveringstid för tolkning är enligt Figur 7 och Figur 8 relativt linjär. Mätvärdena påvisar en låg initial kostnad, vilket bidrar till att metoden behöver cirka tre millisekunder för att tolka cirka 0 0003 tecken. Skillnaden mellan L-system med eller utan förgreningar är för detta experiment marginell, detta stämmer överens med mätvärdena i avsnitt 4.3.1.

Tolkning utfört med GPU-lösningen påvisar också en relativt linjär utveckling. Metoden är dock beroende av upplösning då algoritmen exekverar för varje pixel. Enligt Figur 9 och Figur 10 ökar exekveringstiden drastiskt vid högre upplösningar, vilket kan förklaras av dålig parallellism på grund av att trådarna behöver invänta varandra (Pharr, 2005). Skillnaden mellan L-system med eller utan förgreningar är större för GPU-lösningen än för CPU-lösningen. Differensen är större vid längre L-system på grund av att minnesavläsningarna tar längre tid desto mer minne som är allokerat (Kashkovsky m.fl., 2017).

5.2.2 Tolkning och rendering

Tolkning och rendering på CPU’n följer också en relativt linjär utveckling, med marginella skillnader mellan L-system med eller utan förgreningar. Den initiala kostnaden ökar dock beroende på upplösning, enligt Figur 11 och Figur 12. Skälet kommer från längre överföringstider mellan CPU’n och GPU’n samt längre exekveringstid för att skapa resultat-texturen och tabellen (eng. array) vid samma storlek.

Exekveringstiden för GPU-lösningens tolkning och rendering är relativt linjär. Jämfört med endast tolkning har inte de initiala kostnaderna stigit mycket. Lutningen på graferna är dock

(30)

brantare, vilket resulterar i lång exekveringstid vid längre L-system. Differensen mellan L-system med eller utan förgreningar är mindre än vid endast tolkning. Skälet motiveras av att instruktionen för utritning av linje bildar en flaskhals, vilken reducerar fördelarna som minskad läsning av minne bidrog med.

Både CPU- och GPU-lösningen har för- och nackdelar. CPU-lösningens tolkning har en låg initial kostnad och exekveringstiden ökar relativt långsamt vid längre L-system. När rendering ska utföras ökar dock exekveringstiden fort vid högre upplösningar och de initiala kostnaderna stiger. Relativt till CPU-lösningen har GPU-lösningen högre initiala kostnader från överföringstider samt skapandet av trådar. Generellt ökar exekveringstiden för både tolkning och rendering snabbare på GPU-lösningen. Det finns dock extremer, exempelvis presterar GPU’n bättre på korta L-system med en hög upplösning, enligt Figur 17.

Figur 17 ​Illustration av exekveringstidens förändring beroende på L-systemets längd.

Exakta mätvärden finns i Appendix A, Tabell 6 och Tabell 7.

Ytterligare jämförelse visas i Figur 18, vilken illustrerar ett utdrag av skillnaden mellan CPU- och GPU-lösningen beroende på upplösning. Exekveringstiden ökar icke-linjärt när upplösningen höjs. För GPU-lösningen beror det på dålig parallellism gentemot CPU-lösningen som ökar de initiala kostnaderna markant med högre upplösningar.

Figur 18 ​Illustration av exekveringstidens förändring beroende på upplösning. Exakta mätvärden finns i Appendix A, Tabell 6 och Tabell 7.

(31)

5.2.3 Minnesförbrukning

Minnesförbrukningen för CPU-lösningen är svår att analysera. Vad som går att utläsa från Figur 15, mätvärden som Unity ger ut i form av hur mycket hela objektet har allokerat, är att Unity ökar allokeringen beroende på experimentets upplösning. Minnesförbrukningen är relativt hög och ökar icke-linjärt vid högre upplösningar. Utvecklingen förklaras genom att CPU-lösningen både skapar en textur som resultatet skrivs till samt en tvådimensionell tabell för snabbare skrivning i renderingen.

Figur 16 visar GPU-lösningens minnesförbrukning som ökar beroende på L-systemets längd.

Mätvärdena är låga i kontrast till CPU-lösningen men ökar inte vid högre upplösning, vilket bidrar till misstankar om att texturen utesluts från värdena Unity skickar ut. GPU-lösningens minnesförbrukning är dock svår att avgöra då ökningen är liten och inom spannet för standardavvikelsen. Eftersom undersökningen stannar vid cirka 5 0002 tecken finns dock risken att minnesförbrukningen ökar snabbt vid längre L-system, då längder långt över

tecken inte testats i undersökningen.

5 000 2

5.3 Slutsatser

Hypotesen, baserat på Magdics (2009), påstod att korta L-system presterar bättre på CPU’n.

Enligt undersökningen stämmer påståendet då metodens initiala kostnad är låg vid korta L-system, vilket gör CPU-lösningens initialisering snabbare än vad som krävs för beräknings-shadern. CPU-lösningens exekveringstid för rendering är relativt låg vid låga upplösningar men exekveringstiden ökar drastiskt vid högre upplösningar. Skälet är att både överföring till GPU’n tar längre tid samt att renderings-algoritmen är direkt beroende av experimentets upplösning. En mer optimal renderings-algoritm hade kunnat reducera problemet.

GPU-lösningens styrkor visas vid tolkning och rendering av korta L-system exekverade på höga upplösningar. Metoden brister dock vid korta L-system med låg upplösning då initiala kostnader existerar via skapandet av trådar och överföringstider (Lipp m.fl., 2010). Långa L-system är långsamma att exekvera på GPU-lösningen. För dessa L-system skalar inte högre upplösning lika bra som vid kortare L-system, på grund av att trådarna behöver invänta varandra (Pharr, 2005). Hypotesen kring att GPU-lösningen skalar bra med högre upplösningar stämmer därmed vid kortare L-system men inte för de längre.

Svaret på frågeställningen “Hur skiljer exekveringstiden för att tolka och rendera L-system med en enklare algoritm på GPU’n, gentemot att tolka och rendera L-system på CPU’n med liknande algortim?”, som har sitt ursprung ur Anderssons (2016) arbete har till viss del redan besvarats. Sammanfattningsvis har CPU-lösningen kortare exekveringstid på fler experiment än GPU-lösningen. Undantag existerar, men återfinns vid extremerna av inställningarna. Den generella regeln för båda implementeringar går därmed mot att utan GPU-anpassad kod, alltså kod utan förgreningar och med bättre minneshantering, presterar CPU’n bättre. Mätvärdena gällande exekveringstid från samtliga figurer i avsnitt 5.1 påvisar en linjär utveckling mellan kortare och längre L-system. Enligt Figur 18 ökar dock

(32)

exekveringstiden icke-linjärt beroende på upplösning, vilket betyder att exekveringstiden förlängs mer beroende på upplösning gentemot L-systemets längd. Med pilottestet som utgångspunkt begränsades undersökningen till drygt 5 0002 tecken och en upplösning på pixlar . Frågan som kan ställas är dock hur exekveringstiden hade ökat vid exempelvis 024

1 2

tecken. Om lösningarna ska användas i realtid eller under spelutveckling är de dock 00 000

1

inte praktiska redan vid mätningarnas gränser, vilket är en synvinkel som diskuteras vidare i diskussionen nedan (avsnitt 6.2).

Minnesförbrukningen kan tolkas på flera sätt. CPU-lösningens mätvärden visar inte exakt hur mycket minne implementeringen kräver, men det är tydligt att förbrukningen ökar snabbt vid högre upplösningar. Frågan är dock om högre upplösningar än 0241 2 pixlar kommer vara av intresse och om CPU-lösningens minnesförbrukning är ett icke-problem i slutändan, eftersom exekveringstid blir ett problem mycket tidigare. GPU-lösningens mätvärden är också oklara med en uppmätt liten ökning. Risken finns att mycket långa L-system ökar minnesförbrukningen, vilket inte testats i undersökningen. Faktum är dock att L-system som är längre än 5 0002 tecken tar för lång tid att exekvera, vilket leder till samma icke-problem som med CPU-lösningen. Minnesförbrukning kan alltså bli ett problem för båda lösningar, men exekveringstiden är ett större problem som förekommer mycket tidigare än någon form av brist på tillgängligt minne.

(33)

6 Avslutande diskussion

6.1 Sammanfattning

Undersökningens syfte har varit att implementera en enkel algoritm på CPU’n samt GPU’n och jämföra dess exekveringstid. Fokus har legat på L-system då användningsområden finns i spelutveckling, exempelvis procedurell generering av vegetation.

Algoritmen implementerades i spelmotorn Unity där lösningarna testades via två L-system, ett med och ett utan förgreningar. Ytterligare testades L-systemen på diverse längder samt upplösningar. Experimenten påvisade att lösningarna besitter både styrkor och svagheter.

CPU-lösningen hade en relativt låg exekveringstid vid både korta och till viss del långa L-system men ökade vid korta L-system med hög upplösning. GPU-lösningen presterade däremot bra vid korta L-system med hög upplösning. Metodens exekveringstid ökade dock fort vid längre L-system kombinerat med hög upplösning. Vid längre L-system presterar alltså CPU-lösningen bättre än GPU-lösningen, oavsett testad upplösning då GPU-lösningens trådar tvingas invänta varandra (Pharr, 2005).

Sammanfattningsvis visar undersökningen att GPU-anpassad kod är i detta fall nödvändigt och att exekvera icke-anpassad kod, från CPU-lösningen, på GPU’n inte resulterade i bättre prestanda. Det finns extremfall då GPU-lösningen presterar bättre, men de räcker inte till för att kunna rekommendera metoden annat än i specialfall, exempelvis avbelastning för CPU’n.

6.2 Diskussion

Resultatets trovärdighet påverkas av flera faktorer. Mätfel är ett problem som är reducerat tack vare många körningar per experiment samt beräkning av standardavvikelse. Faktorn kvarstår dock då exempelvis en längre process kan startas av operativsystemet i bakgrunden, vilket riskerar förskjuta experimentets mätvärden. Om alla värden under samma experiment påverkas syns detta ej i standardavvikelsen, vilket gör problemet svårt att upptäcka. Stora förskjutningar skulle kunna upptäckas vid inspektion, varpå experimentet får genomföras igen. Ytterligare påverkas mätvärdena av att experimenten genomförs i utvecklings-motorn och inte i en exporterad version av projektet. När exekvering och rendering av diverse utvecklings-verktyg i Unity exkluderas tenderar prestandan att öka. Insamling av mätvärden krävde dock att experimenten utfördes med utvecklings-verktygen inkluderade.

Undersökningens slutgiltiga bild kring hur lösningarna jämför mot varandra påverkas även av vilken hårdvara experimenten genomförs på. Skillnaden mellan lösningarna ser troligtvis annorlunda ut beroende på hårdvara. Testning av olika hårdvara är dock ett område som sträcker utanför undersökningen. Faktorn gällande hårdvara är alltså en påverkan på trovärdigheten som kvarstår och som inga åtgärder har tagits vid för att reducera.

6.2.1 Etiska aspekter

Resultaten från experimenten är egenproducerade från kod skriven under projektets genomförande. Inga mätvärden har uteslutits från undersökningen och alla exekveringstider

(34)

har redovisats i avsnitt 5.1 och Appendix A. Värdena som inte redovisats för varje experiment är minnesåtgång och standardavvikelse. Skälet att alla mätvärden för minnesåtgång inte redovisats kommer från att minnesåtgången endast förändrades beroende på en variabel.

Om alla experiment redovisats skulle tabellen vara lika stor som exekveringstidernas tabeller, men innehålla mycket av samma data. Standardavvikelsen har inte redovisats för varje experiment på grund av låga värden som inte överstiger en millisekund. Samtliga experiment visade låg standardavvikelse vid 1000 körningar, vilket påvisar att experimentet är konsekvent. Alla L-system som varit del av undersökningen har redovisats, vilket bidrar till lättare återskapande av experimenten. Undersökningens metod inkluderar varken människor eller djur, därmed finns det inga involverade personer eller djur som experimenten måste förhålla sig till i fråga om anonymitet eller säkerhet. Etiska aspekter som diskuterats grundas på de allmänna regler som Eriksson (2017) presenterar.

6.2.2 Samhälleliga aspekter

Resultaten från undersökningen är svåra att använda i sammanhang utanför Unity. Om lösningarna implementeras i ett annat arbete är det oklart hur mycket diverse faktorer har påverkat resultaten som presenterats. Vad undersökningen dock påvisar är att GPU-anpassad kod är viktigt, men till vilken grad och exakt vilka justeringar som krävs för att reducera skillnaden mellan lösningarna är inte helt självklart. Vidare forskning och mjukvaruutveckling kan använda undersökningen för att påvisa varför GPU-anpassad kod är viktigt samt vid vilka extremer som en naiv GPU-lösning faktiskt kan vara lämplig.

Undersökningen har således bidragit med ett intygande om att, trots ny hårdvara, krävs fortfarande GPU-anpassad kod för att åstadkomma korta exekveringstider. Uppsatsen och liknande studier som jämför olika algoritmer och implementeringar bidrar till att bättre beslut kan fattas kring om en CPU- eller GPU-lösning passar bäst och hur den bör utformas beroende på mängd och typ av data.

En viktig fråga är om algoritmerna är lämpliga att användas i ett potentiellt spel. Om algoritmerna exekverar i realtid med liknande hårdvara bör upplösningen inte överstiga pixlar och L-systemets längd hållas kortare än cirka tecken. För statiska L-system 56

2 2 004

borde algoritmen inte exekvera varje bildruta då resultatet kan sparas och återanvändas i körtid, vilket gör mätvärdena mer lovande. Tolkning och rendering vid exempelvis en laddningsskärm eller enstaka tillfällen är alltså mer lämpligt och ger utrymme att höja upplösningen till 125 2 pixlar och L-systemets längd till cirka 0006 tecken. Rekommenderade upplösningar och längder på L-system beror på ett antal faktorer. En faktor är om L-systemet innehåller förgreningar, i det fallet kommer GPU-lösningens exekveringstider påverkas mer än CPU-lösningens. L-systemets fördelning av instruktioner är också en stor faktor, där avsnitt 4.3 visade att exempelvis renderings-instruktioner är dyra oavsett vilken metod som utför dem.

Båda lösningars exekveringstid ökar drastiskt vid längre L-system. Avancerade lösningar likt Lipp m.fl. (2010) och Magdics (2010) fördelar L-systemet vid generering och har därmed möjlighet till parallellisering både på CPU’n och GPU’n. Metoderna som denna undersökning presenterat är avsevärt långsammare men finner ändå användningsområden. Om exempelvis gräs skulle genereras krävs varken höga upplösningar eller långa L-system. Gräs i

(35)

spel är ofta smått och långt från kameran vilket tillåter sänka inställningarna. Realtid är som tidigare nämnt till största del uteslutet, men för att generera exempelvis 001 olika typer av gräs-texturer under en laddningsskärm bör inte längre än 450 millisekunder vara nödvändigt vid en upplösning på 125 2 pixlar och en längd under 0006 tecken. Tiden är baserad på CPU-lösningen men även om GPU-lösningen är långsammare kan metoden vara användbar om CPU’n är överbelastad. GPU’n kan alltså hjälpa till att fördela arbetet mellan processorerna utan krav på en avancerad implementering.

Avslutande är det implementeringens svårighet som resultaten bör sättas i relation till.

Algoritmen är kort, intuitiv och enkel att felsöka. Potentiella spelstudios har möjligheten att spara in utvecklingstid, som är en viktig och dyr resurs i spelutveckling, genom att använda en enklare lösning likt den som använts i undersökningen. Om ändamålet är att generera gräs-texturer eller liknande material under utveckling eller i laddningsskärm behövs inte en mer avancerad algoritm. Vid generering av större strukturer som kräver mer detalj och högre upplösning finns risken att algoritmen behöver större modifikationer, alternativt vidare sökning efter en mer skalbar metod.

6.3 Framtida arbete

För att utöka bilden av jämförelsen skulle algoritmen kunna implementeras i en mer kontrollerad miljö. En egenutvecklad miljö där det finns full kontroll över programmet och dess resursallokering skulle resultera i generella mätvärden som är mer relevanta för spel som skapas utanför Unity, men också för andra områden än spel.

Mätning av exekveringstid är högst beroende av vilken hårdvara som programmet utförs på.

Enligt Suh m.fl (2017) och Danielsson m.fl. (2017) kan det bero på bland annat dynamisk justering av klockhastighet och volt eller exempelvis hårdvarans förmåga att förutspå förgreningar i koden, funktioner vars stöd varierar bland olika processorer. En undersökning som breddar mätningarna till flera CPU- och GPU-modeller skulle kunna klargöra hur mycket hårdvara påverkar algoritmen och om GPU-anpassad kod spelar lika stor roll för olika GPU-modeller.

Implementering av någon av lösningarna i faktisk spelutveckling skulle bidra till en bättre bild kring hur bra metoderna fungerar i spelproduktion. Ett projekt på den storleken kan ge nya insikter då algoritmerna troligtvis kommer modifieras till spelstudions behov.

Modifiering av algoritmen skulle kunna utforska möjligheten att anpassa den till mer GPU-anpassad kod eller utveckling av en bättre renderings-algoritm för CPU-lösningen. En intressant del av ett sådant projekt är hur långt bort föreslagna förändringar ligger från implementeringarna som använts i denna undersökning och hur stor prestandaskillnad små eller stora modifikationer resulterar i.

References

Related documents

område för folk att åka upp till, och då pratar jag inte om ungdomarna utan om

Deci och Ryan (2000) menar att möjlighet till befordran skapar ökad kompetens, vilket i sin tur bidrar till ökad motivation för den anställda.. Det går att skapa incitament för de

Eftersom vi har funnit att vissa del- tagare verkligen har dragit nytta av kursen och andra inte i samma utsträckning, så tror vi att det går att utveckla framgångsrika kur- ser

Steg 0-2: Hämta instruktion, samt beräkna adress Steg 3-4: Utför en instruktion Steg 5-6: Utför en annan instruktion.

Undersök vidare Var det verkligen värme från dina händer som fick vattnet att stiga i sugröret eller kunde trycket från dina händer vara orsaken.. Hur kan man

T rots att målet om Frisk luft är svårt att nå i Göteborg klarades delmålen för partiklar 2013.. Anledningen till att parti- kelhalterna var så låga i Haga under 2013 kan bero

några mera ofta användbara metoder vid lösningen af plan-geometriska problem,.. ) paragrafen i detta

Under de senaste tre decennierna har populära managementböcker erbjudit snabba och enkla recept för hur ledare ska leda personal på ett effektivt sätt. Dessa böcker har