Toimintojen upottaminen

Laskennassa funktion upottaminen (eng. Inlining ) on optimointimenetelmä , jossa funktiokutsu korvataan suoraan sen rungolla. Funktioiden sisällyttäminen on pohjimmiltaan samanlainen kuin makrokorvaus , mutta toisin kuin se ei muuta lähdekoodia ja tapahtuu käännöshetkellä, kun taas makrot muuttavat lähdekoodia ennen kääntämistä.

Toimintojen sisällyttäminen on tärkeä optimointi, mutta tehokkuusvaikutus voi vaihdella [1] . Tyypillisesti jotkin rivitykset parantavat suorituskykyä hyvin pienellä tilalla, mutta liikakäyttö voi hidastaa ohjelmaa, koska tekstin sisäinen koodi kuluttaa liikaa käskyvälimuistia ja vie myös huomattavan määrän tilaa. Jones & Marlow'n vuonna 1999 julkaisema kirja [2] tarjoaa yleiskatsauksen 1980- ja 1990-lukujen akateemiseen kirjallisuuteen funktion inlinoinnista.

Yleiskatsaus

Funktioiden sisällyttäminen on samanlainen kuin makrolaajennus – pohjimmiltaan kääntäjä asettaa uuden kopion funktiosta jokaiseen paikkaan, jossa sitä kutsutaan. Sisäisiä toimintoja sisältävä koodi toimii hieman normaalia koodia nopeammin, koska funktioiden kutsumisen lisävaiheet ovat poissa, mutta se lisää myös muistin kulutusta. Jos funktio on sisäänrakennettu 10 kertaa, koodiin lisätään 10 kopiota funktiosta. Siksi upottaminen sopii parhaiten pieniin usein käytettyihin toimintoihin. C++ : ssa oletusarvoisesti luokkamäärityksessä määritellyt menetelmät on rivitetty (ei tarvitse käyttää rivin avainsanoja ); muuten avainsana on pakollinen. Kääntäjä voi jättää huomioimatta ohjelmoijan yrityksen julistaa funktio inline - tämä pätee pääasiassa erittäin suuriin funktioihin.

Funktioinline-toimintoa käytetään säästämään CPU-aikaa, joka kuluu toimintokutsuihin. Yleensä käytetään usein suoritettaviin toimintoihin. Tekniikka säästää myös muistia hyvin pienille toiminnoille, lisäksi se mahdollistaa muiden optimointien soveltamisen .

Ilman inline-ohjeita kääntäjä usein päättää, mitkä funktiot rivittää. Ohjelmoijalla on vain vähän hallintaa siihen, mitkä toiminnot tulevat sisään. Jos ohjelmoija päättää toiminnon sisällyttämisestä itse, hän voi käyttää tietoa tietyn sovelluksen toiminnasta parempaan optimointiin.

Kun funktiota kutsutaan, ohjaus siirretään sen runkoon haarakäskyllä ​​( haara) tai kutsukäskyllä ​​(kutsu). Sisäänrakentaminen mahdollistaa näiden ohjeiden poistamisen - tässä tapauksessa ohjaus siirtyy suoraan toimintokoodiin.

Yleensä kääntäjät käyttävät rivitystä toteuttaakseen joitain . Esimerkiksi syklien rungot ja ehdot on laskettava suoraan suorituksen aikana . Tämä saavutetaan upottamalla koodi olosuhteiden ja silmukoiden rungon arvioimiseksi, joissa niitä kutsutaan. Lausunto lisää myös suorituskykyä.

Kun tarkastellaan funktionaalisten ohjelmointikielten kontekstissa , funktion rivitystä seuraa yleensä beta - redusointimuunnos .

Ohjelmoija voi upottaa funktion manuaalisesti kopioimalla sen koodin ja liittämällä sen kutsusivustoille . Muut inline-ohjausmenetelmät (katso alla) ovat kuitenkin parempia, koska ne eivät aiheuta virheitä, joita esiintyy, jos ohjelmoija ei päivitä yhtä sen kaksoiskappaleista korjatessaan virhettä rivifunktiossa.

Vaikutus suorituskykyyn

Tämän optimoinnin tärkein vaikutus on parantaa suorituskykyä (johtuen puhelukustannusten eliminoinnista) ja lisätä muistin kulutusta [a] (johtuen toimintorungon päällekkäisyydestä ). Koodin monistamisesta johtuva muistinkulutus hallitsee, paitsi yksinkertaisissa tapauksissa [b] . Siivouksen pääasiallinen vaikutus on siis koodin nopeuttaminen tilan kustannuksella.

