[java] int의 나머지 연산자는 java.util.Objects.requireNonNull?

일부 내부 방법에서 최대한 많은 성능을 얻으려고합니다.

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어쨌든 할 수없는 프리미티브에 대해 작업 하므로 컴파일러가 왜이 줄을 생성합니까? 버그입니까? 아니면 ‘정상적인’행동?

(비트 마스크로 해결할 수도 있지만 궁금합니다.)

[최신 정보]

  1. 운영자는 관련이없는 것 같습니다 (아래 답변 참조).

  2. 이클립스 컴파일러 (버전 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 답변을 참조하십시오.


답변