Alkukantaisten pitäminen Jaavalla

alkukielet ovat olleet osa Java-ohjelmointikieltä sen ensimmäisestä julkaisusta lähtien vuonna 1996, ja silti ne ovat edelleen yksi kiistanalaisimmista kielen ominaisuuksista. John Moore puolustaa vahvasti primitiivien säilyttämistä Javan kielessä vertaamalla yksinkertaisia Java-vertailuarvoja sekä primitiivien kanssa että ilman niitä. Tämän jälkeen hän vertaa Javan suorituskykyä Scalan, C++: n ja JavaScriptin suorituskykyyn tietyntyyppisessä sovelluksessa, jossa primitiivisillä on merkittävä ero.

kysymys: Mitkä ovat kolme tärkeintä tekijää kiinteistöjen hankinnassa?
vastaus: sijainti, sijainti, sijainti.

tämän vanhan ja usein käytetyn sanonnan tarkoituksena on antaa ymmärtää, että sijainti hallitsee täysin kaikkia muita tekijöitä, kun on kyse kiinteistöstä. Samankaltaisessa argumentissa kolme tärkeintä tekijää, jotka on otettava huomioon primitiivisten tyyppien käyttämiseksi Jaavalla, ovat suorituskyky, suorituskyky, suorituskyky. Kiinteistöargumentilla ja primitiiviargumentilla on kaksi eroa. Ensinnäkin kiinteistöillä sijainti hallitsee lähes kaikissa tilanteissa, mutta primitiivisten tyyppien käyttämisestä saatavat suoritusvoitot voivat vaihdella suuresti eri sovellusten välillä. Toiseksi kiinteistöihin liittyy muitakin huomioon otettavia tekijöitä, vaikka ne ovat yleensä vähäisiä sijaintiin nähden. Alkeellisilla tyypeillä on vain yksi syy käyttää niitä-suorituskyky; ja sitten vain, jos sovellus on sellainen, joka voi hyötyä niiden käytöstä.

Primitives tarjoaa vain vähän arvoa useimmille liiketoimintaan liittyville ja Internet-sovelluksille, jotka käyttävät asiakas-palvelin-ohjelmointimallia, jonka taustajärjestelmässä on tietokanta. Mutta numeeristen laskelmien hallitsemien sovellusten suorituskyky voi hyötyä suuresti primitiivien käytöstä.

alkukielisten sisällyttäminen Jaavaan on ollut yksi kiistanalaisimmista kielenmuotoilupäätöksistä, mistä osoituksena on tähän päätökseen liittyvien artikkelien ja foorumipostausten määrä. Simon Ritter totesi JAX Londonissa marraskuussa 2011 pitämässään pääpuheessa, että primitiivien poistamista Javan tulevassa versiossa harkittiin vakavasti (katso slide 41). Tässä artikkelissa aion lyhyesti esitellä primitives ja Javan dual-tyyppi järjestelmä. Koodinäytteiden ja yksinkertaisten vertailuarvojen avulla teen kantani siihen, miksi Java primitiivejä tarvitaan tietyntyyppisiin sovelluksiin. Aion myös verrata Javan suorituskykyä Scala, C++, ja JavaScript.

primitiiviset vs. objektit

kuten luultavasti jo tiedät, jos luet tätä artikkelia, Javalla on kaksityyppinen järjestelmä, jota kutsutaan yleensä primitiivisiksi tyypeiksi ja objektityypeiksi, jotka usein lyhennetään yksinkertaisesti primitiivisiksi ja objekteiksi. Javassa on ennalta määriteltyjä primitiivisiä tyyppejä kahdeksan, ja niiden nimet ovat varattuja avainsanoja. Yleisesti käytettyjä esimerkkejä ovat int, double ja boolean. Pohjimmiltaan kaikki muut tyypit Javassa, mukaan lukien kaikki käyttäjän määrittelemät tyypit, ovat oliotyyppejä. (Sanon ”periaatteessa”, koska array tyypit ovat hieman hybridi, mutta ne ovat paljon enemmän objektityypit kuin primitiivinen tyypit.) Jokaiselle primitiiviselle tyypille on olemassa vastaava kääreluokka, joka on objektityyppi; esimerkkejä ovat Integer int, Double double ja Boolean boolean.

