Et tilfelle for å holde primitiver I Java

Primitiver har vært En Del Av Java-programmeringsspråket siden den første utgivelsen i 1996, og likevel forblir de et av de mer kontroversielle språkfunksjonene. John Moore gjør en sterk sak for å holde primitiver I Java-språket ved å sammenligne enkle Java benchmarks, både med og uten primitiver. Han sammenligner Deretter Ytelsen Til Java Til Scala, C++ og JavaScript i en bestemt type applikasjon, hvor primitiver gjør en merkbar forskjell.

Spørsmål: Hva er de tre viktigste faktorene i å kjøpe fast eiendom?
Svar: Plassering, plassering, plassering.

dette gamle og ofte brukte ordtaket er ment å antyde at plasseringen helt dominerer alle andre faktorer når det gjelder fast eiendom. I et lignende argument er de tre viktigste faktorene å vurdere for å bruke primitive typer I Java ytelse, ytelse, ytelse. Det er to forskjeller mellom argumentet for fast eiendom og argumentet for primitiver. Først med fast eiendom dominerer plasseringen i nesten alle situasjoner, men ytelsesgevinstene ved å bruke primitive typer kan variere sterkt fra en type applikasjon til en annen. For det andre, med fast eiendom, er det andre faktorer å vurdere, selv om de vanligvis er små i forhold til plassering. Med primitive typer er det bare en grunn til å bruke dem-ytelse; og da bare hvis applikasjonen er den typen som kan dra nytte av deres bruk.

Primitiver gir liten verdi til de fleste forretningsrelaterte og Internett-applikasjoner som bruker en klient-server programmeringsmodell med en database på backend. Men ytelsen til applikasjoner som domineres av numeriske beregninger, kan ha stor nytte av bruken av primitiver.

inkludering av primitiver I Java har vært en av de mer kontroversielle språk design beslutninger, som dokumentert av antall artikler og foruminnlegg knyttet til denne beslutningen. Simon Ritter bemerket I sin jax London i November 2011 keynote adresse som seriøs vurdering ble gitt til fjerning av primitiver i en fremtidig versjon av Java (se lysbilde 41). I denne artikkelen vil jeg kort introdusere primitiver og Java dual-type system. Ved hjelp av kodeeksempler og enkle referanser, vil jeg gjøre saken min for Hvorfor Java primitiver er nødvendig for visse typer applikasjoner. Jeg vil også sammenligne Javas ytelse med Scala, C++ og JavaScript.

Primitiver versus objekter

Som du sikkert allerede vet om du leser denne artikkelen, Har Java et dual-type system, vanligvis referert til som primitive typer og objekttyper, ofte forkortet bare som primitiver og objekter. Det er åtte primitive typer forhåndsdefinert I Java, og navnene deres er reserverte søkeord. Vanlige eksempler er int, double og boolean. I Hovedsak er alle Andre typer I Java, inkludert alle brukerdefinerte typer, objekttyper. (Jeg sier «i hovedsak» fordi array typer er litt av en hybrid, men de er mye mer som objekttyper enn primitive typer.) For hver primitiv type er det en tilsvarende innpakningsklasse som er en objekttype; eksempler er Integer for int, Double for double og Boolean for boolean.

Primitive typer er verdibaserte, men objekttyper er referansebaserte, og der ligger både kraften og kilden til kontrovers av primitive typer. For å illustrere forskjellen, vurder de to erklæringene nedenfor. Den første erklæringen bruker en primitiv type og den andre bruker en wrapper klasse.

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

Ved hjelp av autoboxing, en funksjon lagt TIL JDK 5, kunne jeg forkorte den andre erklæringen til bare

Integer n2 = 100;

, men den underliggende semantikken endres ikke. Autoboxing forenkler bruken av wrapper klasser og reduserer mengden kode en programmerer må skrive, men det endrer ikke noe under kjøring.

forskjellen mellom det primitive n1 og innpakningsobjektet n2 er illustrert med diagrammet I Figur 1.

John I. Moore, Jr.

Figur 1. Minne layout av primitiver versus objekter

variabelen n1 har en heltallsverdi, men variabelen n2 inneholder en referanse til et objekt, og det er objektet som holder heltallsverdien. I tillegg inneholder objektet referert av n2 også en referanse til klasseobjektet Double.

problemet med primitiver

