Programování

Případ pro udržování primitiv v Javě

Primitiva jsou součástí programovacího jazyka Java od jeho počátečního vydání v roce 1996, a přesto zůstávají jednou z kontroverznějších funkcí jazyka. John Moore je silným argumentem pro zachování primitiv v jazyce Java porovnáním jednoduchých standardů Java, ať už s primitivy nebo bez nich. Poté porovná výkon Javy s výkonem Scaly, C ++ a JavaScriptu v konkrétním typu aplikace, kde primitiva významně odlišují.

Otázka: Jaké jsou tři nejdůležitější faktory při nákupu nemovitostí?

Odpovědět: Umístění, umístění, umístění.

Toto staré a často používané rčení má naznačovat, že umístění zcela dominuje všem ostatním faktorům, pokud jde o nemovitosti. V podobném argumentu jsou tři nejdůležitější faktory, které je třeba při použití primitivních typů v Javě zvážit, výkon, výkon, výkon. Mezi argumentem pro nemovitost a argumentem pro primitivy existují dva rozdíly. Za prvé, u nemovitostí dominuje umístění téměř ve všech situacích, ale zisky z používání primitivních typů se mohou u jednotlivých druhů aplikací velmi lišit. Zadruhé, v případě nemovitostí je třeba vzít v úvahu i další faktory, i když jsou ve srovnání s lokalitou obvykle menší. U primitivních typů existuje jediný důvod, proč je používat - výkon; a pak pouze v případě, že aplikace je druh, který může těžit z jejich použití.

Primitivy nabízejí malou hodnotu většině obchodních a internetových aplikací, které používají programovací model klient-server s databází na back-endu. Ale výkon aplikací, kterým dominují numerické výpočty, může z použití primitiv značně těžit.

Zahrnutí primitiv do Javy bylo jedním z nejkontroverznějších rozhodnutí o designu jazyka, o čemž svědčí množství článků a příspěvků na fóru souvisejících s tímto rozhodnutím. Simon Ritter ve svém hlavním projevu JAX London v listopadu 2011 uvedl, že se vážně uvažuje o odstranění primitivů v budoucí verzi Java (viz snímek 41). V tomto článku stručně představím primitiva a systém Java duálního typu. Pomocí ukázek kódu a jednoduchých měřítek uvedu svůj důvod, proč jsou pro určité typy aplikací potřebné Java primitiva. Porovnám také výkon Javy s výkony Scaly, C ++ a JavaScriptu.

Měření výkonu softwaru

Výkon softwaru se obvykle měří z hlediska času a prostoru. Časem může být skutečná doba chodu, například 3,7 minuty, nebo pořadí růstu na základě velikosti vstupu, například Ó(n2). Podobná opatření existují pro výkon prostoru, který se často vyjadřuje z hlediska využití hlavní paměti, ale může se rozšířit i na využití disku. Zlepšení výkonu obvykle zahrnuje časoprostorový kompromis v tom, že změny ke zlepšení času mají často nepříznivý vliv na prostor a naopak. Měření pořadí růstu závisí na algoritmu a přechod z tříd obalů na primitiva nezmění výsledek. Ale pokud jde o skutečný čas a prostor, použití primitiv namísto tříd obálek nabízí vylepšení v čase i prostoru současně.

Primitivy versus objekty

Jak již pravděpodobně víte, pokud čtete tento článek, Java má systém dvojího typu, obvykle označovaný jako primitivní typy a typy objektů, často zkráceně jednoduše jako primitivní a objekty. V Javě je předdefinováno osm primitivních typů a jejich názvy jsou vyhrazená klíčová slova. Mezi běžně používané příklady patří int, dvojnásobek, a booleovský. V podstatě všechny ostatní typy v Javě, včetně všech uživatelem definovaných typů, jsou typy objektů. (Říkám „v podstatě“, protože typy polí jsou trochu hybridní, ale mnohem více se podobají objektovým typům než primitivním typům.) Pro každý primitivní typ existuje odpovídající obalová třída, která je typem objektu; příklady zahrnují Celé číslo pro int, Dvojnásobek pro dvojnásobek, a Booleovský pro booleovský.

Primitivní typy jsou založeny na hodnotách, ale typy objektů jsou založeny na referencích a v tom spočívá jak síla, tak zdroj kontroverze primitivních typů. Pro ilustraci rozdílu zvažte dvě níže uvedená prohlášení. První deklarace používá primitivní typ a druhá používá obálkovou třídu.

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

Pomocí autoboxingu, funkce přidané do JDK 5, jsem mohl zkrátit druhou deklaraci jednoduše

 Celé číslo n2 = 100; 

ale základní sémantika se nemění. Autoboxing zjednodušuje použití tříd obálky a snižuje množství kódu, který programátor musí zapsat, ale za běhu to nic nezmění.

