자바에서 프리미티브를 유지하는 경우

Primitives는 1996 년 처음 출시 된 이후 Java 프로그래밍 언어의 일부 였지만 여전히 논란이 많은 언어 기능 중 하나입니다. John Moore는 프리미티브 유무에 관계없이 간단한 Java 벤치 마크를 비교하여 Java 언어로 프리미티브를 유지하는 강력한 사례를 만듭니다. 그런 다음 특정 유형의 애플리케이션에서 Java의 성능을 Scala, C ++ 및 JavaScript의 성능과 비교합니다. 여기서 기본 요소는 눈에 띄는 차이를 만듭니다.

질문 : 부동산 구매에서 가장 중요한 세 가지 요소는 무엇입니까?

답변 : 위치, 위치, 위치.

이 오래되고 자주 사용되는 속담은 부동산과 관련하여 위치가 다른 모든 요소를 ​​완전히 지배한다는 것을 의미합니다. 유사한 주장에서 Java에서 기본 유형을 사용하기 위해 고려해야 할 가장 중요한 세 가지 요소는 성능, 성능, 성능입니다. 부동산에 대한 주장과 원시에 대한 주장에는 두 가지 차이점이 있습니다. 첫째, 부동산의 경우 거의 모든 상황에서 위치가 지배적이지만 기본 유형을 사용하여 얻을 수있는 성능 이점은 응용 프로그램 종류에 따라 크게 달라질 수 있습니다. 둘째, 부동산의 경우 일반적으로 위치에 비해 사소하지만 고려해야 할 다른 요소가 있습니다. 원시 유형을 사용하는 이유는 성능입니다.; 그리고 그 응용 프로그램이 그들의 사용으로부터 혜택을받을 수있는 종류 인 경우에만.

프리미티브는 백엔드에 데이터베이스가있는 클라이언트-서버 프로그래밍 모델을 사용하는 대부분의 비즈니스 관련 및 인터넷 애플리케이션에 거의 가치를 제공하지 않습니다. 그러나 수치 계산이 지배하는 응용 프로그램의 성능은 기본 요소를 사용하여 큰 이점을 얻을 수 있습니다.

이 결정과 관련된 기사 및 포럼 게시물의 수에서 알 수 있듯이 Java에 기본 요소를 포함시키는 것은 더 논란이 많은 언어 디자인 결정 중 하나였습니다. Simon Ritter는 2011 년 11 월 JAX London의 기조 연설에서 Java의 차기 버전에서 기본 요소를 제거하는 것을 진지하게 고려하고 있다고 언급했습니다 (슬라이드 41 참조). 이 기사에서는 프리미티브와 Java의 이중 유형 시스템을 간략하게 소개합니다. 코드 샘플과 간단한 벤치 마크를 사용하여 특정 유형의 애플리케이션에 Java 기본 요소가 필요한 이유를 설명하겠습니다. 또한 Java의 성능을 Scala, C ++ 및 JavaScript의 성능과 비교합니다.

소프트웨어 성능 측정

소프트웨어 성능은 일반적으로 시간과 공간의 측면에서 측정됩니다. 시간은 3.7 분과 같은 실제 실행 시간이거나 O ( n 2) 와 같은 입력 크기에 따른 증가 순서 일 수 있습니다 . 공간 성능에 대한 유사한 측정이 존재하며, 이는 종종 주 메모리 사용량으로 표현되지만 디스크 사용량으로 확장 될 수도 있습니다. 성능 향상에는 일반적으로 시간을 개선하기위한 변경 사항이 종종 공간에 해로운 영향을 미치고 그 반대의 경우도 있다는 점에서 시간-공간 절충이 포함됩니다. 성장 순서 측정은 알고리즘에 따라 다르며 래퍼 클래스에서 기본 형식으로 전환해도 결과가 변경되지 않습니다. 그러나 실제 시간 및 공간 성능과 관련하여 래퍼 클래스 대신 기본 형식을 사용하면 시간과 공간이 동시에 향상됩니다.

프리미티브 대 객체

이 기사를 읽고 있는지 이미 알고 계시 겠지만, Java에는 일반적으로 기본 유형 및 객체 유형이라고하는 이중 유형 시스템이 있으며, 종종 단순히 기본 유형 및 객체로 축약됩니다. Java에 사전 정의 된 8 개의 기본 유형이 있으며 이름은 예약 된 키워드입니다. 일반적으로 사용되는 예는 int, double하고 boolean. 기본적으로 모든 사용자 정의 유형을 포함하여 Java의 다른 모든 유형은 객체 유형입니다. (배열 유형은 약간의 하이브리드이기 때문에 "본질적으로"라고 말하지만, 기본 유형보다는 객체 유형과 훨씬 더 비슷합니다.) 각 원시 유형에는 객체 유형 인 해당 래퍼 클래스가 있습니다. 예에는 Integerfor int, Doublefor doubleBooleanfor가 포함 boolean됩니다.