Sisällyttämisen tärkein etu on kuitenkin mahdollisuus lisäoptimointiin ja parempaan suunnitteluun suurentamalla funktiorungon kokoa, koska suurempia toimintoja voidaan optimoida paremmin [3] . Vaikutusta suorituskykyyn on melko vaikea arvioida täysin, koska muistijärjestelmään (ensisijaisesti käskyvälimuistiin ) kohdistuu suuri määrä vaikutuksia, joilla on merkittävä vaikutus nykyaikaisten prosessorien suorituskykyyn. Tiettyjen toimintojen upottaminen tietyn ohjelman ja välimuistin mukaan voi joko lisätä tai vähentää suorituskykyä [1] .

Sisäänrakentamisen vaikutus riippuu myös ohjelmointikielestä, johtuen erilaisesta abstraktiotasosta. Alemman tason pakollisilla kielillä, kuten C ja Fortran, voidaan saavuttaa 10-20 % nopeuden lisäys ilman vain vähän vaikutusta koodin kokoon. Abstraktimmilla kielillä vaikutus voi olla paljon suurempi johtuen upotuksen poistamien tasojen määrästä. Esimerkkinä voidaan mainita Self -kieli , jossa upotuksen ansiosta oli mahdollista saavuttaa kiihtyvyyskertoimet 4:stä 55:een [4] .

Toimintokutsun poistamisen välitön hyöty on seuraava:

Sisällön pääasiallinen hyöty on kuitenkin sen tarjoamissa mahdollisuuksissa lisäoptimointiin. Koodioptimoinnit useista toiminnoista voidaan suorittaa ilman, että tarvitaan inter-procedural-optimointia (IPO): laajennettuun funktiorunkoon sisällyttämisen jälkeen ylimääräiset "prosessinsisäiset" ("globaalit") optimoinnit ovat mahdollisia. Esimerkiksi:

Tällaiset optimoinnit voidaan tehdä ilman rivitystä, mutta ne vaativat paljon monimutkaisempia kääntäjiä ja linkkejä (jos kutsu ja toteutus ovat erillisissä käännösyksiköissä).