Før jeg prøver å overbevise deg om behovet for primitive typer, bør jeg erkjenne at mange mennesker ikke vil være enige med meg. Sherman Alpert i» Primitive typer anses skadelig «hevder at primitiver er skadelig fordi de blander» prosessuelle semantikk i en ellers ensartet objektorientert modell. Primitiver er ikke førsteklasses objekter, men de eksisterer på et språk som hovedsakelig involverer førsteklasses objekter.»Primitiver og objekter (i form av wrapper klasser) gir to måter å håndtere logisk lignende typer, men de har svært forskjellige underliggende semantikk. For eksempel, hvordan skal to tilfeller sammenlignes for likestilling? For primitive typer bruker man operatoren ==, men for objekter er det foretrukne valget å ringe equals() – metoden, som ikke er et alternativ for primitiver. På samme måte eksisterer forskjellige semantikk når man tilordner verdier eller passerer parametere. Selv standardverdiene er forskjellige; f. eks. 0 for int versus null for Integer.

For mer bakgrunn om dette problemet, se Eric Brunos blogginnlegg, «En moderne primitiv diskusjon», som oppsummerer noen av fordeler og ulemper med primitiver. En rekke diskusjoner om Stack Overflow fokuserer også på primitiver ,inkludert » Hvorfor bruker folk fortsatt primitive typer I Java?»og» Er det en grunn til å alltid bruke Objekter i stedet for primitiver?.»Programmers Stack Exchange er vert for en lignende diskusjon med tittelen» Når skal du bruke primitive vs class I Java ?».

Minneutnyttelse

a double I Java opptar alltid 64 biter i minnet, men størrelsen på en referanse avhenger Av Java virtual machine (JVM). Min datamaskin kjører 64-bitversjonen Av Windows 7 og en 64-bit JVM, og derfor tar en referanse på datamaskinen min 64 bits. Basert på diagrammet I Figur 1 forventer jeg at en enkelt double som n1 skal okkupere 8 byte (64 bits), og jeg forventer at en enkelt Double som n2 skal okkupere 24 byte — 8 for referansen til objektet, 8 for double – verdien lagret i objektet, og 8 for referansen til klasseobjektet for Double. Plus, Java bruker ekstra minne for å støtte søppelsamling for objekter typer, men ikke for primitive typer. La oss sjekke det ut.

Ved hjelp av en tilnærming som ligner På Glen McCluskey i » Java primitive typer vs. wrappers, » metoden vist I Notering 1 måler antall byte okkupert av en n-by-n matrise (todimensjonal array) av double.

Oppføring 1. Beregning av minneutnyttelse av type double

Endring av koden i Oppføring 1 med de åpenbare typeendringene (ikke vist), kan vi også måle antall byte okkupert av en n-by-n-matrise på Double. Når jeg tester disse to metodene på datamaskinen min ved hjelp av 1000-by-1000 matriser, får jeg resultatene vist I Tabell 1 nedenfor. Som illustrert tilsvarer versjonen for primitiv type double litt mer enn 8 byte per oppføring i matrisen, omtrent det jeg forventet. Versjonen for objekttype Double krevde imidlertid litt mer enn 28 byte per oppføring i matrisen. I dette tilfellet er minneutnyttelsen av Double mer enn tre ganger minneutnyttelsen av double, noe som ikke bør være en overraskelse for alle som forstår minneoppsettet illustrert i Figur 1 ovenfor.

Runtime performance

for å sammenligne runtime forestillinger for primitiver og objekter, trenger vi en algoritme dominert av numeriske beregninger. For denne artikkelen har jeg valgt matrisemultiplikasjon, og jeg beregner tiden som kreves for å multiplisere to 1000-by-1000 matriser. Jeg kodet matrisemultiplikasjon for double på en enkel måte som vist i Liste 2 nedenfor. Selv om det kan være raskere måter å implementere matrisemultiplikasjon (kanskje ved hjelp av samtidighet), er dette punktet ikke veldig relevant for denne artikkelen. Alt jeg trenger er vanlig kode i to lignende metoder, en som bruker primitive double og en som bruker wrapper-klassen Double. Koden for å multiplisere to matriser av typen Double er akkurat slik I Notering 2 med de åpenbare typeendringene.

Oppføring 2. Multiplisere to matriser av typen dobbel

jeg kjørte de to metodene for å multiplisere to 1000-by-1000 matriser på datamaskinen min flere ganger og målte resultatene. Gjennomsnittstidene er vist I Tabell 2. I dette tilfellet er runtime-ytelsen til double mer enn fire ganger så rask som for Double. Det er rett og slett for mye av en forskjell å ignorere.

