Programovanie

Prípad zachovania primitívov v Jave

Primitive sú súčasťou programovacieho jazyka Java od jeho prvého vydania v roku 1996, a napriek tomu zostávajú jednou z najkontroverznejších funkcií jazyka. John Moore je silným argumentom pre udržanie primitívov v jazyku Java porovnaním jednoduchých štandardov Java, s primitívmi aj bez nich. Potom porovnáva výkon Javy s výkonmi Scaly, C ++ a JavaScriptu v konkrétnom type aplikácie, kde primitívy významne odlišujú.

Otázka: Aké sú tri najdôležitejšie faktory pri kúpe nehnuteľností?

Odpoveď: Poloha, umiestnenie, umiestnenie.

Toto staré a často používané porekadlo má naznačovať, že umiestnenie úplne dominuje všetkým ostatným faktorom, pokiaľ ide o nehnuteľnosti. V podobnom argumente sú tri najdôležitejšie faktory, ktoré je potrebné pri použití primitívnych typov v Jave zvážiť, výkon, výkon, výkon. Medzi argumentom pre nehnuteľnosť a argumentom pre primitívov sú dva rozdiely. Po prvé, v prípade nehnuteľností dominuje poloha takmer vo všetkých situáciách, ale zisky z používania primitívnych typov sa môžu medzi jednotlivými druhmi aplikácií veľmi líšiť. Po druhé, v prípade nehnuteľností je potrebné vziať do úvahy aj ďalšie faktory, aj keď sú v porovnaní s umiestnením zvyčajne nepatrné. Pri primitívnych typoch existuje jediný dôvod, prečo ich používať - výkon; a to iba vtedy, ak je aplikácia taká, ktorá môže ťažiť z ich použitia.

Primitívy ponúkajú malú hodnotu pre väčšinu obchodných a internetových aplikácií, ktoré používajú programovací model klient-server s databázou na backende. Ale výkon aplikácií, v ktorých dominujú numerické výpočty, môže mať z použitia primitívov veľký úžitok.

Zahrnutie primitívov do Javy bolo jedným z najkontroverznejších rozhodnutí o dizajne jazyka, o čom svedčí množstvo článkov a príspevkov na fórach týkajúcich sa tohto rozhodnutia. Simon Ritter vo svojom príhovore JAX v Londýne v novembri 2011 uviedol, že sa vážne uvažuje o odstránení primitívov v budúcej verzii Java (pozri snímku 41). V tomto článku stručne predstavím primitívne prvky a systém Java dvojakého typu. Pomocou ukážok kódu a jednoduchých referenčných hodnôt uvediem príklad, prečo sú pre určité typy aplikácií potrebné primitívy Java. Porovnám tiež výkon Javy s výkonmi Scaly, C ++ a JavaScriptu.

Meranie výkonu softvéru

Výkon softvéru sa zvyčajne meria z hľadiska času a priestoru. Časom môže byť skutočná doba chodu, napríklad 3,7 minúty, alebo poradie rastu na základe veľkosti vstupu, ako je napr O(n2). Podobné opatrenia existujú aj pri výkone priestoru, ktorý sa často vyjadruje v pojmoch využitia hlavnej pamäte, ale môže sa rozšíriť aj na využitie disku. Zlepšenie výkonu zvyčajne vyžaduje kompromis v časopriestore, pretože zmeny na zlepšenie času majú často nepriaznivý vplyv na priestor a naopak. Meranie poradia rastu závisí od algoritmu a prechod z obalových tried na primitívne výsledky nezmení. Pokiaľ však ide o skutočný časový a priestorový výkon, použitie primitívov namiesto súhrnných tried ponúka vylepšenia v čase aj priestore súčasne.

Primitíva verzus objekty