Joissakin tapauksissa kielimääritykset sallivat kuitenkin ohjelmien tehdä lisäoletuksia proseduuriargumenteista. Sisällyttämisen jälkeen nämä tiedot voivat kadota, jolloin jotkin optimoinnit ovat mahdottomia. Älykkäämmät kääntäjät (kuten Glasgow'n Haskell-kääntäjä ) seuraavat tätä, mutta yksinkertainen inline-toteutus ei ota tätä huomioon.

Sisäänrakennetun muistijärjestelmän osalta voidaan erottaa seuraavat edut:

Sisällyttämisen suora hinta on koodikoon kasvu, joka johtuu funktion rungon päällekkäisyydestä kussakin puhelupaikassa. Näitä kustannuksia ei esiinny vain erittäin pienissä funktioissa, joissa funktion runko on pienempi kuin kutsun hinta (kutsuminen, argumenttien käsittely ja palautusarvot), kuten triviaali accessori (getterit ja asettajat); tai toiminnoille, joita käytetään vain kerran. Siksi optimoitaessa kokoa (tarvitaan usein sulautetuissa järjestelmissä ) tämä optimointitekniikka voidaan minimoida tai sitä ei käytetä.

Sisällys heikentää myös suorituskykyä laajentamalla (kopioimalla) koodia, mikä heikentää käskyvälimuistin suorituskykyä. [8] Hidastuminen on havaittavin, jos ennen laajennusta ohjelman työmäärä (ns. kuuma koodin osa) sijoitetaan yhdelle muistihierarkian tasolle (esimerkiksi tason 1 välimuistiin ) ja laajennuksen jälkeen se ei enää mahdu sinne, mikä johtaa toistuviin välimuistin puutteisiin tällä tasolla. Välimuistihierarkian eri tasojen suorituskyvyn merkittävien erojen vuoksi tämä hidastaa koodia. Hierarkian korkeimmalla tasolla tämä voi johtaa sivuvirheiden määrän kasvuun , katastrofaaliseen suorituskyvyn heikkenemiseen pusertamisen vuoksi ja jopa ohjelman täydelliseen epäonnistumiseen. Jälkimmäinen on harvinainen tyypillisissä työpöytä- ja palvelinsovelluksissa, joissa koodin koko on pieni verrattuna käytettävissä olevaan muistiin, mutta se voi olla ongelma resurssirajoitteisissa ympäristöissä (esim. sulautetut järjestelmät). Yksi ratkaisu on jakaa funktiokoodi lyhyeksi kuumaksi ( nopea ) poluksi upotuksilla ja pitkäksi kylmäksi (hidas) poluksi ilman upotuksia. [kahdeksan]

Sisäänrakentamisen tärkein suorituskykyvaikutus kohdistuu ensisijaisesti suuriin toimintoihin, joita käytetään monissa paikoissa. Kannattavuuspisteen määrittäminen, jonka ylittyessä rivitys vähentää suorituskykyä, on kuitenkin monimutkaista ja riippuu todellisesta työmäärästä. Reunatapauksissa tämä voi olla manuaalista optimointia tai optimointia profiloinnin avulla . [9] Samanlainen ongelma esiintyy muissa hajautusoptimoinneissa, kuten silmukan purkamisessa : vaikka käskyjen määrä vähenee, suorituskyky voi heikentyä hitaamman välimuistin vuoksi.

On vaikea arvioida tarkasti upottamisen vaikutusta välimuistin suorituskykyyn. Pienillä välimuistikooilla (paljon pienempi kuin työkoko ennen laajennusta) sekvenssin lisääminen hallitsee, joten rivitys parantaa välimuistin suorituskykyä. Jos välimuistin koko on lähellä työkokoa , välimuistin suorituskyky heikkenee, koska lisäyksen jälkeen laajennettu koodi ei mahdu välimuistiin. Jos välimuisti on suurempi kuin työtila , upotuksella on vain vähän vaikutusta välimuistin suorituskykyyn. Myös jotkin välimuistin suunnittelun parannukset (kuten kuorman edelleenlähetys) voivat lieventää välimuistin menetysten lisääntymistä. [kymmenen]

Kääntäjän tuki

Kääntäjät käyttävät useita menetelmiä päättääkseen, mitkä funktiot sisällytetään; Näiden joukossa sekä ohjelmoijien nimenomaiset ohjeet tiettyjä toimintoja varten että yleiset ohjeet komentorivin valinnoista ovat tärkeitä. Monissa kääntäjissä monilla kielillä automaattinen rivitys perustuu arvioon siitä, onko se hyödyllistä; muissa tapauksissa se voidaan asettaa manuaalisesti kääntäjän käskyjen avulla tai käyttämällä inline. Tämä avainsana osoittaa vain, että funktio on upotettava - tämän ilmaisun merkitys vaihtelee kielen ja kääntäjän mukaan.

Kääntäjän kehittäjät ottavat edellä mainitut asiat huomioon ja ottavat kääntäjiinsä käyttöön erilaisia ​​heuristiikkaa , jotka valitsevat sisäänrakennettavat toiminnot, jotta useimmissa tapauksissa se parantaa suorituskykyä sen sijaan, että se heikentää sitä.

Toteutus

Kääntäjän suorittama rivityksen toteuttaminen on yleensä melko yksinkertaista. Riippuen siitä, suoritetaanko tämä toiminto koodissa eri kielillä, kääntäjä voi suorittaa rivityksen joko korkean tason välimuodossa (kuten abstraktissa syntaksipuussa ) tai matalan tason välimuodossa. Kummassakin tapauksessa kääntäjä yksinkertaisesti arvioi argumentit , tallentaa ne muuttujiin, jotka vastaavat funktion argumentteja, ja lisää sitten funktion rungon kutsupaikkaan.

Linkkeri voi myös upottaa toimintoja, joiden lähdekoodia ei ole saatavilla, kuten kirjastofunktiot (katso linkkiajan optimointi ). Suoritusaika voi myös sisällyttää toimintoja ajon aikana . Ympäristöt, kuten Java Hotspot , käyttävät dynaamisia profilointitietoja päättääkseen, mitkä ominaisuudet rakennetaan [11] .

Tässä on yksinkertainen esimerkki "manuaalisesta" upottamisesta lähdetasolla C :ssä :

int pred ( int x ) { jos ( x == 0 ) paluu 0 ; muu paluu x - 1 ; }

Ennen upottamista:

int func ( int y ) { paluu pred ( y ) + pred ( 0 ) + pred ( y + 1 ); }

Upotuksen jälkeen:

int func ( int y ) { int tmp ; jos ( y = = 0 ) tmp = 0 ; muuten tmp = y - 1 ; /* (yksi) */ if ( 0 == 0 ) tmp += 0 ; muuten tmp + = 0-1 ; _ /* (2) */ jos ( y + 1 == 0 ) tmp += 0 ; muuten tmp += ( y + 1 ) - 1 ; /* (3) */ paluu tmp ; }

Huomaa, että tämä on vain esimerkki. Oikeassa C-sovelluksessa olisi parempi käyttää sisäistä toiminnallisuutta, kuten parametroitua makroa tai rivifunktiota jotta kääntäjä muuntaa koodin tällä tavalla. Seuraavassa osiossa luetellaan tapoja optimoida tämä koodi.

Upotus assembler-makroissa

Kokoonpanokielimakrot tarjoavat vaihtoehtoisen lähestymistavan upottamiseen, jossa käskysarja luodaan "laajentamalla" jotain alkuperäistä makrolauseketta (nolla tai useammalla parametrilla). Yksi parametreistä voi määrittää, että käskysarjan sijaan generoidaan erillinen kertaluonteinen aliohjelma , joka sisältää halutun sekvenssin ja jota kutsutaan sisäänrakennetun koodin sijaan. Esimerkki:

MOVE FROM=taulukko1,TO=jono2,INLINE=EI

Heuristiikka

Useita erilaisia ​​upotuksen heuristioita on tutkittu. Tyypillisesti inline-algoritmille annetaan tietty tilabudjetti (ohjelman koon sallittu lisäys). Algoritmi pyrkii rakentamaan arvokkaimmat haasteet ylittämättä tätä budjettia. Tässä mielessä monet upotusalgoritmit mallintavat tyypillisesti reppuongelman ratkaisua . [12] Päättääkseen, mitkä kutsupaikat ovat arvokkaimpia, sisäänrakennetun algoritmin on arvioitava niiden hyöty eli odotettu suoritusajan lyheneminen. Yleensä hyödyn arvioimiseen käytetään profilointidataa eri koodipolkujen suoritustiheydestä. [13]

Profilointidatan lisäksi nykyaikaiset dynaamiset kääntäjät käyttävät useita kehittyneempiä heuristioita, kuten: [6]

  • Arvioimalla, mitkä suorituspolut johtavat suurimman suorituskyvyn kasvuun (mukaan lukien kääntäjän lisäoptimoinnit sisällyttämisen jälkeen) ja lisäävät tällaisten polkujen hyötysuhdetta algoritmille.
  • Upottamisen kannattavuuskynnyksen mukautuva säätö käännösyksikön koon ja jo sisällytetyn koodin määrän perusteella.
  • Rutiinien ryhmittäminen klusteriin ja kokonaisten klustereiden upottaminen yksittäisten rutiinien sijaan. Tässä tapauksessa heuristinen valitsee klusterit ryhmittelymenetelmillä, joille klusterin oikean osajoukon rivitys johtaa huonompaan suorituskykyyn kuin ei rivitystä.

Edut

Inlineing itsessään on jo optimointia, koska se eliminoi puhelun ylimääräiset kustannukset, mutta sen tärkein etu on, että se on yhteistoiminnallinen optimointi . Tämä tarkoittaa, että sen jälkeen, kun funktion runko on sisällytetty kutsupaikkaan (usein argumenteilla, jotka voivat olla kiinteitä vakioita ), kääntäjällä on mahdollisuus suorittaa aiemmin mahdottomia muunnoksia. Saattaa esimerkiksi käydä ilmi, että haaralauseen ehto on aina tosi (tai aina epätosi) kyseiselle puhelusivustolle. Tämä puolestaan ​​johtaa kuolleen koodin poistamiseen, invariantin koodin poimimiseen silmukasta tai induktiivisen muuttujan poistamiseen .

Yllä olevassa C-esimerkissä on runsaasti tilaa lisäoptimoinnille. Kääntäjä voi tehdä seuraavaa:

  • Lausekkeet tmp += 0(2) ja (3) merkityillä riveillä eivät tee mitään. Kääntäjä voi poistaa ne.
  • Ehto 0 == 0on aina tosi, joten kääntäjä voi korvata rivin (2) sanalla tmp += 0(joka ei tee mitään).
  • Kääntäjä voi kirjoittaa ehdon uudelleen y+1 == 0muotoon y == -1.
  • Kääntäjä voi lyhentää lausekkeen (y + 1) - 1vain muotoon y.
  • Lausekkeet yja y+1eivät voi olla yhtä suuria kuin nolla. Näin kääntäjä voi poistaa yhden tarkistuksen.
  • Muotolausekkeissa if (y == 0) return yarvo ytunnetaan rungossa ja voidaan rivittää.

Tämän seurauksena toiminto näyttää tältä:

int func ( int y ) { jos ( y == 0 ) paluu 0 ; jos ( y == -1 ) paluu -2 ; paluu 2 * y - 1 ; }

Rajoitukset

Täysi rivitys ei aina ole mahdollista rekursion vuoksi : rekursiivinen puhelun rivitys ei koskaan valmistu. Tähän ongelmaan on olemassa useita ratkaisuja, kuten upottaminen tiettyyn rajaan asti tai puhelukaavion analysointi ja syklien katkaiseminen tietyissä solmuissa (eli upotussyklin katkaiseminen joissakin paikoissa) [14] . Samanlainen ongelma ilmenee makrolaajennuksessa - rekursiivinen laajennus ei lopu koskaan. Yleensä tämä ratkaistaan ​​yksinkertaisella kiellolla käyttää rekursiivisia makroja (kuten C ja C ++).

Vertailu makroihin

Perinteisesti C :n kaltaisilla kielillä rivitys tehtiin lähdetasolla käyttämällä parametroituja makroja. C99 :ssä saatavilla olevalla kyvyllä käyttää todellista funktion inliningiä on useita etuja:

  • C-kielessä makrot eivät suorita tyyppitarkistusta eivätkä edes tarkista argumenttien muodostuksen oikeellisuutta, toisin kuin funktiokutsut.
  • C-makrossa paluuavainsanalla on eri merkitys kuin funktiossa (se aiheuttaa kutsuvan funktion poistumisen, ei makron). Toisin sanoen makro ei voi palauttaa mitään, mikä ei ole sen viimeisen lausekkeen tulos.
  • Koska C-makrot käyttävät pelkkää tekstin korvaamista, tämä voi johtaa tahattomiin sivuvaikutuksiin ja tehottomuuteen argumentin uudelleenlaskennan ja operaattorin ensisijaisuuden vuoksi .
  • Kääntäjävirheitä makroissa on usein vaikea ymmärtää, koska ne viittaavat lisättyyn koodiin eivätkä ohjelmoijan kirjoittamaan koodiin. Siksi inline-koodin virheenkorjaustiedot ovat yleensä hyödyllisempiä kuin tiedot koodista, jossa on laajennettuja makroja.
  • Monet konstruktit ovat epämukavia tai mahdottomia ilmaista makroja käyttämällä, tai tarvitaan huomattavasti erilainen syntaksia. Sisäiset funktiot käyttävät samaa syntaksia kuin tavalliset funktiot, ja ne voidaan helposti ilmoittaa tekstin sisäisiksi tai ei-riviksi.

Monet kääntäjät voivat myös sisällyttää joitain rekursiivisia funktioita [15] ja rekursiiviset makrot ovat yleensä kiellettyjä.

C++:n luoja Björn Stroustrup haluaa korostaa, että makroja tulee välttää aina kun mahdollista, ja suosittelee useammin sisäisten funktioiden käyttöä.

Valintamenetelmät

Monet kääntäjät toimivat aggressiivisesti rivissä aina, kun siitä on hyötyä. Vaikka tämä voi kasvattaa suoritettavan -tiedoston kokoa , tämä aggressiivinen strategia yleensä kannattaa, koska tietokonejärjestelmän muisti kasvaa nopeammin kuin prosessorin nopeus. Sisäänrakentaminen on kriittinen optimointi toiminnallisille ja olio- ohjelmointikielille: ne käyttävät yleensä pieniä funktioita, ja inlining antaa niille tarpeeksi kontekstia, jotta klassinen optimointi olisi tehokasta.

Ohjelmointikielien tuki

Monet kielet, mukaan lukien Java ja toiminnalliset kielet , eivät tarjoa kielirakenteita funktioiden sisällyttämiseen, mutta niiden kääntäjät tai tulkit toimivat automaattisesti aggressiivisesti. [6] Muut kielet tarjoavat syntaksin eksplisiittisille ohjeille, yleensä kääntäjäohjeiden (pragma) muodossa .

Ada - ohjelmointikielessä on ohje sisäänrakennetuille funktioille.

Common Lispissä voit määrittää funktioita tekstin sisäisiksi määrityksiksi inline: [16]

( declaim ( inline dispatch )) ( defun dispatch ( x ) ( funcall ( get ( auto x ) 'lähetys ) x ))

GHC Haskell -kääntäjä yrittää automaattisesti sisällyttää riittävän pieniä funktioita, mutta on myös mahdollista käyttää direktiivejä osoittamaan tarvetta sisällyttää direktiiviin: [17]

key_function :: Int -> String -> ( Bool , Double ) {-# INLINE key_function #-}

C ja C++

C- ja C++-kielillä on avainsana inline. Se toimii samanaikaisesti kääntäjädirektiivinä, joka osoittaa, että rivitys on "toivottu" mutta ei "pakollinen" ja muuttaa myös toiminnon näkyvyyttä ja linkkerin käyttäytymistä. Muutos näkyvyyteen on tarpeen, jotta funktio voidaan rivittää käyttämällä standardia C-työkalupakkia: jossa yksittäisten tiedostojen (tai käännösyksiköiden ) kokoaminen edeltää linkittämistä. Siksi, jotta linkkeri sisäisiin funktioihin, ne on ilmoitettava otsikossa (jotta ne ovat näkyvissä) ja merkitä inline(jotta vältetään moniselitteisyys useista määritelmistä).

Katso myös

Muistiinpanot

  1. Muistin kulutus on "käskyjen lukumäärä". Tämä on ohjelman käyttämä tila RAM-muistissa ja itse suoritettavan tiedoston koko .
  2. Koodin kokoa pienennetään funktioille, joita kutsutaan vain kerran, tai erittäin pienille funktioille, joissa puhelun hinta on suurempi kuin funktion runko

Linkit

  1. 1 2 Chen, Chang, Conte, Hwu, 1993 .
  2. Jones, Marlow, 1999 , 8. Aiheeseen liittyvää työtä, s. 17.
  3. Chen, Chang, Conte, Hwu, 1993 , 3.4 Toiminnon sisäinen laajennus, s. 14.
  4. Jones, Marlow, 1999 , 8. Katso myös (aiheeseen liittyvä työ) s. 17.
  5. Habr: Pakoanalyysi ja skalarisointi: Anna GC:n levätä
  6. 1 2 3 [1] Arkistoitu 8. marraskuuta 2020, Wayback Machine Prokopets et al., An Optimization Driven Incremental Inline Substitution Algorithm for Just-In-Time Compilers, CGO'19-julkaisu JVM Graal -kääntäjässä käytetystä rakentajasta
  7. Chen, Chang, Conte, Hwu, 1993 , 3.4 Toiminnon sisäinen laajennus, s. 19-20.
  8. 1 2 Benjamin Poulain. Epätavallinen nopeuden lisäys: koolla on väliä (8. elokuuta 2013). Haettu 4. joulukuuta 2020. Arkistoitu alkuperäisestä 12. marraskuuta 2020.
  9. Katso esimerkiksi Adaptive System Optimization Arkistoitu 9. elokuuta 2011 Wayback Machine Jikes RVM -kääntäjässä Javalle.
  10. Chen, Chang, Conte, Hwu, 1993 , 3.4 Toiminnon sisäinen laajennus, s. 24-26.
  11. [2] Arkistoitu 8. marraskuuta 2020 Wayback Machinessa Kuvaus Graal JIT -kääntäjässä Javalle käytetystä inlineristä
  12. [3] Scheifler, Function Inlining Analysis for a Structured Programming Language
  13. [4] Matthew Arnold, Steven Fink, Vivek Sarkar ja Peter F. Sweeney, Vertaileva tutkimus staattisista ja profiilipohjaisista heuristioista inliningille
  14. Jones, Marlow, 1999 , 4. Ensuring Termination, s. 6-9.
  15. ja sisäinen semantiikka rekursiivisille alirutiineille ", Henry Baker
  16. Ilmoitus INLINE , NOTINLINE Arkistoitu 14. tammikuuta 2020 Wayback Machinessa Common Lisp HyperSpec -standardissa
  17. 7.13.5.1. INLINE pragma Arkistoitu 6. toukokuuta 2014 Wayback Machinessa Luku 7. GHC-kieliominaisuudet

Kirjallisuus

Ulkoiset linkit