SciMark 2.0 benchmark

Så langt har jeg brukt det enkle, enkle benchmark av matrisemultiplikasjon for å demonstrere at primitiver kan gi betydelig større databehandlingsytelse enn objekter. For å forsterke mine påstander vil jeg bruke en mer vitenskapelig benchmark. SciMark 2.0 Er En Java benchmark for vitenskapelig og numerisk databehandling tilgjengelig fra National Institute Of Standards And Technology (NIST). Jeg lastet ned kildekoden for denne referansen og opprettet to versjoner, den opprinnelige versjonen ved hjelp av primitiver og en annen versjon ved hjelp av wrapper klasser. For den andre versjonen erstattet jeg int med Integer og double med Double for å få full effekt av å bruke wrapper klasser. Begge versjonene er tilgjengelige i kildekoden for denne artikkelen.

last ned

John I. Moore, Jr.

scimark benchmark måler ytelsen til flere beregningsrutiner og rapporterer en sammensatt score i omtrentlige Mflops(millioner av flyttallsoperasjoner per sekund). Dermed er større tall bedre for denne referansen. Tabell 3 gir gjennomsnittlig sammensatt score fra flere kjøringer av hver versjon av denne referansen på min datamaskin. Som vist, runtime forestillinger av De to versjonene Av SciMark 2.0 benchmark var i samsvar med matrise multiplikasjon resultatene ovenfor i at versjonen med primitiver var nesten fem ganger raskere enn den versjonen ved hjelp av wrapper klasser.

Du har sett noen variasjoner Av Java-programmer som gjør numeriske beregninger, ved hjelp av både en hjemmelaget benchmark og en mer vitenskapelig. Hvordan Er Java sammenlignet med andre språk? Jeg vil konkludere med en rask titt på Hvordan Javas ytelse sammenlignes med tre andre programmeringsspråk: Scala, C++ og JavaScript.

Benchmarking Scala

Scala er et programmeringsspråk som kjører PÅ JVM og ser ut til å bli stadig mer populært. Scala har et enhetlig typesystem, noe som betyr at det ikke skiller mellom primitiver og objekter. Ifølge Erik Osheim i Scalas Numeriske type klasse (Pt. 1), Scala bruker primitive typer når det er mulig, men vil bruke objekter om nødvendig. På Samme måte Sier Martin Oderskys beskrivelse Av Scalas Arrays det»… En Scala-matrise Array er representert Som En Java int, en Array er representert Som En Java double…»

så betyr Dette At Scalas enhetlige type system vil ha runtime ytelse som kan sammenlignes med Javas primitive typer? La oss se.

jeg er ikke så dyktig Med Scala som Jeg er Med Java, men jeg forsøkte å konvertere koden for matrix multiplikasjon benchmark direkte fra Java Til Scala. Resultatet er vist I Liste 3 nedenfor. Da jeg kjørte Scala-versjonen av referansen på datamaskinen min, var den i gjennomsnitt 12,30 sekunder, noe som setter ytelsen Til Scala svært nær Java Med primitiver. Det resultatet er mye bedre enn jeg forventet og støtter påstandene om Hvordan Scala håndterer numeriske typer.

last ned

John I. Moore, Jr.

Oppføring 3. Multiplisere to matriser I Scala

Benchmarking C++

Siden C++ kjører direkte på «bare metall» i stedet for i en virtuell maskin, ville man naturlig forvente At C++ kjører raskere enn Java. Videre Reduseres Java-ytelsen noe av Det Faktum At Java sjekker tilgang til arrays for å sikre at hver indeks er innenfor grensene deklarert for arrayet, Mens C++ ikke gjør det (En C++ – funksjon som kan føre til bufferoverløp, som kan utnyttes av hackere). Jeg fant C++ å være noe vanskeligere Enn Java i å håndtere grunnleggende todimensjonale arrays, men heldigvis kan mye av Denne klosset være skjult inne i de private delene av en klasse. For C++ opprettet jeg en enkel versjon av en klasse Matrix, og jeg overbelastet operatøren * for å multiplisere to matriser, men den grunnleggende matrisemultiplikasjonsalgoritmen ble konvertert direkte fra Java-versjonen. C++ kildekoden er vist i Oppføring 4.

last ned

John I. Moore, Jr.

Oppføring 4. Multiplisere to matriser I C++

