a primitívek az 1996-os első kiadás óta a Java programozási nyelv részét képezik, mégis továbbra is az egyik legvitatottabb nyelvi jellemző. John Moore határozottan állítja, hogy a primitíveket a Java nyelven tartja az egyszerű Java referenciaértékek összehasonlításával, mind primitívekkel, mind anélkül. Ezután összehasonlítja a Java teljesítményét a Scala, A C++ és a JavaScript teljesítményével egy adott típusú alkalmazásban, ahol a primitívek figyelemre méltó különbséget tesznek.
kérdés: Mi a három legfontosabb tényező az ingatlanvásárlásban?
válasz: Hely, Hely, hely.
ez a régi és gyakran használt mondás azt jelenti, hogy a helyszín teljesen uralja az összes többi tényezőt, amikor az ingatlanról van szó. Hasonló érvben a primitív típusok Java-ban történő használatának három legfontosabb tényezője a teljesítmény, a teljesítmény, a teljesítmény. Két különbség van az ingatlan érvelése és a primitívek érvelése között. Először is, az ingatlanoknál a hely dominál szinte minden helyzetben, de a primitív típusok használatából származó teljesítménynövekedés nagyban változhat az egyik alkalmazástól a másikig. Másodszor, az ingatlanokkal más tényezőket is figyelembe kell venni, annak ellenére, hogy általában kisebbek a helyhez képest. A primitív típusoknál csak egy ok van a használatukra — Teljesítmény; majd csak akkor, ha az alkalmazás az a fajta, amely profitálhat a használatukból.
a primitívek kevés értéket kínálnak a legtöbb üzleti és internetes alkalmazásnak, amelyek kliens-szerver programozási modellt használnak, adatbázissal a háttérben. De a numerikus számítások által dominált alkalmazások teljesítménye nagyban profitálhat a primitívek használatából.
a primitívek beépítése a Java-ba az egyik legvitatottabb nyelvtervezési döntés volt, amit az ehhez a döntéshez kapcsolódó cikkek és fórumbejegyzések száma is bizonyít. Simon Ritter megjegyezte az övében JAX London 2011. novemberi vitaindító beszédében komolyan fontolóra vették a primitívek eltávolítását a Java jövőbeli verziójában (lásd 41.dia). Ebben a cikkben röviden bemutatom a primitives és a Java kettős típusú rendszerét. Kódmintákat és egyszerű referenciaértékeket használva meg fogom érteni, hogy miért van szükség Java primitívekre bizonyos típusú alkalmazásokhoz. Összehasonlítom a Java teljesítményét a Scala, A C++ és a JavaScript teljesítményével is.
- Primitives versus objects
- a probléma a primitívekkel
- memóriahasználat
- lista 1. A double
- futásidejű teljesítmény
- lista 2. Két Double
- a SciMark 2.0 benchmark
- Benchmarking Scala
- lista 3. Két mátrix szorzása Scala
- Benchmarking C++
- lista 4. Két mátrix szorzása C++ – ban
- Benchmarking JavaScript
- lista 5. Két mátrix szorzása JavaScript-ben
- összefoglalva
- A szerzőről
- további olvasmányok
Primitives versus objects
mint valószínűleg már tudja, ha ezt a cikket olvassa, a Java kettős típusú rendszerrel rendelkezik, amelyet általában primitív típusoknak és objektumtípusoknak neveznek, gyakran egyszerűen primitíveknek és objektumoknak rövidítve. Nyolc primitív típus van előre definiálva a Java – ban, nevük pedig fenntartott kulcsszavak. A leggyakrabban használt példák a int
, double
és boolean
. Lényegében a Java összes többi típusa, beleértve az összes felhasználó által definiált típust is, objektumtípus. (Azt mondom, hogy “lényegében”, mert a tömbtípusok kissé hibridek, de sokkal inkább hasonlítanak az objektumtípusokra, mint a primitív típusokra.) Minden primitív típushoz tartozik egy megfelelő wrapper osztály, amely objektumtípus; példák: Integer
int
, Double
double
és Boolean
boolean
.
a kezdetleges típusok értékalapúak, de a tárgytípusok referenciaalapúak, és ebben rejlik a kezdetleges típusok vitájának ereje és forrása is. A különbség szemléltetésére vegye figyelembe az alábbi két nyilatkozatot. Az első deklaráció primitív típust, a második pedig wrapper osztályt használ.
int n1 = 100;Integer n2 = new Integer(100);
az autoboxing használatával, amely a JDK 5-hez hozzáadott funkció, lerövidíthetem a második deklarációt egyszerűen
Integer n2 = 100;
– ra, de az alapul szolgáló szemantika nem változik. Az AutoBOX leegyszerűsíti a wrapper osztályok használatát, és csökkenti a programozónak írandó kód mennyiségét, de futásidőben semmit sem változtat.
a primitív n1
és a burkoló objektum n2
közötti különbséget az 1.ábrán látható ábra szemlélteti.
a n1
változó egész értéket tartalmaz, de a n2
változó hivatkozást tartalmaz egy objektumra, és az objektum tartja az egész értéket. Ezenkívül a n2
által hivatkozott objektum hivatkozást tartalmaz a Double
osztályobjektumra is.
a probléma a primitívekkel
mielőtt megpróbálnám meggyőzni Önt a primitív típusok szükségességéről, el kell ismernem, hogy sokan nem értenek egyet velem. Sherman Alpert a ” károsnak tekintett primitív típusokban “azt állítja, hogy a primitívek károsak, mert” eljárási szemantikát kevernek egy egyébként egységes objektum-orientált modellbe. A primitívek nem első osztályú tárgyak, mégis olyan nyelven léteznek, amely elsősorban első osztályú tárgyakat foglal magában.”A primitívek és az objektumok (burkolóosztályok formájában) két módot kínálnak a logikailag hasonló típusok kezelésére, de nagyon eltérő mögöttes szemantikával rendelkeznek. Például, hogyan kell összehasonlítani két esetet az egyenlőség szempontjából? A primitív típusoknál az ==
operátort használjuk, de az objektumok esetében az előnyben részesített választás a equals()
metódus meghívása, ami nem lehetséges a primitívek számára. Hasonlóképpen, különböző szemantika létezik értékek hozzárendelésekor vagy paraméterek átadásakor. Még az alapértelmezett értékek is különböznek; pl. 0
int
versus null
Integer
esetén.
további háttér ebben a kérdésben, lásd Eric Bruno blogbejegyzése, “a modern primitív vita”, amely összefoglalja néhány előnye és hátránya a primitívek. A verem túlcsordulásáról szóló számos vita a primitívekre is összpontosít, beleértve: “miért használják az emberek még mindig primitív típusokat a Java-ban?”és” van-e ok arra, hogy mindig tárgyakat használjunk primitívek helyett?.”A programozók Stack Exchange ad otthont egy hasonló vita címe:” mikor kell használni primitív vs osztály Java?”.
memóriahasználat
a double
a Java-ban mindig 64 bitet foglal el a memóriában, de a referencia mérete a Java virtuális géptől (JVM) függ. A számítógép a Windows 64 bites verzióját és a 64 bites JVM-et futtatja, ezért a számítógépen található hivatkozás 64 bitet foglal el. Az 1. ábrán látható ábra alapján arra számítok, hogy egyetlen double
, például n1
8 bájtot (64 bitet) foglal el, és egyetlen Double
, például n2
24 bájtot foglal el — 8 az objektumra való hivatkozáshoz, 8 az objektumban tárolt double
értékhez, 8 pedig az osztályobjektumra való hivatkozáshoz Double
. Ráadásul a Java extra memóriát használ az Objektumtípusok szemétgyűjtésének támogatására, de a primitív típusokra nem. Nézzük meg.
Glen McCluskey-hoz hasonló megközelítés alkalmazása a ” Java primitív típusok vs. csomagolók, ” az 1.felsorolásban bemutatott módszer az An által elfoglalt bájtok számát méri n-by-n mátrix (kétdimenziós tömb) nak,-nek double
.
lista 1. A double
típusú memóriahasználat kiszámítása az 1. listában szereplő kód módosítása a nyilvánvaló típusváltozásokkal (nem látható), megmérhetjük az n-by-n mátrix által elfoglalt bájtok számát is Double
. Amikor ezt a két módszert tesztelem a számítógépemen 1000×1000 mátrixok segítségével, az alábbi 1.táblázatban látható eredményeket kapom. Mint illusztráltuk, a double
primitív típus verziója valamivel több mint 8 bájtnak felel meg a mátrix bejegyzésenként, nagyjából amire számítottam. A Double
objektumtípus verziója azonban a mátrix bejegyzésenként valamivel több mint 28 bájtot igényelt. Így ebben az esetben a Double
memóriahasználata több mint háromszorosa a double
memóriahasznosításának, ami nem lehet meglepetés annak, aki megérti a fenti 1.ábrán bemutatott memóriaelrendezést.
futásidejű teljesítmény
a primitívek és objektumok futásidejű teljesítményének összehasonlításához numerikus számítások által dominált algoritmusra van szükségünk. Ehhez a cikkhez a mátrixszorzást választottam, és kiszámolom a két 1000×1000 mátrix szorzásához szükséges időt. Kódoltam mátrix szorzás double
egyszerű módon, amint az az alábbi 2. felsorolásban látható. Bár lehetnek gyorsabb módszerek a mátrixszorzás megvalósítására (esetleg párhuzamosság használatával), ez a pont nem igazán releváns a cikk szempontjából. Csak egy közös kódra van szükségem két hasonló módszerben, az egyik a primitív double
, a másik pedig a wrapper osztály Double
. A Double
típusú két mátrix szorzásának kódja pontosan olyan, mint a 2.felsorolásban, a nyilvánvaló típusváltozásokkal.
lista 2. Két Double
típusú mátrix szorzása a két módszert többször futtattam két 1000×1000 mátrix szorzására a számítógépemen, és megmértem az eredményeket. Az átlagos időket a 2.táblázat mutatja. Így ebben az esetben a double
futásidejű teljesítménye több mint négyszer olyan gyors, mint a Double
. Ez egyszerűen túl nagy különbség ahhoz, hogy figyelmen kívül hagyjuk.
a SciMark 2.0 benchmark
eddig a mátrixszorzás egyetlen, egyszerű benchmarkját használtam annak bizonyítására, hogy a primitívek lényegesen nagyobb számítási teljesítményt nyújthatnak, mint az objektumok. Hogy megerősítsem az állításaimat, egy tudományosabb referenciaértéket fogok használni. SciMark 2.0 egy Java benchmark a tudományos és numerikus Számítástechnika elérhető a National Institute of Standards And Technology (NIST). Letöltöttem ennek a benchmarknak a forráskódját, és létrehoztam két verziót, az eredeti verziót primitívekkel, a második verziót pedig wrapper osztályokkal. A második változatnál a int
– et Integer
– re, a double
– t Double
– ra cseréltem, hogy a wrapper osztályok teljes hatását érjem el. Mindkét verzió elérhető a cikk forráskódjában.
a SciMark benchmark számos számítási rutin teljesítményét méri, és összetett pontszámot jelent hozzávetőleges mflops-ban (millió lebegőpontos művelet másodpercenként). Így a nagyobb számok jobbak ehhez a referenciaértékhez. A 3. táblázat a referenciaérték egyes verzióinak több futtatásából származó átlagos összetett pontszámokat adja meg a számítógépemen. Mint látható, a SciMark 2.0 benchmark két verziójának futásidejű teljesítménye összhangban volt a fenti mátrixszorzási eredményekkel, mivel a primitívekkel rendelkező verzió majdnem ötször gyorsabb volt, mint a wrapper osztályokat használó verzió.
látta a Java programok néhány változatát, amelyek numerikus számításokat végeznek, mind otthoni, mind tudományos szempontból. De hogyan viszonyul a Java más nyelvekhez? Végezetül röviden áttekintem, hogy a Java teljesítménye hogyan viszonyul három másik programozási nyelvhez: Scala, C++ és JavaScript.
Benchmarking Scala
a Scala egy programozási nyelv, amely a JVM-en fut, és úgy tűnik, hogy egyre népszerűbb. A Scala egységes típusrendszerrel rendelkezik, ami azt jelenti, hogy nem tesz különbséget a primitívek és a tárgyak között. Erik Osheim szerint a Scala numerikus típusú osztályában (Pt. 1), A Scala primitív típusokat használ, ha lehetséges, de szükség esetén objektumokat fog használni. Hasonlóképpen, Martin Odersky leírása Scala Tömbjeiről azt mondja, hogy”… a Scala tömb Array
Java int
– ként, a Array
Java double
– ként van ábrázolva …”
tehát ez azt jelenti, hogy a Scala egységes típusú rendszerének futási teljesítménye összehasonlítható lesz a Java primitív típusaival? Lássuk csak.
nem vagyok olyan jártas a Scala-val, mint a Java-val, de megpróbáltam a mátrixszorzási benchmark kódját közvetlenül Java-ról Scala-ra konvertálni. Az eredmény az alábbi 3. listában látható. Amikor a benchmark Scala verzióját futtattam a számítógépemen, átlagosan 12, 30 másodperc volt, ami a Scala teljesítményét nagyon közel hozza a Java primitívekhez. Ez az eredmény sokkal jobb, mint amire számítottam, és alátámasztja azt az állítást, hogy a Scala hogyan kezeli a numerikus típusokat.
lista 3. Két mátrix szorzása Scala
Benchmarking C++
mivel a C++ közvetlenül “csupasz fémen” fut, nem pedig virtuális gépen, természetesen elvárható, hogy a C++ gyorsabban fusson, mint a Java. Ezenkívül a Java teljesítményét kissé csökkenti az a tény, hogy a Java ellenőrzi a tömbökhöz való hozzáférést annak biztosítása érdekében, hogy minden index a tömb számára deklarált határokon belül legyen, míg a C++ nem (egy C++ funkció, amely puffertúlcsordulásokhoz vezethet, amelyeket a hackerek kihasználhatnak). Úgy találtam, hogy a C++ valamivel kínosabb, mint a Java az alapvető kétdimenziós tömbök kezelésében, de szerencsére ennek a kínosságnak nagy része elrejthető egy osztály privát részeiben. A C++ számára létrehoztam egy Matrix
osztály egyszerű változatát, és a *
operátort két mátrix szorzásához túlterheltem, de az alapvető mátrixszorzási algoritmust közvetlenül a Java verzióból konvertáltam. A C++ forráskód a 4. listában látható.
lista 4. Két mátrix szorzása C++ – ban
az Eclipse CDT (Eclipse for C++ Developers) használatával a MinGW C++ fordítóval létrehozható mind az alkalmazás hibakeresési, mind kiadási verziói. A C++ teszteléséhez többször futtattam a kiadási verziót, és átlagoltam az eredményeket. Ahogy az várható volt, a C++ észrevehetően gyorsabban futott ezen az egyszerű benchmarkon, átlagosan 7,58 másodpercet a számítógépemen. Ha a nyers teljesítmény az elsődleges tényező a programozási nyelv kiválasztásában, akkor a C++ a választott nyelv a numerikusan intenzív alkalmazásokhoz.
Benchmarking JavaScript
Oké, ez meglepett. Tekintettel arra, hogy a JavaScript nagyon dinamikus nyelv, arra számítottam, hogy a teljesítménye a legrosszabb, még rosszabb, mint a Java a wrapper osztályokkal. De valójában a JavaScript teljesítménye sokkal közelebb állt a Java primitívekhez. A JavaScript teszteléséhez telepítettem a csomópontot.js, egy JavaScript motor, amelynek hírneve nagyon hatékony. Az eredmények átlagosan 15,91 másodpercet tettek ki. Lista 5 mutatja A JavaScript változata a mátrix szorzás benchmark, hogy futottam Node.js
lista 5. Két mátrix szorzása JavaScript-ben
összefoglalva
amikor a Java először megérkezett a helyszínre mintegy 18 évvel ezelőtt, ez nem volt a legjobb nyelv teljesítmény szempontjából a numerikus számítások által uralt alkalmazások számára. De az idő múlásával, a technológiai fejlődéssel olyan területeken, mint a just-In-time (JIT) összeállítás (más néven adaptív vagy dinamikus összeállítás), a Java teljesítménye az ilyen típusú alkalmazásoknál ma már összehasonlítható azokkal a nyelvekkel, amelyeket natív kódba fordítanak, amikor primitív típusokat használnak.
ezenkívül a primitívek kiküszöbölik a szemétgyűjtés szükségességét, ezáltal újabb teljesítményelőnyt biztosítanak a primitíveknek az objektumtípusokkal szemben. A 4. táblázat összefoglalja a mátrixszorzási referenciaérték futásidejű teljesítményét a számítógépemen. Más tényezők, mint például a karbantarthatóság, a hordozhatóság és a fejlesztői szakértelem, a Java-t jobb választássá teszik sok ilyen alkalmazás számára.
mint korábban tárgyaltuk, úgy tűnik, hogy az Oracle komolyan fontolóra veszi a primitívek eltávolítását a Java jövőbeli verziójában. Hacsak a Java fordító nem képes a primitívekhez hasonló teljesítményű kódot generálni, úgy gondolom, hogy a Java-ból való eltávolításuk kizárná a Java használatát bizonyos alkalmazásosztályokban; nevezetesen azok az alkalmazások, amelyeket numerikus számítások uralnak. Ebben a cikkben egy egyszerű mátrixszorzáson alapuló referenciaértéket és egy tudományosabb referenciaértéket, a SciMark 2.0-t használtam ennek a pontnak az érvelésére.
A szerzőről
John I. Moore, Jr. A Citadella matematika és számítástechnika professzora széles körű tapasztalattal rendelkezik mind az iparban, mind az egyetemeken, speciális szakértelemmel rendelkezik az objektumorientált technológia, a szoftverfejlesztés és az alkalmazott matematika területén. Több mint három évtizede tervez és fejleszt szoftvereket relációs adatbázisok és több magas rendű nyelv felhasználásával, és az 1.1-es verzió óta széles körben dolgozik a Java-ban. Emellett számos tudományos kurzust és ipari szemináriumot fejlesztett ki és tanított a számítástechnika fejlett témáiról.
további olvasmányok
- Paul Krill írta az Oracle hosszú távú Java-terveiről az “Oracle lays out long-range Java intentions” (JavaWorld, 2012.március) című könyvben. Ez a cikk, a kapcsolódó megjegyzésszálakkal együtt, motivált, hogy megírjam a primitívek védelmét.
- Szymon Guz a primitív típusok és burkolóosztályok benchmarkingjában elért eredményeiről a “Primitives and objects benchmark in Java” (SimonOnSoftware, 2011.január) című könyvben ír.
- a Programming — Principles and Practice Using C++ támogatási weboldalán (Addison-Wesley, 2009) A C++ készítője, Bjarne Stroustrup megvalósítást nyújt egy mátrix osztály számára, amely sokkal teljesebb, mint a cikkhez mellékelt.
- John Rose, Brian Goetz és Guy Steele megvitatják az értéktípusok fogalmát az “értékek állapota” (OpenJDK.net, 2014. április). Az értéktípusokat megváltoztathatatlannak tekinthetjük felhasználó által definiált aggregált típusok identitás nélkül, mind az objektumok, mind a primitívek tulajdonságainak kombinálásával. Az értéktípusok mantrája: “kódok, mint egy osztály, úgy működik, mint egy int.”