원시 유형은 값 기반이지만 객체 유형은 참조 기반이며 그 안에 원시 유형의 논쟁의 원인과 힘이 있습니다. 차이점을 설명하기 위해 아래 두 선언을 고려하십시오. 첫 번째 선언은 기본 유형을 사용하고 두 번째 선언은 래퍼 클래스를 사용합니다.

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

JDK 5에 추가 된 기능인 오토 박싱을 사용하여 두 번째 선언을 간단히

 Integer n2 = 100; 

그러나 기본 의미는 변경되지 않습니다. 오토 박싱은 래퍼 클래스의 사용을 단순화하고 프로그래머가 작성해야하는 코드의 양을 줄이지 만 런타임에 아무것도 변경하지 않습니다.

기본 n1개체와 래퍼 개체 의 차이점은 n2그림 1의 다이어그램에 나와 있습니다.

존 I. 무어 주니어

변수 n1는 정수 값을 보유하지만 변수 n2는 객체에 대한 참조를 포함하며 정수 값을 보유하는 객체입니다. 또한에서 참조하는 객체 n2에는 object 클래스에 대한 참조도 포함 Double됩니다.

프리미티브의 문제

원시 유형의 필요성을 이해하기 전에 많은 사람들이 저에 동의하지 않을 것임을 인정해야합니다. "유해한 것으로 간주되는 원시 유형"의 Sherman Alpert는 원시 유형이 "절차 적 의미론을 통일 된 객체 지향 모델에 혼합하기 때문에 해롭다"고 주장합니다. 원시 객체는 일류 객체가 아니지만 주로 첫 번째를 포함하는 언어로 존재합니다. 클래스 개체. " 프리미티브와 객체 (래퍼 클래스 형태)는 논리적으로 유사한 유형을 처리하는 두 가지 방법을 제공하지만 기본 의미는 매우 다릅니다. 예를 들어 두 인스턴스가 같은지 어떻게 비교해야합니까? 기본 유형의 경우 ==연산자를 사용하지만 객체의 경우 선호하는 선택은equals()기본 요소에 대한 옵션이 아닙니다. 마찬가지로 값을 할당하거나 매개 변수를 전달할 때 다른 의미가 존재합니다. 기본값조차도 다릅니다. 예를 들어, 0intnull대한 Integer.

이 문제에 대한 자세한 배경 정보는 기본 요소의 장단점을 요약 한 Eric Bruno의 블로그 게시물 "A modern primitive discussion"을 참조하십시오. Stack Overflow에 대한 많은 토론에서는 "사람들이 Java에서 여전히 기본 유형을 사용하는 이유는 무엇입니까?"를 포함하여 기본 요소에 중점을 둡니다. 그리고 "원본 대신 항상 객체를 사용하는 이유가 있습니까?" 프로그래머 Stack Exchange는 "Java에서 기본 대 클래스를 사용할 때"라는 유사한 토론을 진행합니다.

메모리 활용

doubleJava의 A 는 항상 메모리에서 64 비트를 차지하지만 참조 크기는 JVM (Java Virtual Machine)에 따라 다릅니다. 내 컴퓨터는 64 비트 버전의 Windows 7과 64 비트 JVM을 실행하므로 내 컴퓨터의 참조가 64 비트를 차지합니다. 도 1의 도면에 기초하여 I 번의 기대 double등을 n18 바이트 (64 비트)를 점유하고, I는 단일 기대 Double등을 n224 바이트를 차지하는데 -에 대한 개체 (8)에 대한 참조를 위해 8 double에 저장된 값 에 대한 클래스 개체에 대한 참조는 8입니다 Double. 또한 Java는 기본 유형이 아닌 객체 유형에 대한 가비지 수집을 지원하기 위해 추가 메모리를 사용합니다. 확인 해보자.

"Java 기본 유형 대 래퍼"의 Glen McCluskey와 유사한 접근 방식을 사용하여 목록 1에 표시된 방법은의 nxn 행렬 (2 차원 배열)이 차지하는 바이트 수를 측정합니다 double.