primitiiviset tyypit ovat arvoperusteisia, mutta oliotyypit ovat referenssipohjaisia, ja siinä piilee sekä primitiivisten tyyppien voima että kiistojen lähde. Eron valaisemiseksi tarkastellaan seuraavia kahta julistusta. Ensimmäisessä ilmoituksessa käytetään alkeellista tyyppiä ja toisessa kääreluokkaa.

int n1 = 100;Integer n2 = new Integer(100);

JDK 5: een lisätyn autoboksauksen avulla voisin lyhentää toisen julistuksen yksinkertaisesti

Integer n2 = 100;

, mutta taustalla oleva semantiikka ei muutu. Autoboksaus yksinkertaistaa kääreluokkien käyttöä ja vähentää ohjelmoijan kirjoittaman koodin määrää, mutta se ei muuta suorituksen aikana mitään.

alkukantaisen n1 ja kääreesineen n2 eroa havainnollistaa Kuvan 1 kaavio.

John I. Moore, Jr.

Kuva 1. Muistin asettelu primitives vs. objektit

muuttuja n1 sisältää kokonaisluvun arvon, mutta muuttuja n2 sisältää viittauksen objektiin, ja se on objekti, jolla on kokonaisluvun arvo. Lisäksi n2 viitattu kohde sisältää myös viittauksen luokan objektiin Double.

primitiivisten ongelma

ennen kuin yritän vakuuttaa primitiivisten tyyppien tarpeellisuudesta, minun on tunnustettava, että monet ihmiset eivät ole kanssani samaa mieltä. Sherman Alpert esittää teoksessaan ”primitiiviset tyypit, joita pidetään haitallisina”, että primitiiviset ovat haitallisia, koska ne sekoittavat ” proseduraalisen semantiikan muuten yhtenäiseen objektiorientoituneeseen malliin. Primitiivit eivät ole ensimmäisen luokan esineitä, mutta ne ovat olemassa kielellä, joka sisältää pääasiassa ensimmäisen luokan esineitä.”Primitiivit ja esineet (kääreluokkien muodossa) tarjoavat kaksi tapaa käsitellä loogisesti samanlaisia tyyppejä, mutta niillä on hyvin erilaiset perussemantiikat. Miten esimerkiksi kahta tapausta pitäisi verrata tasa-arvon kannalta? Primitiivisille tyypeille käytetään == – operaattoria, mutta olioille suositellaan equals() – menetelmää, joka ei ole primitiivisille vaihtoehto. Vastaavasti erilaisia semantiikoita on olemassa annettaessa arvoja tai siirrettäessä parametreja. Jopa oletusarvot ovat erilaisia; esim. 0 int vs. null Integer.

aiheesta löytyy lisää taustatietoa Eric Brunon blogikirjoituksesta ”a modern primitive discussion”, jossa on yhteenveto alkukantaisten hyvistä ja huonoista puolista. Useissa Stack Overflow – keskusteluissa keskitytään myös primitiivisiin, kuten ”Miksi ihmiset käyttävät yhä primitiivisiä tyyppejä Jaavalla?”ja” onko syytä käyttää aina esineitä alkukantaisten sijaan?.”Programmers Pino Exchange isännöi samanlaista keskustelua otsikolla” When to use primitive vs class in Java?”.

Muistin käyttö

a double Javassa vie muistissa aina 64 bittiä, mutta referenssin koko riippuu Java-virtuaalikoneesta (JVM). Tietokoneellani on Windows 7: n 64-bittinen versio ja 64-bittinen JVM, joten viittaus tietokoneeseeni vie 64 bittiä. Kuvan 1 kaavion perusteella odottaisin yhden double , kuten n1, vievän 8 tavua (64 bittiä), ja odottaisin yhden Double, kuten n2, vievän 24 tavua — 8, jos viitataan objektiin, 8, Jos double arvo tallennetaan objektiin, ja 8, jos viitataan luokan objektiin Double. Lisäksi Java käyttää ylimääräistä muistia tukemaan roskien keräämistä esinetyypeille, mutta ei alkeellisille tyypeille. Mennään katsomaan.

käyttämällä samankaltaista lähestymistapaa kuin Glen Mccluskeylla ” Java primitive types vs. wrappers, ” listauksessa 1 esitetty menetelmä mittaa niiden tavujen määrää, joissa on doublen-by-n-matriisi (kaksiulotteinen matriisi).