Rozdíl mezi primitivem n1 a obalový objekt n2 je znázorněno na schématu na obrázku 1.

John I. Moore, Jr.

Proměnná n1 obsahuje celočíselnou hodnotu, ale proměnnou n2 obsahuje odkaz na objekt a je to objekt, který obsahuje celočíselnou hodnotu. Kromě toho objekt, na který odkazuje n2 také obsahuje odkaz na objekt třídy Dvojnásobek.

Problém s primitivy

Než se vás pokusím přesvědčit o potřebě primitivních typů, měl bych uznat, že mnoho lidí se mnou nebude souhlasit. Sherman Alpert v dokumentu „Primitivní typy považované za škodlivé“ tvrdí, že primitiva jsou škodlivá, protože kombinují „procedurální sémantiku do jinak jednotného objektově orientovaného modelu. Primitivy nejsou prvotřídními objekty, přesto existují v jazyce, který zahrnuje především objekty třídy. “ Primitiva a objekty (ve formě obálkových tříd) poskytují dva způsoby zpracování logicky podobných typů, ale mají velmi odlišnou základní sémantiku. Například, jak by se měly porovnávat dvě instance pro rovnost? U primitivních typů se používá == operátor, ale u objektů je preferovanou volbou volání se rovná() metoda, která pro primitivy není volbou. Podobně existují různé sémantiky při přiřazování hodnot nebo předávání parametrů. I výchozí hodnoty se liší; např., 0 pro int proti nula pro Celé číslo.

Další informace o tomto problému najdete v příspěvku na blogu Erica Bruna „Moderní primitivní diskuse“, který shrnuje některé klady a zápory primitiv. Řada diskusí o Stack Overflow se také zaměřuje na primitiva, včetně „Proč lidé stále používají primitivní typy v Javě?“ a „Existuje důvod vždy používat Objekty místo primitiv?“ Programmers Stack Exchange hostí podobnou diskusi s názvem „Kdy použít primitivní vs třídu v Javě?“.

Využití paměti

A dvojnásobek v Javě vždy zabírá 64 bitů v paměti, ale velikost reference závisí na virtuálním stroji Java (JVM). Můj počítač používá 64bitovou verzi systému Windows 7 a 64bitový JVM, a proto reference v mém počítači zabírá 64 bitů. Na základě diagramu na obrázku 1 bych očekával jeden dvojnásobek jako n1 obsadit 8 bajtů (64 bitů) a očekával bych jediný Dvojnásobek jako n2 obsadit 24 bajtů - 8 pro odkaz na objekt, 8 pro dvojnásobek hodnota uložená v objektu a 8 pro odkaz na objekt třídy pro Dvojnásobek. Navíc Java používá extra paměť k podpoře uvolňování paměti pro typy objektů, ale ne pro primitivní typy. Pojďme to zkontrolovat.

Použitím přístupu podobného přístupu Glen McCluskey v „Java primitive types vs. wrappers“ metoda uvedená v seznamu 1 měří počet bytů obsazených maticí n-by-n (dvourozměrné pole) dvojnásobek.