Ako už pravdepodobne viete, ak čítate tento článok, má Java systém dvojakého typu, ktorý sa zvyčajne označuje ako primitívne typy a typy objektov, často skrátene ako primitívne typy a objekty. V Jave je preddefinovaných osem primitívnych typov a ich názvy sú vyhradené kľúčové slová. Medzi bežne používané príklady patrí int, dvojitýa boolean. V zásade všetky ostatné typy v Jave, vrátane všetkých používateľom definovaných typov, sú typy objektov. (Hovorím „v podstate“, pretože typy polí sú trochu hybridné, ale oveľa viac sa podobajú objektovým typom ako primitívne typy.) Pre každý primitívny typ existuje zodpovedajúca trieda obálky, ktorá je typom objektu; príklady zahŕňajú Celé číslo pre int, Dvojitý pre dvojitýa Boolovský pre boolean.

Primitívne typy sú založené na hodnotách, ale typy objektov sú založené na referenciách a v tom spočíva sila aj zdroj kontroverzie primitívnych typov. Na ilustráciu rozdielu zvážte dve nižšie uvedené vyhlásenia. Prvá deklarácia používa primitívny typ a druhá triedu wrapper.

 int n1 = 100; Celé číslo n2 = nové celé číslo (100); 

Pomocou autoboxingu, funkcie pridanej do JDK 5, som mohol skrátiť druhú deklaráciu na jednoducho

 Celé číslo n2 = 100; 

ale základná sémantika sa nemení. Autoboxing zjednodušuje použitie tried obálky a znižuje množstvo kódu, ktorý musí programátor napísať, ale za behu to nič nezmení.

Rozdiel medzi primitívom n1 a obalový objekt n2 je znázornený na diagrame na obrázku 1.

John I. Moore, ml.

Premenná n1 obsahuje celočíselnú hodnotu, ale premennú n2 obsahuje odkaz na objekt a je to objekt, ktorý obsahuje celočíselnú hodnotu. Okrem toho objekt, na ktorý odkazuje n2 tiež obsahuje odkaz na objekt triedy Dvojitý.

Problém s primitívmi

Skôr ako sa vás pokúsim presvedčiť o potrebe primitívnych typov, mal by som uznať, že veľa ľudí so mnou nebude súhlasiť. Sherman Alpert v dokumente „Primitívne typy považované za škodlivé“ tvrdí, že primitívne látky sú škodlivé, pretože miešajú „procedurálnu sémantiku do inak jednotného objektovo orientovaného modelu. Primitívne objekty nie sú prvotriednymi objektmi, napriek tomu existujú v jazyku, ktorý zahŕňa predovšetkým objekty triedy. “ Primitívne objekty a objekty (vo forme tried obálky) poskytujú dva spôsoby zaobchádzania s logicky podobnými typmi, majú však veľmi odlišnú základnú sémantiku. Ako by sa napríklad dalo porovnávať dva prípady týkajúce sa rovnosti? Pre primitívne typy sa používa znak == operátor, ale preferovanou voľbou pre objekty je zavolať na rovná sa () metóda, ktorá pre primitívov nie je možnosťou. Podobne existujú rôzne sémantiky pri prideľovaní hodnôt alebo odovzdávaní parametrov. Aj predvolené hodnoty sú odlišné; napr. 0 pre int proti nulový pre Celé číslo.

Viac informácií o tejto problematike nájdete v blogovom príspevku Erica Bruna „Moderná primitívna diskusia“, v ktorom sú zhrnuté niektoré klady a zápory primitívov. Mnoho diskusií o Stack Overflow sa zameriava aj na primitívne nástroje, napríklad „Prečo ľudia stále používajú primitívne typy v Jave?“ a „Existuje dôvod vždy používať objekty namiesto primitívnych nástrojov?“ Programmers Stack Exchange hostí podobnú diskusiu s názvom „Kedy použiť primitívne vs class v Jave?“.

Využitie pamäte