listalle 1. Laskemalla muistin käyttöä tyyppi kaksinkertainen

muuttamalla koodia listaus 1 ilmeinen tyyppi muutoksia (ei esitetty), voimme myös mitata määrä tavuja käytössä n-by-n matriisi Double. Kun testaan näitä kahta menetelmää tietokoneellani käyttäen 1000-by-1000-matriiseja, saan alla olevassa taulukossa 1 esitetyt tulokset. Kuten havainnollistaa, primitiivisen tyypin double versio vastaa hieman yli 8 tavua per merkintä matriisissa, suurin piirtein mitä odotin. Oliotyypin Double versio vaati kuitenkin matriisissa hieman yli 28 tavua per merkintä. Näin ollen tässä tapauksessa muistin käyttöaste Double on yli kolminkertainen verrattuna muistin käyttöasteeseen double, minkä ei pitäisi olla yllätys kenellekään, joka ymmärtää yllä olevassa kuvassa 1 esitetyn muistin asettelun.

runtime performance

verrataksemme primitiivien ja olioiden suoritusaikoja tarvitsemme numeeristen laskelmien hallitseman algoritmin. Tähän artikkeliin olen valinnut matriisin kertolasku, ja lasken kahden 1000-by-1000-matriisin kertomiseen tarvittavan ajan. I koodasi matriisikertolaskun double suoraviivaisesti, kuten alla olevassa luettelossa 2 esitetään. Vaikka saattaa olla nopeampia tapoja toteuttaa matriisikertolasku (ehkä käyttämällä samanaikaisuutta), tällä pisteellä ei ole varsinaisesti merkitystä tämän artikkelin kannalta. Tarvitsen vain yhteisen koodin kahdella samanlaisella menetelmällä, joista toisessa käytetään alkukantaista double ja toisessa kääreluokkaa Double. Koodi, jolla kerrotaan kaksi matriisia tyyppiä Double, on täsmälleen samanlainen kuin listauksessa 2, jossa on ilmeiset tyyppimuutokset.

listalle 2. Kertomalla kaksi matriisi tyyppiä kaksinkertainen

juoksin kaksi menetelmää kertoa kaksi 1000-by-1000 matriisi tietokoneellani useita kertoja ja mittasin tulokset. Keskimääräiset ajat on esitetty taulukossa 2. Tällöin double: n suoritusteho on siis yli neljä kertaa nopeampi kuin Double: n. Se on yksinkertaisesti liian suuri ero jätettäväksi huomiotta.

SciMark 2.0-vertailuarvo

tähän mennessä olen käyttänyt matriisilukujen kertolaskun yksittäistä, yksinkertaista vertailuarvoa osoittaakseni, että primitiivit voivat tuottaa huomattavasti suuremman laskentatehon kuin oliot. Vahvistaakseni väitteitäni käytän tieteellisempää vertailukohtaa. SciMark 2.0 on Java-vertailukohta tieteelliseen ja numeeriseen laskentaan, joka on saatavilla National Institute of Standards and Technologyltä (NIST). Latasin lähdekoodin tähän vertailukohtaan ja loin kaksi versiota, alkuperäisen version käyttäen primitiivejä ja toisen version käyttäen kääreluokkia. Toisessa versiossa I korvattiin int Integer ja double Double, jotta kääreluokkien käyttö olisi täysin mahdollista. Molemmat versiot löytyvät tämän artikkelin lähdekoodista.

download

John I. Moore, Jr.

SciMark benchmark mittaa useiden laskennallisten rutiinien suorituskykyä ja raportoi yhdistelmäarvosanan likimääräisinä Mflops-arvoina (miljoonia liukulukuoperaatioita sekunnissa). Näin ollen suuremmat luvut ovat parempia tässä vertailukohdassa. Taulukossa 3 esitetään keskimääräiset yhdistelmäpisteet tämän vertailuarvon kunkin version useista suorituksista tietokoneellani. Kuten kävi ilmi, scimark 2.0-vertailuversion kahden version ajonaikaiset suoritukset vastasivat edellä esitettyjä matriisikertolaskutuloksia, sillä primitiivejä sisältävä versio oli lähes viisi kertaa nopeampi kuin kääreluokkia käyttävä versio.

