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


12

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

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

더 논리적입니다.


@Lino는 확실하지만, 실제로 70 번째 줄과 관련이있는 것은INVOKESTATIC
RobAu

어떤 컴파일러를 사용하십니까? 보통 javac은 이것을 생성하지 않습니다.
apangin

어떤 컴파일러를 사용하십니까? Java 버전, Openjdk / Oracle / etc? 편집 : whops, @apangin이 더 빠르다, 죄송합니다
lugiorgi

1
openjdk version "11.0.6" 2020-01-14우분투 64 비트에서 java 11을 사용하여 Intellij 2019.3에서 컴파일됩니다 .
RobAu

답변:


3

왜 안돼?

가정

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 확인에 대한이 기사를 반드시 읽어야 합니다 .


1
통찰력있는 답변에 대한 @maaartinus에게 감사드립니다. 링크 된 기사를 반드시 읽어 드리겠습니다.
RobAu

1
여부를 테스트 할 이유가없고, 실제로는 "테스트가 아닌 정적 인 상태 검사가 수행되어야한다" this비는이다 null. 당신이 자신을 말했듯이, 같은 호출 c.test()할 때 실패합니다 c입니다 null및 대신 방법을 입력하는 즉시 실패한다. 따라서 내 test()에서는 this절대로 null존재할 수 없습니다 (그렇지 않으면 JVM 버그가 있음). 따라서 확인할 필요가 없습니다. 컴파일 타임 상수를 위해 모든 인스턴스에서 메모리를 예약 할 시점이 없으므로 실제 수정은 필드 taxos를 로 변경해야합니다 static. 그렇다면 관련 test()이 없는지 여부 입니다 static.
Holger

2

글쎄, 내 질문은 연산자가 아니라 필드 자체와 관련이 없으므로 '잘못된'것 같습니다. 아직도 이유를 모르겠다 ..

   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

1
컴파일러가 실제로 this참조를 두려워 할 수 null있습니까? 이것이 가능할까요?
atalantus

1
컴파일러가 Integer어떻게 든 필드를 컴파일하지 않는 한 아무런 의미가 없습니다. 이것이 오토 박스의 결과입니까?
RobAu

1
ALOAD 0참조 하지 this않습니까? 따라서 컴파일러가 nullcheck를 추가하는 것이 이치에 맞습니다.
Lino

1
따라서 컴파일러는 실제로 null 검사를 추가하고 this있습니까? Great : /
RobAu

1
나는 javac내일을 검증하기 위해 명령 줄로 최소한의 코드를 만들려고 노력할 것이다 . 그리고 이것이 또한이 행동을 보여 주면 javac-bug 일 수 있다고 생각합니까?
RobAu

2

먼저,이 동작에 대한 최소한의 재현 가능한 예가 있습니다.

/**
 * 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 답변을 참조하십시오.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.