En sag for at holde primitiver i Java

primitiver har været en del af Java programmeringssprog siden dets første udgivelse i 1996, og alligevel forbliver de et af de mere kontroversielle sprogfunktioner. John Moore gør en stærk sag for at holde primitiver på Java-sproget ved at sammenligne enkle Java-benchmarks, både med og uden primitiver. Han sammenligner derefter Java ‘ s ydeevne med Scala, C++ og JavaScript i en bestemt type applikation, hvor primitiver gør en bemærkelsesværdig forskel.

spørgsmål: Hvad er de tre vigtigste faktorer i køb af fast ejendom?
svar: beliggenhed, beliggenhed, beliggenhed.

dette gamle og ofte anvendte ordsprog er beregnet til at antyde, at placering fuldstændigt dominerer alle andre faktorer, når det kommer til fast ejendom. I et lignende argument er de tre vigtigste faktorer at overveje for at bruge primitive typer i Java ydeevne, ydeevne, ydeevne. Der er to forskelle mellem argumentet for fast ejendom og argumentet for primitiver. Først, med fast ejendom, placering dominerer i næsten alle situationer, men præstationsgevinster ved at bruge primitive typer kan variere meget fra en slags applikation til en anden. For det andet, med fast ejendom, der er andre faktorer at overveje, selvom de normalt er mindre i forhold til placering. Med primitive typer er der kun en grund til at bruge dem — ydeevne; og så kun hvis applikationen er den slags, der kan drage fordel af deres brug.

primitiver tilbyder ringe værdi til de fleste forretningsrelaterede og internetapplikationer, der bruger en klient-server programmeringsmodel med en database på backend. Men udførelsen af applikationer, der domineres af numeriske beregninger, kan drage stor fordel af brugen af primitiver.

inkluderingen af primitiver i Java har været en af de mere kontroversielle beslutninger om sprogdesign, hvilket fremgår af antallet af artikler og forumindlæg relateret til denne beslutning. Simon Ritter bemærkede i sin keynote-adresse i London i November 2011, at der blev taget alvorligt hensyn til fjernelse af primitiver i en fremtidig version af Java (SE slide 41). I denne artikel vil jeg kort introducere primitiver og Java ‘ s dual-type system. Ved hjælp af kodeprøver og enkle benchmarks vil jeg gøre min sag for, hvorfor Java-primitiver er nødvendige for visse typer applikationer. Jeg vil også sammenligne Java ‘ s ydeevne med Scala, C++ og JavaScript.

primitiver versus objekter

som du sikkert allerede ved, hvis du læser denne artikel, har Java et dual-type system, normalt omtalt som primitive typer og objekttyper, ofte forkortet simpelthen som primitiver og objekter. Der er otte primitive typer foruddefineret i Java, og deres navne er reserverede nøgleord. Almindeligt anvendte eksempler inkluderer int, double og boolean. I det væsentlige er alle andre typer i Java, inklusive alle brugerdefinerede typer, objekttyper. (Jeg siger “i det væsentlige”, fordi array typer er lidt af en hybrid, men de er meget mere som objekttyper end primitive typer.) For hver primitiv type er der en tilsvarende indpakningsklasse, der er en objekttype; eksempler inkluderer Integer for int, Double for double og Boolean for boolean.

Primitive typer er værdibaserede, men objekttyper er referencebaserede, og deri ligger både kraften og kilden til kontrovers af primitive typer. For at illustrere forskellen skal du overveje de to erklæringer nedenfor. Den første erklæring bruger en primitiv type, og den anden bruger en indpakningsklasse.

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

ved hjælp af autoboksning, en funktion tilføjet til JDK 5, kunne jeg forkorte den anden erklæring til simpelthen

Integer n2 = 100;

men den underliggende semantik ændres ikke. Autoboksning forenkler brugen af indpakningsklasser og reducerer mængden af kode, som en programmør skal skrive, men det ændrer ikke noget ved kørsel.

forskellen mellem det primitive n1 og indpakningsobjektet n2 er illustreret ved diagrammet i Figur 1.

John I. Moore, Jr.

Figur 1. Hukommelse layout af primitiver versus objekter

variablen n1 har en heltalsværdi, men variablen n2 indeholder en reference til et objekt, og det er objektet, der indeholder heltalsværdien. Derudover indeholder objektet, der henvises til af n2, også en henvisning til klasseobjektet Double.