Ved Hjelp Av Eclipse CDT (Eclipse For C++ Utviklere) Med mingw C++ kompilatoren, er det mulig å lage både debug og release versjoner av et program. For å teste C++ kjørte jeg utgivelsesversjonen flere ganger og gjennomsnittlig resultatene. Som forventet kjørte C++ merkbart raskere på denne enkle referansen, i gjennomsnitt 7,58 sekunder på datamaskinen min. Hvis rå ytelse er den primære faktoren for å velge et programmeringsspråk, Er C++ det foretrukne språket for numerisk intensive applikasjoner.

Benchmarking JavaScript

Ok, dette overrasket meg. Gitt At JavaScript er et veldig dynamisk språk, forventet jeg at ytelsen skulle være den verste av alt, enda verre Enn Java med wrapper klasser. Men Faktisk Var JavaScript-ytelsen mye nærmere Java med primitiver. For å teste JavaScript installerte Jeg Node.js, En JavaScript-motor med et rykte for å være svært effektiv. Resultatene var i gjennomsnitt 15,91 sekunder. Oppføring 5 viser JavaScript-versjonen av matrix multiplikasjon benchmark som jeg kjørte På Node.js

last ned

John I. Moore, Jr.

Oppføring 5. Multiplisere to matriser I JavaScript

som konklusjon

Da Java først kom på scenen for noen 18 år siden, var Det ikke det beste språket fra et ytelsesperspektiv for applikasjoner som domineres av numeriske beregninger. Men over tid, med teknologiske fremskritt på områder som just-in-time (JIT) kompilering (aka adaptiv eller dynamisk kompilering), Er Javas ytelse for slike applikasjoner nå sammenlignbar med språk som er kompilert i innfødt kode når primitive typer brukes.

dessuten eliminerer primitiver behovet for søppelinnsamling, og gir dermed en annen ytelsesfordel av primitiver over objekttyper. Tabell 4 oppsummerer runtime ytelsen til matrise multiplikasjon benchmark på min datamaskin. Andre faktorer som vedlikehold, portabilitet og utviklerkompetanse gjør Java til et bedre valg for mange slike applikasjoner.

Som tidligere diskutert, Ser Oracle ut til å ta alvorlig hensyn til fjerning av primitiver i en fremtidig Versjon Av Java. Med Mindre Java-kompilatoren kan generere kode med ytelse som er sammenlignbar med primitiver, tror jeg at fjerning fra Java ville utelukke Bruk Av Java for bestemte klasser av applikasjoner; nemlig de applikasjonene dominert av numeriske beregninger. I denne artikkelen har jeg brukt en enkel benchmark basert på matrix multiplikasjon og en mer vitenskapelig benchmark, SciMark 2.0, å argumentere dette punktet.

Om Forfatteren

John I. Moore, Jr. Professor I Matematikk og Informatikk Ved Citadel, har et bredt spekter av erfaring i både industri og akademia, med spesifikk kompetanse innen objektorientert teknologi, software engineering, og anvendt matematikk. I mer enn tre tiår har han designet og utviklet programvare ved hjelp av relasjonsdatabaser og flere høyordensspråk, og han har jobbet mye I Java siden versjon 1.1. I tillegg har han utviklet og undervist en rekke akademiske kurs og industrielle seminarer om avanserte emner innen informatikk.

Videre lesing

  1. Paul Krill skrev Om Oracles langsiktige planer For Java i «Oracle legger ut Langsiktige Java-intensjoner» (JavaWorld, Mars 2012). Denne artikkelen, sammen med den tilhørende kommentartråden, motiverte meg til å skrive dette forsvaret av primitiver.
  2. Szymon Guz skriver om sine resultater i benchmarking primitive typer og wrapper klasser i «Primitiver and objects benchmark in Java» (SimonOnSoftware, januar 2011).
  3. på støttesiden For Programmeringsprinsipper Og Praksis Ved Bruk Av C++ (Addison-Wesley, 2009) gir c++ – skaperen Bjarne Stroustrup en implementering for en matriseklasse som er mye mer komplett enn den som følger med denne artikkelen.
  4. John Rose, Brian Goetz og Guy Steele diskuterer et konsept kalt verdityper i «State Of The Values» (OpenJDK.net, April 2014). Verdityper kan betraktes som uforanderlige brukerdefinerte aggregattyper uten identitet, og kombinerer egenskapene til både objekter og primitiver. Mantraet for verdityper er » koder som en klasse, fungerer som en int .»

Legg igjen en kommentar

Din e-postadresse vil ikke bli publisert.