목록 1. double 유형의 메모리 사용률 계산

 public static long getBytesUsingPrimitives(int n) { System.gc(); // force garbage collection long memStart = Runtime.getRuntime().freeMemory(); double[][] a = new double[n][n]; // put some random values in the matrix for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) a[i][j] = Math.random(); } long memEnd = Runtime.getRuntime().freeMemory(); return memStart - memEnd; } 

Modifying the code in Listing 1 with the obvious type changes (not shown), we can also measure the number of bytes occupied by an n-by-n matrix of Double. When I test these two methods on my computer using 1000-by-1000 matrices, I get the results shown in Table 1 below. As illustrated, the version for primitive type double equates to a little more than 8 bytes per entry in the matrix, roughly what I expected. However, the version for object type Double required a little more than 28 bytes per entry in the matrix. Thus, in this case, the memory utilization of Double is more than three times the memory utilization of double, which should not be a surprise to anyone who understands the memory layout illustrated in Figure 1 above.

Table 1. Memory utilization of double versus Double

Version Total bytes Bytes per entry
Using double 8,380,768 8.381
Using Double 28,166,072 28.166

Runtime performance

To compare the runtime performances for primitives and objects, we need an algorithm dominated by numerical calculations. For this article I have chosen matrix multiplication, and I compute the time required to multiply two 1000-by-1000 matrices. I coded matrix multiplication for double in a straightforward manner as shown in Listing 2 below. While there may be faster ways to implement matrix multiplication (perhaps using concurrency), that point is not really relevant to this article. All I need is common code in two similar methods, one using the primitive double and one using the wrapper class Double. The code for multiplying two matrices of type Double is exactly like that in Listing 2 with the obvious type changes.

Listing 2. Multiplying two matrices of type double

 public static double[][] multiply(double[][] a, double[][] b) { if (!checkArgs(a, b)) throw new IllegalArgumentException("Matrices not compatible for multiplication"); 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) { double sum = 0.0; for (int i = 0; i < a[0].length; ++i) sum += a[rowNum][i]*b[i][colNum]; result[rowNum][colNum] = sum; } } return result; } 

I ran the two methods to multiply two 1000-by-1000 matrices on my computer several times and measured the results. The average times are shown in Table 2. Thus, in this case, the runtime performance of double is more than four times as fast as that of Double. That is simply too much of a difference to ignore.

Table 2. Runtime performance of double versus Double

Version Seconds
Using double 11.31
Using Double 48.48

The SciMark 2.0 benchmark

지금까지 행렬 곱셈의 간단한 단일 벤치 마크를 사용하여 기본 요소가 객체보다 훨씬 더 큰 컴퓨팅 성능을 제공 할 수 있음을 보여주었습니다. 내 주장을 강화하기 위해 더 과학적인 벤치 마크를 사용할 것입니다. SciMark 2.0은 NIST (National Institute of Standards and Technology)에서 제공하는 과학 및 수치 컴퓨팅을위한 Java 벤치 마크입니다. 이 벤치 마크의 소스 코드를 다운로드하고 두 가지 버전을 만들었습니다. 원래 버전은 기본 형식을 사용하고 다른 하나는 래퍼 클래스를 사용하는 버전입니다. 두 번째 버전 에서는 래퍼 클래스 사용의 전체 효과를 얻기 위해 및 로 교체 int했습니다 . 이 기사의 소스 코드에서 두 버전을 모두 사용할 수 있습니다.IntegerdoubleDouble

Benchmarking Java 다운로드 : 소스 코드 다운로드 John I. Moore, Jr.

The SciMark benchmark measures performance of several computational routines and reports a composite score in approximate Mflops (millions of floating point operations per second). Thus, larger numbers are better for this benchmark. Table 3 gives the average composite scores from several runs of each version of this benchmark on my computer. As shown, the runtime performances of the two versions of the SciMark 2.0 benchmark were consistent with the matrix multiplication results above in that the version with primitives was almost five times faster than the version using wrapper classes.

Table 3. Runtime performance of the SciMark benchmark

SciMark version Performance (Mflops)
Using primitives 710.80
Using wrapper classes 143.73

You've seen a few variations of Java programs doing numerical calculations, using both a homegrown benchmark and a more scientific one. But how does Java compare to other languages? I'll conclude with a quick look at how Java's performance compares to that of three other programming languages: Scala, C++, and JavaScript.

Benchmarking Scala

Scala는 JVM에서 실행되는 프로그래밍 언어이며 인기를 얻고있는 것으로 보입니다. 스칼라는 통일 된 타입 시스템을 가지고 있습니다. 즉, 프리미티브와 객체를 구별하지 않습니다. Scala의 Numeric 유형 클래스 (Pt. 1)의 Erik Osheim에 따르면, Scala는 가능하면 기본 유형을 사용하지만 필요한 경우 객체를 사용합니다. 마찬가지로 Martin Odersky의 Scala 배열에 대한 설명은 "... Scala 배열 Array[Int]은 Java 로 표시되고 int[]an Array[Double]은 Java 로 표시됩니다 double[]."라고 말합니다.

그렇다면 이것은 Scala의 통합 유형 시스템이 Java의 기본 유형에 필적하는 런타임 성능을 가질 것이라는 것을 의미합니까? 보자.