Výpis 1. Výpočet využití paměti typu double

 public static long getBytesUsingPrimitives (int n) {System.gc (); // vynutit uvolňování paměti dlouhý memStart = Runtime.getRuntime (). freeMemory (); double [] [] a = nový double [n] [n]; // vložte do matice nějaké náhodné hodnoty pro (int i = 0; i <n; ++ i) {for (int j = 0; j <n; ++ j) a [i] [j] = matematika. náhodný(); } dlouhý memEnd = Runtime.getRuntime (). freeMemory (); vrátit memStart - memEnd; } 

Úpravou kódu v seznamu 1 se zřejmými změnami typu (nezobrazeno) můžeme také měřit počet bytů obsazených maticí n-by-n Dvojnásobek. Když testuji tyto dvě metody na svém počítači pomocí matic 1000 x 1000, dostanu výsledky uvedené v tabulce 1 níže. Jak je znázorněno, verze pro primitivní typ dvojnásobek odpovídá něco málo přes 8 bajtů na záznam v matici, zhruba to, co jsem očekával. Verze pro typ objektu Dvojnásobek vyžadováno o něco více než 28 bytů na záznam v matici. V tomto případě tedy využití paměti Dvojnásobek je více než trojnásobek využití paměti dvojnásobek, což by nemělo být překvapením pro každého, kdo rozumí rozložení paměti znázorněnému na obrázku 1 výše.

Tabulka 1. Využití paměti typu double versus Double

VerzeCelkem bajtůBajty na záznam
Použitím dvojnásobek8,380,7688.381
Použitím Dvojnásobek28,166,07228.166

Výkon za běhu

K porovnání běhových výkonů pro primitiva a objekty potřebujeme algoritmus, kterému dominují numerické výpočty. Pro tento článek jsem zvolil násobení matic a vypočítám čas potřebný k vynásobení dvou matic 1000 x 1000. Kódoval jsem maticové násobení pro dvojnásobek přímým způsobem, jak je uvedeno v seznamu 2 níže. I když mohou existovat rychlejší způsoby, jak implementovat násobení matic (možná pomocí souběžnosti), tento bod není pro tento článek opravdu relevantní. Vše, co potřebuji, je společný kód ve dvou podobných metodách, jeden s použitím primitivu dvojnásobek a jeden pomocí třídy wrapper Dvojnásobek. Kód pro násobení dvou matic typu Dvojnásobek je přesně takový jako v seznamu 2 se zřejmými změnami typu.

Výpis 2. Násobení dvou matic typu double

 public static double [] [] multiply (double [] [] a, double [] [] b) {if (! checkArgs (a, b)) hodit novou IllegalArgumentException ("Matice nejsou kompatibilní pro násobení"); int nRows = a.length; int nCols = b [0] .length; double [] [] result = new double [nRows] [nCols]; for (int rowNum = 0; rowNum <nRows; ++ rowNum) {for (int colNum = 0; colNum <nCols; ++ colNum) {dvojnásobný součet = 0,0; pro (int i = 0; i <a [0]. délka; ++ i) součet + = a [číslo řádku] [i] * b [i] [sloupec]; výsledek [rowNum] [colNum] = součet; }} vrátit výsledek; } 

Spustil jsem tyto dvě metody, abych několikrát rozmnožil dvě matice 1000 x 1000 v počítači a změřil výsledky. Průměrné časy jsou uvedeny v tabulce 2. V tomto případě tedy runtime výkon dvojnásobek je více než čtyřikrát rychlejší než v Dvojnásobek. To je prostě příliš velký rozdíl na to, abychom jej ignorovali.

Tabulka 2. Runtime výkon dvojí versus dvojí

VerzeSekundy
Použitím dvojnásobek11.31
Použitím Dvojnásobek48.48

SciMark 2.0 měřítko

Dosud jsem použil jednoduchý jednoduchý test násobení matic, abych prokázal, že primitiva mohou přinést výrazně vyšší výpočetní výkon než objekty. Abych posílil svá tvrzení, použiji více vědecké měřítko. SciMark 2.0 je měřítko Java pro vědecké a numerické výpočty dostupné od National Institute of Standards and Technology (NIST). Stáhl jsem si zdrojový kód pro toto měřítko a vytvořil dvě verze, původní verzi pomocí primitiv a druhou verzi pomocí tříd wrapperů. Pro druhou verzi jsem nahradil int s Celé číslo a dvojnásobek s Dvojnásobek získat plný efekt používání tříd obálky. Obě verze jsou k dispozici ve zdrojovém kódu tohoto článku.

stáhnout Benchmarking Java: Stáhněte si zdrojový kód John I. Moore, Jr.

Benchmark SciMark měří výkon několika výpočetních rutin a hlásí složené skóre v přibližných Mflops (miliony operací s pohyblivou řádovou čárkou za sekundu). Větší čísla jsou tedy pro tuto referenční hodnotu lepší. Tabulka 3 uvádí průměrné složené skóre z několika běhů každé verze tohoto benchmarku na mém počítači. Jak je znázorněno, běhové výkony dvou verzí benchmarku SciMark 2.0 byly konzistentní s výsledky multiplikace matice výše v tom, že verze s primitivy byla téměř pětkrát rychlejší než verze používající obálkové třídy.

Tabulka 3. Výkon za běhu benchmarku SciMark

Verze SciMarkVýkon (Mflops)
Používání primitiv710.80
Používání tříd obalů143.73

Viděli jste několik variant programů Java, které prováděly numerické výpočty, přičemž používaly jak domácí měřítko, tak vědeckější. Jak je ale Java v porovnání s jinými jazyky? Na závěr se krátce podívám na to, jak je výkon Javy ve srovnání s výkonem tří dalších programovacích jazyků: Scala, C ++ a JavaScript.

Benchmarking Scala

Scala je programovací jazyk, který běží na JVM a zdá se, že si získává na popularitě. Scala má systém jednotného typu, což znamená, že nerozlišuje mezi primitivy a objekty. Podle Erika Osheima ve Scalově třídě číselných typů (Pt. 1) Scala používá primitivní typy, pokud je to možné, ale v případě potřeby použije objekty. Podobně Martin Odersky popisuje Scala's Arrays, že „... pole Scala Pole [Int] je reprezentován jako Java int [], an Pole [Double] je reprezentován jako Java dvojnásobek[] ..."

Znamená to tedy, že systém sjednoceného typu Scaly bude mít běhový výkon srovnatelný s primitivními typy Java? Uvidíme.

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