olet nähnyt muutaman muunnelman Java-ohjelmista, jotka tekevät numeerisia laskelmia, käyttäen sekä omaa että tieteellisempää vertailukohtaa. Mutta miten Java vertautuu muihin kieliin? Lopetan vilkaisemalla nopeasti, miten Javan suorituskyky vertautuu kolmen muun ohjelmointikielen: Scalan, C++: n ja JavaScriptin suorituskykyyn.

Benchmarking Scala

Scala on JVM: llä toimiva ohjelmointikieli, joka näyttää kasvattavan suosiotaan. Scalassa on yhtenäinen tyyppijärjestelmä, eli se ei tee eroa primitiivien ja objektien välillä. Erik Osheimin mukaan Scalan numeerisessa tyyppiluokassa (Pt. 1), Scala käyttää primitiivisiä tyyppejä mahdollisuuksien mukaan, mutta käyttää esineitä tarvittaessa. Samoin Martin Oderskyn kuvaus Scalan matriiseista sanoo, että”… Scala-array Array esiintyy Jaavana int, Array Jaavana double …”

tarkoittaako tämä siis sitä, että Scalan unified type-järjestelmällä on ajonaikainen suorituskyky, joka on verrattavissa Javan primitiivisiin tyyppeihin? Katsotaanpa.

en ole yhtä taitava Scalan kanssa kuin Javan kanssa, mutta yritin muuntaa matriisin kertolaskun vertailukoodin suoraan Javasta Scalaksi. Tulos näkyy alla olevassa listauksessa 3. Kun ajoin Scala-version vertailuarvosta tietokoneellani, se oli keskimäärin 12.30 sekuntia, mikä asettaa Scalan suorituskyvyn hyvin lähelle Javan primitiivejä. Tulos on paljon parempi kuin odotin ja tukee väitteitä siitä, miten Scala käsittelee numeerisia tyyppejä.

download

John I. Moore, Jr.

listasi 3. Kertomalla kaksi matriisia Scalassa

Benchmarking C++

koska C++ toimii suoraan ”paljaalla metallilla” eikä virtuaalikoneessa, voisi luonnollisesti olettaa C++: n toimivan Javaa nopeammin. Lisäksi Javan suorituskykyä vähentää hieman se, että Java tarkistaa käyttöoikeudet matriiseihin varmistaakseen, että jokainen indeksi on array ilmoitettujen rajojen sisällä, kun taas C++ ei (C++ – ominaisuus, joka voi johtaa Puskurin ylivuotoon, jota hakkerit voivat hyödyntää). Löysin C++ on hieman hankalampi kuin Java käsiteltäessä perus kaksiulotteisia taulukoita, mutta onneksi paljon tästä awkwardness voidaan piilottaa sisällä yksityisiä osia luokan. C++: lle loin yksinkertaisen version Matrix luokasta ja ylikuormitin operaattorin * kahden matriisin kertomiseen, mutta perusmatriisin kertolaskualgoritmi muunnettiin suoraan Java-versiosta. C++ – lähdekoodi näkyy listauksessa 4.

download

John I. Moore, Jr.

listasi 4. Kertomalla kaksi matriisia C++

käyttäen Eclipse CDT: tä (Eclipse for C++ Developers) MinGW C++ – kääntäjällä on mahdollista luoda sekä debug-että julkaisuversioita sovelluksesta. Testatakseni C++: aa suoritin julkaisuversion useita kertoja ja tein tulosten keskiarvon. Kuten odotettiin, C++ juoksi huomattavasti nopeammin tällä yksinkertaisella vertailuarvolla, keskimäärin 7.58 sekuntia tietokoneellani. Jos raw-suorituskyky on ensisijainen tekijä ohjelmointikielen valinnassa, C++ on valintakieli numeerisesti intensiivisissä sovelluksissa.

Benchmarking JavaScript

Okei, tämä yllätti. Koska JavaScript on hyvin dynaaminen kieli, odotin sen suorituskyvyn olevan kaikkein huonoin, jopa huonompi kuin Java kääre luokat. Mutta itse asiassa, JavaScript suorituskyky oli paljon lähempänä kuin Java primitives. Testata JavaScript asensin solmu.JS, JavaScript-moottori, jolla on maine erittäin tehokkaana. Tulos oli keskimäärin 15,91 sekuntia. Listing 5 näyttää JavaScript versio matriisin kertolasku benchmark että juoksin solmu.js

