Toán tử còn lại trên int gây ra java.util.Objects.requireNonNull?


12

Tôi đang cố gắng để có được hiệu suất cao nhất có thể từ một số phương pháp nội bộ.

Mã Java là:

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
}

Trong hồ sơ của tôi, tôi thấy có 1% CPU chi tiêu java.util.Objects.requireNonNull, nhưng tôi thậm chí không gọi đó. Khi kiểm tra mã byte, tôi thấy điều này:

 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

Vì vậy, trình biên dịch tạo ra kiểm tra này (vô dụng?). Tôi làm việc trên các nguyên thủy, không thể là nulldù sao, vậy tại sao trình biên dịch tạo ra dòng này? Có phải là một lỗi? Hay hành vi 'bình thường'?

(Tôi có thể làm việc xung quanh với một bitmask, nhưng tôi chỉ tò mò)

[CẬP NHẬT]

  1. Toán tử dường như không có gì để làm với nó (xem câu trả lời bên dưới)

  2. Sử dụng trình biên dịch nhật thực (phiên bản 4.10) tôi nhận được kết quả hợp lý hơn này:

    công khai getParent (I) Tôi ném java / io / IOException 
       L0
        NGÀY 77 L0
        ILOAD 1
        ICONST_4
        IREM
        ISTORE 2
       L1
        NGÀY 78 L

Vì vậy, đó là hợp lý hơn.


@Lino chắc chắn, nhưng điều đó không thực sự phù hợp với dòng 70 với nguyên nhânINVOKESTATIC
RobAu

Bạn sử dụng trình biên dịch nào? Bình thường javackhông tạo ra điều này.
apangin

Bạn sử dụng trình biên dịch nào? Phiên bản Java, Openjdk / Oracle / vv.? Chỉnh sửa: whops, @apangin đã nhanh hơn, xin lỗi
lugiorgi

1
Nó được biên dịch từ Intellij 2019.3, với java 11, openjdk version "11.0.6" 2020-01-14trên Ubuntu 64 bit.
RobAu

Câu trả lời:


3

Tại sao không?

Giả định

class C {
    private final int taxos = 4;

    public int test() {
        final int a = 7;
        final int b = this.taxos;
        return a % b;
    }
}

một cuộc gọi như c.test()nơi cđược tuyên bố là C phải ném khi cnull. Phương pháp của bạn tương đương với

    public int test() {
        return 3; // `7 % 4`
    }

khi bạn làm việc với hằng số. Với testviệc không tĩnh, việc kiểm tra phải được thực hiện. Thông thường, nó sẽ được thực hiện hoàn toàn khi một trường được truy cập hoặc một phương thức không tĩnh được gọi, nhưng bạn không làm điều đó. Vì vậy, một kiểm tra rõ ràng là cần thiết. Một khả năng là gọi Objects.requireNonNull.

Mã byte

Quên không phải là mã byte về cơ bản không liên quan đến hiệu suất. Nhiệm vụ của javaclà tạo ra một số mã byte có thực thi tương ứng với mã nguồn của bạn. Nó không có nghĩa là thực hiện bất kỳ tối ưu hóa nào , vì mã được tối ưu hóa thường dài hơn và khó phân tích hơn, trong khi mã byte thực sự là mã nguồn cho trình biên dịch JIT tối ưu hóa. Vì vậy, javacdự kiến ​​sẽ giữ cho nó đơn giản ....

Buổi biểu diễn

Trong hồ sơ của tôi, tôi thấy có 1% chi tiêu cho CPU java.util.Objects.requireNonNull

Tôi sẽ đổ lỗi cho hồ sơ đầu tiên. Hồ sơ Java khá khó và bạn không bao giờ có thể mong đợi kết quả hoàn hảo.

Bạn có lẽ nên thử làm cho phương thức tĩnh. Bạn chắc chắn nên đọc bài viết này về kiểm tra null .


1
Cảm ơn @maaartinus cho câu trả lời sâu sắc của bạn. Tôi chắc chắn sẽ đọc lên bài viết liên kết của bạn.
RobAu

1
Với việc kiểm tra là không tĩnh, việc kiểm tra phải được thực hiện. Thực tế, không có lý do gì để kiểm tra xem có phải thislà không null. Như bạn nói cho mình, một cuộc gọi như c.test()phải thất bại khi cnullvà nó đã thất bại ngay lập tức, thay vì nhập phương pháp. Vì vậy, bên trong test(), thiskhông bao giờ có thể có null(nếu không sẽ có lỗi JVM). Vì vậy, không cần phải kiểm tra. Khắc phục thực tế nên thay đổi trường taxosthành static, vì không có điểm nào trong việc lưu trữ bộ nhớ trong mọi trường hợp cho hằng số thời gian biên dịch. Sau đó, cho dù test()statickhông thích hợp.
Holger

2

Vâng, có vẻ như câu hỏi của tôi là "sai" vì nó không liên quan gì đến nhà điều hành, mà là chính lĩnh vực này. Vẫn không biết tại sao ..

   public int test() {
        final int a = 7;
        final int b = this.taxos;
        return a % b;
    }

Mà biến thành:

  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
Trình biên dịch thực sự có thể sợ rằng thistài liệu tham khảo null? Điều này sẽ có thể?
atalantus

1
Không, điều đó không có ý nghĩa gì, trừ khi trình biên dịch biên dịch trường thành một Integercách nào đó, và đây là kết quả của việc tự động đóng hộp?
RobAu

1
Không ALOAD 0tham khảo this? Vì vậy, sẽ có ý nghĩa (không thực sự) rằng trình biên dịch thêm nullcheck
Lino

1
Vì vậy, trình biên dịch đang thực sự thêm một kiểm tra null cho this? Tuyệt vời: /
RobAu

1
Tôi sẽ cố gắng tạo một đoạn mã tối thiểu bằng dòng lệnh javacđể xác minh vào ngày mai; và nếu điều đó cũng cho thấy hành vi này, tôi nghĩ rằng nó có thể là một lỗi javac?
RobAu

2

Thứ nhất, đây là một ví dụ tái tạo tối thiểu của hành vi này:

/**
 * 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;
    }
}

Hành vi này là do cách trình biên dịch Java tối ưu hóa các hằng số thời gian biên dịch .

Lưu ý rằng trong mã byte foo()không có tham chiếu đối tượng được truy cập để lấy giá trị của bar. Đó là bởi vì nó là hằng số thời gian biên dịch và do đó JVM có thể thực hiện iconst_5thao tác để trả về giá trị này.

Khi thay đổi barthành hằng số thời gian không biên dịch (bằng cách xóa finaltừ khóa hoặc không khởi tạo trong khai báo nhưng bên trong hàm tạo), bạn sẽ nhận được:

/**
 * 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;
    }
}

nơi aload_0đẩy tài liệu tham khảo của thisvào toán hạng ngăn xếp để sau đó có được barlĩnh vực của đối tượng này.

Ở đây trình biên dịch đủ thông minh để nhận thấy rằng aload_0( thistham chiếu trong trường hợp các hàm thành viên) có thể không hợp lý null.

Bây giờ là trường hợp của bạn thực sự là một tối ưu hóa trình biên dịch bị thiếu?

Xem câu trả lời @maaartinus.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.