problemet med primitiver

før jeg prøver at overbevise dig om behovet for primitive typer, skal jeg erkende, at mange mennesker ikke er enige med mig. Sherman Alpert i” Primitive typer betragtes som skadelige “hævder, at primitiver er skadelige, fordi de blander” proceduremæssig semantik i en ellers ensartet objektorienteret model. Primitiver er ikke førsteklasses objekter, men de findes på et sprog, der primært involverer førsteklasses objekter.”Primitiver og objekter (i form af indpakningsklasser) giver to måder at håndtere logisk lignende typer på, men de har meget forskellige underliggende semantik. For eksempel, hvordan skal to tilfælde sammenlignes for ligestilling? For primitive typer bruger man == operatoren, men for objekter er det foretrukne valg at kalde equals() – metoden, hvilket ikke er en mulighed for primitiver. Tilsvarende findes der forskellige semantik ved tildeling af værdier eller passerende parametre. Selv standardværdierne er forskellige; f.eks. 0 for int versus null for Integer.

for mere baggrund om dette emne, se Eric Brunos blogindlæg, “en moderne primitiv diskussion”, som opsummerer nogle af fordele og ulemper ved primitiver. En række diskussioner om stakoverløb fokuserer også på primitiver, herunder “hvorfor bruger folk stadig primitive typer i Java?”og” er der en grund til altid at bruge objekter i stedet for primitiver?.”Programmører Stakudveksling er vært for en lignende diskussion med titlen” Hvornår skal man bruge primitiv vs klasse i Java?”.

hukommelsesudnyttelse

A double i Java optager altid 64 bit i hukommelsen, men størrelsen på en reference afhænger af Java virtual machine (JVM). Min computer kører 64-bit version af vinduer 7 og en 64-bit JVM, og derfor en reference på min computer optager 64 bit. Baseret på diagrammet i Figur 1 ville jeg forvente en enkelt double som n1 at besætte 8 bytes (64 bits), og jeg ville forvente en enkelt Double som n2 at besætte 24 bytes — 8 for henvisningen til objektet, 8 for double værdien gemt i objektet og 8 for henvisningen til klasseobjektet for Double. Plus, Java bruger ekstra hukommelse til at understøtte affaldssamling til objekttyper, men ikke til primitive typer. Lad os tjekke det ud.

brug af en tilgang svarende til Glen McCluskey i “Java primitive types vs. indpakning, ” metoden vist i Liste 1 måler antallet af bytes besat af en n-for-n-matrice (todimensionelt array) på double.

liste 1. Beregning af hukommelsesudnyttelse af typen dobbelt

ændring af koden i notering 1 med de åbenlyse typeændringer (ikke vist) kan vi også måle antallet af bytes besat af en n-for-n-matrice på Double. Når jeg tester disse to metoder på min computer ved hjælp af 1000-by-1000 matricer, får jeg resultaterne vist i tabel 1 nedenfor. Som illustreret svarer versionen til primitiv type double til lidt mere end 8 bytes pr. Versionen for objekttype Double krævede dog lidt mere end 28 bytes pr. Således er hukommelsesudnyttelsen af Double i dette tilfælde mere end tre gange hukommelsesudnyttelsen af double, hvilket ikke bør være en overraskelse for alle, der forstår hukommelseslayoutet illustreret i Figur 1 ovenfor.

Runtime performance

for at sammenligne runtime-forestillinger for primitiver og objekter har vi brug for en algoritme domineret af numeriske beregninger. Til denne artikel har jeg valgt matrice multiplikation, og jeg beregner den tid, der kræves for at multiplicere to 1000-by-1000 matricer. Jeg kodede matricemultiplikation for double på en ligetil måde som vist i Liste 2 nedenfor. Selvom der kan være hurtigere måder at implementere matrice multiplikation (måske ved hjælp af samtidighed), er dette punkt ikke rigtig relevant for denne artikel. Alt jeg behøver er fælles kode i to lignende metoder, en ved hjælp af primitive double og en ved hjælp af indpakningsklassen Double. Koden til multiplikation af to matricer af typen Double er nøjagtigt sådan i notering 2 med de åbenlyse typeændringer.

Liste 2. Multiplicere to matricer af typen double

jeg kørte de to metoder til at multiplicere to 1000-by-1000 matricer på min computer flere gange og målte resultaterne. De gennemsnitlige tider er vist i tabel 2. I dette tilfælde er runtime-ydelsen på doublesåledes mere end fire gange så hurtig som den for Double. Det er simpelthen for meget af en forskel at ignorere.