download

John I. Moore, Jr.

listasi 5. Kahden matriisin kertominen Javascriptissä

johtopäätöksissä

kun Java saapui näyttämölle noin 18 vuotta sitten, se ei ollut suorituskyvyn kannalta paras kieli numeeristen laskelmien hallitsemille sovelluksille. Mutta ajan myötä, teknologian kehityksen aloilla, kuten just-In-time (JIT) compilation (aka Adaptiivinen tai dynaaminen compilation), Javan suorituskyky tämäntyyppisissä sovelluksissa on nyt verrattavissa kielten, jotka on koottu natiivikoodiksi, kun primitiivisiä tyyppejä käytetään.

lisäksi primitiivit poistavat roskien keräämisen tarpeen, jolloin primitiiveillä on toinen suoritusetu esinetyyppeihin nähden. Taulukossa 4 on yhteenveto matriisin kertolaskun vertailuarvon suoritusajasta tietokoneellani. Muut tekijät, kuten ylläpidettävyys, siirrettävyys, ja Kehittäjä asiantuntemus tekevät Java parempi valinta monia tällaisia sovelluksia.

kuten aiemmin on puhuttu, Oracle näyttää harkitsevan vakavasti alkukantaisten poistamista Javan tulevassa versiossa. Ellei Java-kääntäjä voi luoda koodia, jonka suorituskyky on verrattavissa alkukantaisten, mielestäni niiden poistaminen Javasta estäisi Javan käytön tietyissä sovellusluokissa; nimittäin niissä sovelluksissa, joita hallitsevat numeeriset laskelmat. Tässä artikkelissa olen käyttänyt yksinkertaista vertailukohtaa, joka perustuu matriisin kertolaskuun, ja tieteellisempää vertailukohtaa, SciMark 2.0, argumentoidakseni tätä asiaa.

tekijästä

John I. Moore, Jr., Matematiikan ja tietojenkäsittelytieteen professorina Citadelissa, hänellä on laaja kokemus sekä teollisuudesta että akatemiasta, erityisosaamista olio-orientoituneen teknologian, ohjelmistotekniikan ja sovelletun matematiikan aloilta. Yli kolmen vuosikymmenen ajan hän on suunnitellut ja kehittänyt relaatiotietokantoja ja useita korkean tason kieliä hyödyntäviä ohjelmistoja, ja hän on työskennellyt laajasti Javalla versiosta 1.1 lähtien. Lisäksi hän on kehittänyt ja opettanut lukuisia tietojenkäsittelytieteen edistyneitä aiheita käsitteleviä akateemisia kursseja ja teollisia seminaareja.

Jatkoluku

  1. Paul Krill kirjoitti Oraclen pitkän kantaman suunnitelmista Jaavalle teoksessaan ”Oracle lays out long-range Java intentions” (JavaWorld, maaliskuu 2012). Tämä artikkeli, sekä siihen liittyvät kommentit Lanka, motivoi minua kirjoittamaan tämän puolustuksen primitives.
  2. Szymon Guz kirjoittaa tuloksistaan primitiivisten tyyppien ja kääreluokkien vertailussa julkaisussa ”Primitives and objects benchmarking in Java” (SimonOnSoftware, tammikuu 2011).
  3. C++: n ohjelmoinnin periaatteet ja käytäntö — tukisivustolla (Addison-Wesley, 2009) C++: n luoja Bjarne Stroustrup tarjoaa toteutuksen matriisiluokalle, joka on paljon täydellisempi kuin tähän artikkeliin liitetty.
  4. John Rose, Brian Goetz ja Guy Steele keskustelevat arvotyypeiksi kutsutusta käsitteestä ”arvojen tilassa” (OpenJDK.net huhtikuuta 2014). Arvotyypit voidaan ajatella muuttumattomina käyttäjän määritteleminä aggregaatteina ilman identiteettiä, yhdistämällä sekä olioiden että primitiivien ominaisuuksia. Mantra arvotyypeille on ” koodit kuin luokka, toimii kuin int.”

Vastaa

Sähköpostiosoitettasi ei julkaista.