일부 내부 방법에서 최대한 많은 성능을 얻으려고합니다.
Java 코드는 다음과 같습니다.
List<DirectoryTaxonomyWriter> writers = Lists.newArrayList();
private final int taxos = 4;
[...]
@Override
public int getParent(final int globalOrdinal) throws IOException {
final int bin = globalOrdinal % this.taxos;
final int ordinalInBin = globalOrdinal / this.taxos;
return this.writers.get(bin).getParent(ordinalInBin) * this.taxos + bin; //global parent
}
내 프로파일 러에서 1 %의 CPU 소비가 있음 java.util.Objects.requireNonNull
을 보았지만 전화조차하지 않습니다. 바이트 코드를 검사 할 때 이것을 보았습니다.
public getParent(I)I throws java/io/IOException
L0
LINENUMBER 70 L0
ILOAD 1
ALOAD 0
INVOKESTATIC java/util/Objects.requireNonNull (Ljava/lang/Object;)Ljava/lang/Object;
POP
BIPUSH 8
IREM
ISTORE 2
따라서 컴파일러는 이것을 검사합니다 (무효?). null
어쨌든 할 수없는 프리미티브에 대해 작업 하므로 컴파일러가 왜이 줄을 생성합니까? 버그입니까? 아니면 ‘정상적인’행동?
(비트 마스크로 해결할 수도 있지만 궁금합니다.)
[최신 정보]
-
운영자는 관련이없는 것 같습니다 (아래 답변 참조).
-
이클립스 컴파일러 (버전 4.10)를 사용하면 이보다 합리적인 결과를 얻습니다.
공개 getParent (I) java / io / IOException 발생 L0 줄 번호 77 L0 ILOAD 1 아이콘 IREM 아이 스토어 2 L1 줄 번호 78 L
더 논리적입니다.
답변
왜 안돼?
가정
class C {
private final int taxos = 4;
public int test() {
final int a = 7;
final int b = this.taxos;
return a % b;
}
}
같은 전화 c.test()
곳 c
으로 선언 C
해야한다 때 던져 c
이다 null
. 귀하의 방법은
public int test() {
return 3; // `7 % 4`
}
상수로만 작업 할 때 로 test
비 정적 인, 검사가 수행해야합니다. 일반적으로 필드에 액세스하거나 비 정적 메서드가 호출되면 암시 적으로 수행되지만 그렇게하지는 않습니다. 따라서 명시적인 검사가 필요합니다. 한 가지 가능성은에 전화하는 것 Objects.requireNonNull
입니다.
바이트 코드
바이트 코드가 기본적으로 성능과 관련이 없다는 것을 잊지 마십시오. 이 작업은 소스 코드와 일치하는 일부 바이트 코드 javac
를 생성 하는 것입니다. 해야 할 의미가 아니에요 어떤 최적화 된 코드는 일반적으로 더 세게 분석하는 것입니다으로 바이트 코드 인 반면, 최적화를 소스 코드 실제로 최적화 JIT 컴파일러가. 따라서 간단하게 유지해야합니다 ….javac
성능
내 프로파일 러에서 1 %의 CPU 소비가 있음을 보았습니다.
java.util.Objects.requireNonNull
먼저 프로파일 러를 비난합니다. Java 프로파일 링은 매우 어렵고 완벽한 결과를 기대할 수 없습니다.
아마도 메소드를 정적으로 만들어야합니다. null 확인에 대한이 기사를 반드시 읽어야 합니다 .
답변
글쎄, 내 질문은 연산자가 아니라 필드 자체와 관련이 없으므로 ‘잘못된’것 같습니다. 아직도 이유를 모르겠다 ..
public int test() {
final int a = 7;
final int b = this.taxos;
return a % b;
}
어느 것이로 변합니까?
public test()I
L0
LINENUMBER 51 L0
BIPUSH 7
ISTORE 1
L1
LINENUMBER 52 L1
ALOAD 0
INVOKESTATIC java/util/Objects.requireNonNull (Ljava/lang/Object;)Ljava/lang/Object;
POP
ICONST_4
ISTORE 2
L2
LINENUMBER 53 L2
BIPUSH 7
ILOAD 2
IREM
IRETURN
답변
먼저,이 동작에 대한 최소한의 재현 가능한 예가 있습니다.
/**
* OS: Windows 10 64x
* javac version: 13.0.1
*/
public class Test {
private final int bar = 5;
/**
* public int foo();
* Code:
* 0: iconst_5
* 1: ireturn
*/
public int foo() {
return bar;
}
/**
* public int foo2();
* Code:
* 0: aload_0
* 1: invokestatic #13 // Method java/util/Objects.requireNonNull:(Ljava/lang/Object;)Ljava/lang/Object;
* 4: pop
* 5: iconst_5
* 6: ireturn
*/
public int foo2() {
return this.bar;
}
}
동작은 Java 컴파일러가 컴파일 타임 상수를 최적화하는 방법 때문입니다 .
바이트 코드에서 foo()
객체 참조는 값을 얻기 위해 액세스됩니다 bar
. 컴파일 타임 상수이기 때문에 JVM은 단순히이 iconst_5
값을 반환 하기 위해 작업을 실행할 수 있기 때문입니다 .
bar
컴파일하지 않는 시간 상수로 변경하면 ( final
키워드 를 제거 하거나 선언 내에서 또는 생성자 내에서 초기화하지 않음) 다음을 얻을 수 있습니다.
/**
* OS: Windows 10 64x
* javac version: 13.0.1
*/
public class Test2 {
private int bar = 5;
/**
* public int foo();
* Code:
* 0: aload_0
* 1: getfield #7
* 4: ireturn
*/
public int foo() {
return bar;
}
/**
* public int foo2();
* Code:
* 0: aload_0
* 1: getfield #7
* 4: ireturn
*/
public int foo2() {
return this.bar;
}
}
여기서 피연산자 스택에 aload_0
대한 참조 를 푸시 this
한 다음 이 객체 의 bar
필드 를 가져옵니다 .
여기서 컴파일러는 aload_0
( this
멤버 함수의 경우 참조)가 논리적으로 될 수 없다는 것을 알기에 충분히 영리 합니다 null
.
이제 귀하의 경우에는 실제로 컴파일러 최적화가 누락 되었습니까?
@maaartinus 답변을 참조하십시오.