SciMark 2.0 benchmark

hidtil har jeg brugt det enkle, enkle benchmark for matrice multiplikation for at demonstrere, at primitiver kan give betydeligt større computerydelse end objekter. For at styrke mine påstande vil jeg bruge et mere videnskabeligt benchmark. SciMark 2.0 Er en Java benchmark for videnskabelig og numerisk computing tilgængelig fra National Institute of Standards and Technology (NIST). Jeg hentede kildekoden til dette benchmark og skabte to versioner, den oprindelige version ved hjælp af primitiver og en anden version ved hjælp af indpakningsklasser. For den anden version erstattede jeg int med Integer og double med Double for at få den fulde effekt af at bruge indpakningsklasser. Begge versioner er tilgængelige i kildekoden til denne artikel.

Hent

John I. Moore, Jr.

SciMark-benchmarket måler ydeevnen for flere beregningsrutiner og rapporterer en sammensat score i omtrentlige Mflops (millioner af flydende punktoperationer pr. Således er større tal bedre for dette benchmark. Tabel 3 giver de gennemsnitlige sammensatte scoringer fra flere kørsler af hver version af dette benchmark på min computer. Som vist var runtime-præstationerne for de to versioner af SciMark 2.0-benchmarket i overensstemmelse med matricen multiplikation resultater ovenfor, idet versionen med primitiver var næsten fem gange hurtigere end versionen ved hjælp af indpakningsklasser.

du har set et par variationer af Java-programmer, der laver numeriske beregninger, ved hjælp af både et hjemmelavet benchmark og en mere videnskabelig. Men hvordan sammenligner Java sig med andre sprog? Jeg vil afslutte med et hurtigt kig på, hvordan Java ‘ s ydeevne sammenligner med tre andre programmeringssprog: Scala, C++ og JavaScript.

Benchmarking Scala

Scala er et programmeringssprog, der kører på JVM og ser ud til at vinde popularitet. Scala har et samlet typesystem, hvilket betyder, at det ikke skelner mellem primitiver og objekter. Ifølge Erik Osheim i Scalas numeriske type klasse (Pt. 1), Scala bruger primitive typer, når det er muligt, men vil bruge objekter, hvis det er nødvendigt. Tilsvarende siger Martin Oderskys beskrivelse af Scalas Arrays, at”… en Scala array Array er repræsenteret som en Java int, en Array er repræsenteret som en Java double…”

så betyder det, at Scalas unified type system vil have runtime ydeevne, der kan sammenlignes med Java ‘ s primitive typer? Lad os se.

jeg er ikke så dygtig med Scala som jeg er med Java, men jeg forsøgte at konvertere koden til matricen multiplikation benchmark direkte fra Java til Scala. Resultatet er vist i Liste 3 nedenfor. Da jeg kørte Scala-versionen af benchmarket på min computer, var det i gennemsnit 12, 30 sekunder, hvilket sætter Scalas ydeevne meget tæt på Java med primitiver. Dette resultat er meget bedre end forventet og understøtter påstandene om, hvordan Scala håndterer numeriske typer.

Hent

John I. Moore, Jr.

Liste 3. Multiplicere to matricer i Scala

Benchmarking C++

da C++ kører direkte på “bare metal” snarere end i en virtuel maskine, ville man naturligvis forvente, at C++ kører hurtigere end Java. Desuden reduceres Java-ydeevnen lidt af det faktum, at Java kontrollerer adgang til arrays for at sikre, at hvert indeks er inden for de grænser, der er erklæret for arrayet, mens C++ ikke gør det (en C++ – funktion, der kan føre til bufferoverløb, som kan udnyttes af hackere). Jeg fandt C++ at være noget mere akavet end Java i forbindelse med grundlæggende todimensionale arrays, men heldigvis kan meget af denne akavet være skjult inde i de private dele af en klasse. For C++ oprettede jeg en simpel version af en Matrix klasse, og jeg overbelastede operatøren * for at multiplicere to matricer, men den grundlæggende matrice multiplikationsalgoritme blev konverteret direkte fra Java-versionen. C++ – kildekoden vises i Liste 4.

Hent

John I. Moore, Jr.

liste 4. Ved at multiplicere to matricer i C++

ved hjælp af Eclipse CDT (Eclipse for C++ Developers) med C++ compiler er det muligt at oprette både debug og release versioner af en applikation. For at teste C++ kørte jeg udgivelsesversionen flere gange og gennemsnit resultaterne. Som forventet løb C++ mærkbart hurtigere på denne enkle benchmark, i gennemsnit 7,58 sekunder på min computer. Hvis rå ydeevne er den primære faktor for valg af et programmeringssprog, er C++ det valgte sprog til numerisk intensive applikationer.

Benchmarking JavaScript

Okay, denne overraskede mig. I betragtning af at JavaScript er et meget dynamisk sprog, forventede jeg, at dets ydeevne var det værste af alt, endnu værre end Java med indpakningsklasser. Men faktisk var JavaScript ‘ s ydeevne meget tættere på Java med primitiver. For at teste JavaScript installerede jeg Node.js, en JavaScript-motor med et ry for at være meget effektiv. Resultaterne var i gennemsnit 15,91 sekunder. Notering 5 viser JavaScript-versionen af matricen multiplikation benchmark, som jeg kørte på Node.js

Hent

John I. Moore, Jr.

Liste 5. Multiplicere to matricer i JavaScript

afslutningsvis

da Java først ankom til scenen for omkring 18 år siden, var det ikke det bedste sprog fra et præstationsperspektiv for applikationer, der domineres af numeriske beregninger. Men over tid, med teknologiske fremskridt inden for områder som just-in-time (JIT) kompilering (aka adaptiv eller dynamisk kompilering), er Java ‘ s ydeevne for disse typer applikationer nu sammenlignelig med sprog, der er kompileret i native kode, når primitive typer bruges.

desuden eliminerer primitiver behovet for affaldsindsamling, hvilket giver en anden ydelsesfordel ved primitiver i forhold til objekttyper. Tabel 4 opsummerer runtime-ydeevnen for matricen multiplikation benchmark på denne computer. Andre faktorer som vedligeholdelse, bærbarhed og udviklerekspertise gør Java til et bedre valg for mange sådanne applikationer.

som tidligere diskuteret ser Oracle ud til at overveje fjernelse af primitiver i en fremtidig version af Java. Medmindre Java-kompilatoren kan generere kode med ydeevne, der kan sammenlignes med primitiver, tror jeg, at deres fjernelse fra Java ville udelukke brugen af Java til bestemte klasser af applikationer; nemlig de applikationer domineret af numeriske beregninger. I denne artikel har jeg brugt et simpelt benchmark baseret på matrice multiplikation og et mere videnskabeligt benchmark, SciMark 2.0, for at argumentere for dette punkt.

om forfatteren

John I. Moore, Jr. Professor i matematik og datalogi ved Citadel har en bred vifte af erfaring inden for både industri og akademi med specifik ekspertise inden for objektorienteret teknologi, programmelteknik og anvendt matematik. I mere end tre årtier har han designet og udviklet programmer ved hjælp af relationsdatabaser og flere højordenssprog, og han har arbejdet meget i Java siden version 1.1. Derudover har han udviklet og undervist adskillige akademiske kurser og industrielle seminarer om avancerede emner inden for datalogi.

yderligere læsning

  1. Paul Krill skrev om Oracles langtrækkende planer for Java i “Oracle lægger langtrækkende Java-intentioner” (Javaverden, marts 2012). Denne artikel, sammen med den tilhørende kommentarer tråd, motiverede mig til at skrive dette forsvar af primitiver.
  2. han skriver om sine resultater i benchmarking af primitive typer og indpakningsklasser i “Primitives and objects benchmark in Java” (Simononsoft, januar 2011).
  3. på supporthjemmesiden til programmering — principper og praksis ved hjælp af C++ (Addison, 2009) giver C++ skaberen Bjarne Stroustrup en implementering til en matriksklasse, der er meget mere komplet end den, der ledsager denne artikel.
  4. John Rose, Brian Goets og Guy Steele diskuterer et koncept kaldet værdityper i ” værdiernes tilstand “(OpenJDK.net, April 2014). Værdityper kan betragtes som uforanderlige brugerdefinerede aggregattyper uden identitet, ved at kombinere egenskaber for både objekter og primitiver. Mantraet for værdityper er ” koder som en klasse, fungerer som en int.”

Skriv et svar

Din e-mailadresse vil ikke blive publiceret.