A dvojitý v jazyku Java vždy zaberá 64 bitov v pamäti, ale veľkosť referencie závisí od virtuálneho stroja Java (JVM). Môj počítač používa 64-bitovú verziu systému Windows 7 a 64-bitový JVM, a preto referencia v mojom počítači zaberá 64 bitov. Na základe diagramu na obrázku 1 by som očakával jeden dvojitý ako napr n1 obsadiť 8 bajtov (64 bitov) a čakal by som jeden Dvojitý ako napr n2 zaberať 24 bajtov - 8 pre referenciu na objekt, 8 pre dvojitý hodnota uložená v objekte a 8 pre odkaz na objekt triedy pre Dvojitý. Java navyše používa dodatočnú pamäť na podporu zhromažďovania odpadkov pre typy objektov, ale nie pre primitívne typy. Poďme to skontrolovať.

Použitím prístupu podobného prístupu Glen McCluskey v časti „Java primitívne typy vs. obaly“ metóda uvedená v zozname 1 meria počet bajtov obsadených maticou n-by-n (dvojrozmerné pole) dvojitý.

Výpis 1. Výpočet využitia pamäte typu double

 public static long getBytesUsingPrimitives (int n) {System.gc (); // vynútiť zber odpadu dlhé memStart = Runtime.getRuntime (). freeMemory (); double [] [] a = nový double [n] [n]; // vložte nejaké náhodné hodnoty do matice pre (int i = 0; i <n; ++ i) {for (int j = 0; j <n; ++ j) a [i] [j] = matematika. random (); } dlhý memEnd = Runtime.getRuntime (). freeMemory (); návrat memStart - memEnd; } 

Pri úprave kódu v zozname 1 so zjavnými zmenami typu (nezobrazené) môžeme tiež merať počet bajtov obsadených maticou n-by-n Dvojitý. Keď tieto dve metódy otestujem na počítači pomocou matíc 1 000 x 1 000, dostanem výsledky uvedené v tabuľke 1 nižšie. Ako je znázornené, verzia pre primitívny typ dvojitý sa rovná niečo viac ako 8 bajtov na vstup do matice, čo je zhruba to, čo som očakával. Verzia pre typ objektu Dvojitý vyžadoval o niečo viac ako 28 bajtov na záznam v matici. V tomto prípade teda využitie pamäte Dvojitý je viac ako trojnásobné využitie pamäte dvojitý, čo by nemalo byť prekvapením pre každého, kto rozumie rozloženiu pamäte znázornenému na obrázku 1 vyššie.

Tabuľka 1. Využitie pamäte typu double verzus Double

VerziaCelkový počet bajtovBajty na vstup
Použitím dvojitý8,380,7688.381
Použitím Dvojitý28,166,07228.166

Runtime výkon

Na porovnanie runtime výkonov pre primitívne a objekty potrebujeme algoritmus, ktorému dominujú numerické výpočty. Pre tento článok som zvolil násobenie matíc a vypočítam čas potrebný na vynásobenie dvoch matíc 1 000 x 1 000. Kódoval som maticové násobenie pre dvojitý spôsobom uvedeným v zozname 2 nižšie. Aj keď môžu existovať rýchlejšie spôsoby implementácie násobenia matíc (možno pomocou súbežnosti), tento bod nie je pre tento článok skutočne relevantný. Potrebujem iba spoločný kód v dvoch podobných metódach, jeden s použitím primitívu dvojitý a jeden s použitím triedy wrapper Dvojitý. Kód na vynásobenie dvoch matíc typu Dvojitý je presne taký ako v zozname 2 so zjavnými zmenami typu.

Výpis 2. Násobenie dvoch matíc typu double

 public static double [] [] multiplikovať (double [] [] a, double [] [] b) {if (! checkArgs (a, b)) hodiť novú IllegalArgumentException ("Matice nie sú kompatibilné pre násobenie"); int nRows = a.length; int nCols = b [0] .dlžka; double [] [] result = new double [nRows] [nCols]; for (int rowNum = 0; rowNum <nRows; ++ rowNum) {for (int colNum = 0; colNum <nCols; ++ colNum) {dvojnásobok = 0,0; pre (int i = 0; i <a [0] .dlžka; ++ i) suma + = a [riadkové číslo] [i] * b [i] [colNum]; výsledok [rowNum] [colNum] = suma; }} vrátiť výsledok; } 

Spustil som tieto dve metódy na niekoľkonásobné rozmnoženie dvoch matíc 1 000 x 1 000 v počítači a výsledky som zmeral. Priemerné časy sú uvedené v tabuľke 2. Teda v tomto prípade výkon za behu programu dvojitý je viac ako štyrikrát rýchlejšia ako v prípade Dvojitý. To je jednoducho príliš veľký rozdiel na to, aby ste ho ignorovali.

Tabuľka 2. Runtime výkon dvojitého verzus dvojitého

VerziaSekúnd
Použitím dvojitý11.31
Použitím Dvojitý48.48

Benchmark SciMark 2.0

Doteraz som použil jediný jednoduchý test násobenia matíc, aby som preukázal, že primitívy môžu priniesť výrazne vyšší výpočtový výkon ako objekty. Na posilnenie svojich tvrdení použijem vedeckejšiu hodnotu. SciMark 2.0 je referenčný štandard Java pre vedecké a numerické výpočty dostupný od Národného ústavu pre štandardy a technológie (NIST). Stiahol som si zdrojový kód pre tento test a vytvoril som dve verzie, pôvodnú verziu pomocou primitívov a druhú verziu pomocou tried wrapperov. Za druhú verziu som vymenil int s Celé číslo a dvojitý s Dvojitý aby ste naplno využili použitie tried obálky. Obe verzie sú k dispozícii v zdrojovom kóde tohto článku.

stiahnite si Benchmarking Java: stiahnite si zdrojový kód John I. Moore, Jr.

Benchmark SciMark meria výkon niekoľkých výpočtových rutín a hlási zložené skóre v približných Mflops (milióny operácií s pohyblivou rádovou čiarkou za sekundu). Pre túto referenčnú hodnotu sú teda lepšie väčšie čísla. Tabuľka 3 uvádza priemerné zložené skóre z niekoľkých sérií každej verzie tohto štandardu v mojom počítači. Ako je znázornené, runtime výkony dvoch verzií benchmarku SciMark 2.0 boli v súlade s vyššie uvedenými výsledkami násobenia matice, pretože verzia s primitívmi bola takmer päťkrát rýchlejšia ako verzia používajúca obalové triedy.

Tabuľka 3. Runtime výkon testovacej hodnoty SciMark

Verzia SciMarkVýkon (Mflops)
Pomocou primitívov710.80
Používanie tried obálky143.73

Videli ste už niekoľko variácií programov Java, ktoré uskutočňovali číselné výpočty, pričom používali domácu aj vedeckejšiu hodnotu. Ako je však Java v porovnaní s inými jazykmi? Na záver sa rýchlo pozriem na to, ako je výkon Java porovnateľný s výkonmi troch ďalších programovacích jazykov: Scala, C ++ a JavaScript.

Benchmarking Scala

Scala je programovací jazyk, ktorý beží na JVM a zdá sa, že si získava na popularite. Scala má systém jednotného typu, čo znamená, že nerozlišuje medzi primitívmi a objektmi. Podľa Erika Osheima v Scalovej triede číselných typov (Pt. 1) Scala používa primitívne typy, pokiaľ je to možné, ale v prípade potreby použije objekty. Podobne opis Scala's Arrays od Martina Oderského hovorí, že „... pole Scala Pole [Int] je reprezentovaný ako Java int [], an Pole [Double] je reprezentovaný ako Java dvojitý [] ..."

Znamená to teda, že zjednotený typ systému Scala bude mať behovú výkonnosť porovnateľnú s primitívnymi typmi Javy? Pozrime sa.

$config[zx-auto] not found$config[zx-overlay] not found