Việc sử dụng từ khóa cuối cùng trong Java có cải thiện hiệu suất không?


347

Trong Java, chúng ta thấy rất nhiều nơi mà finaltừ khóa có thể được sử dụng nhưng việc sử dụng nó không phổ biến.

Ví dụ:

String str = "abc";
System.out.println(str);

Trong trường hợp trên, strcó thể được finalnhưng điều này thường được bỏ đi.

Khi một phương thức sẽ không bao giờ bị ghi đè, chúng ta có thể sử dụng từ khóa cuối cùng. Tương tự như vậy trong trường hợp một lớp sẽ không được kế thừa.

Việc sử dụng từ khóa cuối cùng trong bất kỳ hoặc tất cả các trường hợp này có thực sự cải thiện hiệu suất không? Nếu vậy thì thế nào? Vui lòng giải thích. Nếu việc sử dụng đúng cách finalthực sự quan trọng đối với hiệu năng, lập trình viên Java nên phát triển thói quen nào để sử dụng tốt nhất từ ​​khóa?


Tôi không nghĩ vậy bạn ạ, việc gửi phương thức (gọi bộ đệm trang web và ...) là một vấn đề trong các ngôn ngữ động không phải là ngôn ngữ kiểu tĩnh
Jahan

Nếu tôi chạy công cụ PMD của mình (plugin để nhật thực) được sử dụng để xem xét mục đích, nó sẽ gợi ý thay đổi cho trường hợp như trong hình trên. Nhưng tôi không hiểu khái niệm của nó. Thực sự hiệu suất hit rất nhiều ??
Abhishek Jain

5
Tôi nghĩ rằng đây là một câu hỏi thi điển hình. Tôi nhớ rằng thức không có ảnh hưởng đến hiệu suất, IIRC lớp cuối cùng có thể được tối ưu hóa bởi JRE một cách nào đó bởi vì họ không thể được subclassed.
Kawu

Tôi thực sự đã thử nghiệm này. Trên tất cả các JVM, tôi đã thử nghiệm việc sử dụng cuối cùng trên các biến cục bộ đã cải thiện hiệu năng (tuy nhiên, tuy nhiên có thể là một yếu tố trong các phương thức tiện ích). Mã nguồn là trong câu trả lời của tôi dưới đây.
rustyx

1
"Tối ưu hóa sớm là gốc rễ của mọi tội lỗi". Chỉ cần để trình biên dịch làm công việc của nó. Viết mã dễ đọc và nhận xét tốt. Đó luôn là sự lựa chọn tốt nhất!
kaiser

Câu trả lời:


285

Thường thì không. Đối với các phương thức ảo, HotSpot theo dõi xem phương thức đó có thực sự bị ghi đè hay không và có thể thực hiện các tối ưu hóa như đưa ra giả định rằng một phương thức chưa bị ghi đè - cho đến khi nó tải một lớp ghi đè lên phương thức, tại thời điểm đó nó có thể hoàn tác (hoặc hoàn tác một phần) những tối ưu hóa đó.

(Tất nhiên, điều này giả sử bạn đang sử dụng HotSpot - nhưng đó là JVM phổ biến nhất, vì vậy ...)

Theo suy nghĩ của tôi, bạn nên sử dụng finaldựa trên thiết kế rõ ràng và dễ đọc hơn là vì lý do hiệu suất. Nếu bạn muốn thay đổi bất cứ điều gì vì lý do hiệu suất, bạn nên thực hiện các phép đo phù hợp trước khi bẻ cong mã rõ ràng nhất - theo cách đó bạn có thể quyết định liệu bất kỳ hiệu suất bổ sung nào đạt được có đáng để đọc / thiết kế kém hơn không. (Theo kinh nghiệm của tôi, nó gần như không bao giờ có giá trị; YMMV.)

EDIT: Như các lĩnh vực cuối cùng đã được đề cập, đáng để đưa ra rằng dù sao chúng thường là một ý tưởng tốt, về mặt thiết kế rõ ràng. Họ cũng thay đổi hành vi được bảo đảm về khả năng hiển thị chuỗi chéo: sau khi nhà xây dựng hoàn thành, bất kỳ trường cuối cùng nào cũng được đảm bảo hiển thị trong các luồng khác ngay lập tức. Đây có lẽ là cách sử dụng phổ biến nhất finaltheo kinh nghiệm của tôi, mặc dù với tư cách là người ủng hộ quy tắc "thiết kế thừa kế hoặc cấm đoán" của Josh Bloch, tôi có lẽ nên sử dụng finalthường xuyên hơn cho các lớp học ...


1
@ Abhishek: Về những gì cụ thể? Điểm quan trọng nhất là điểm cuối cùng - mà bạn gần như chắc chắn không nên lo lắng về điều này.
Jon Skeet

9
@Abishek: finalthường được khuyến nghị vì nó giúp mã dễ hiểu hơn và giúp tìm lỗi (vì nó làm cho các lập trình viên có ý định rõ ràng). PMD có thể khuyên bạn nên sử dụng finalvì những vấn đề về phong cách này, không phải vì lý do hiệu suất.
sleske

3
@Abhishek: Rất nhiều trong số đó có khả năng là đặc trưng của JVM và có thể dựa vào các khía cạnh rất tinh tế của bối cảnh. Ví dụ, tôi tin rằng JVM của máy chủ HotSpot vẫn sẽ cho phép nội tuyến các phương thức ảo khi được ghi đè trong một lớp, với kiểm tra loại nhanh khi thích hợp. Nhưng các chi tiết rất khó để xác định và có thể thay đổi giữa các bản phát hành.
Jon Skeet

5
Ở đây tôi xin trích dẫn Java hiệu quả, Phiên bản 2, Mục 15, Giảm thiểu khả năng biến đổi : Immutable classes are easier to design, implement, and use than mutable classes. They are less prone to error and are more secure.. Hơn nữa An immutable object can be in exactly one state, the state in which it was created.so với Mutable objects, on the other hand, can have arbitrarily complex state spaces.. Từ kinh nghiệm cá nhân của tôi, sử dụng từ khóa finalsẽ làm nổi bật ý định của nhà phát triển để nghiêng về tính bất biến, không phải để "tối ưu hóa" mã. Tôi khuyến khích bạn đọc chương này, hấp dẫn!
Louis F.

2
Các câu trả lời khác cho thấy rằng việc sử dụng finaltừ khóa trên các biến có thể làm giảm số lượng mã byte, điều này có thể tạo ra ảnh hưởng đến hiệu suất.
Julien Kronegg

86

Câu trả lời ngắn gọn: đừng lo lắng về điều đó!

Câu trả lời dài:

Khi nói về các biến cục bộ cuối cùng, hãy nhớ rằng việc sử dụng từ khóa finalsẽ giúp trình biên dịch tối ưu hóa mã tĩnh , điều này có thể dẫn đến mã nhanh hơn. Ví dụ, các Chuỗi cuối cùng a + btrong ví dụ dưới đây được nối tĩnh (tại thời gian biên dịch).

public class FinalTest {

    public static final int N_ITERATIONS = 1000000;

    public static String testFinal() {
        final String a = "a";
        final String b = "b";
        return a + b;
    }

    public static String testNonFinal() {
        String a = "a";
        String b = "b";
        return a + b;
    }

    public static void main(String[] args) {
        long tStart, tElapsed;

        tStart = System.currentTimeMillis();
        for (int i = 0; i < N_ITERATIONS; i++)
            testFinal();
        tElapsed = System.currentTimeMillis() - tStart;
        System.out.println("Method with finals took " + tElapsed + " ms");

        tStart = System.currentTimeMillis();
        for (int i = 0; i < N_ITERATIONS; i++)
            testNonFinal();
        tElapsed = System.currentTimeMillis() - tStart;
        System.out.println("Method without finals took " + tElapsed + " ms");

    }

}

Kết quả?

Method with finals took 5 ms
Method without finals took 273 ms

Đã thử nghiệm trên Java Hotspot VM 1.7.0_45-b18.

Vì vậy, cải thiện hiệu suất thực tế là bao nhiêu? Tôi không dám nói. Trong hầu hết các trường hợp có thể là cận biên (~ 270 nano giây trong thử nghiệm tổng hợp này vì việc nối chuỗi được tránh hoàn toàn - một trường hợp hiếm gặp), nhưng trong mã tiện ích được tối ưu hóa cao, nó có thể là một yếu tố. Trong mọi trường hợp, câu trả lời cho câu hỏi ban đầu là có, nó có thể cải thiện hiệu suất, nhưng tốt nhất là một chút .

Bỏ qua các lợi ích về thời gian biên dịch, tôi không thể tìm thấy bất kỳ bằng chứng nào cho thấy việc sử dụng từ khóa finalcó bất kỳ ảnh hưởng nào đến hiệu suất.


2
Tôi viết lại mã của bạn một chút để kiểm tra cả hai trường hợp 100 lần. Cuối cùng, thời gian trung bình của trận chung kết là 0 ms và 9 ms cho trận chung kết. Việc tăng số lần lặp lên 10M, đặt trung bình thành 0 ms và 75 ms. Chạy tốt nhất cho trận chung kết là 0 ms tuy nhiên. Có lẽ đó là kết quả phát hiện VM đang bị vứt đi hay sao? Tôi không biết, nhưng bất kể, việc sử dụng cuối cùng sẽ tạo ra một sự khác biệt đáng kể.
Casper Færgemand

5
Thử nghiệm thiếu sót. Các thử nghiệm trước đó sẽ khởi động JVM và mang lại lợi ích cho các yêu cầu thử nghiệm sau này. Sắp xếp lại các bài kiểm tra của bạn và xem những gì sẽ xảy ra. Bạn cần chạy từng thử nghiệm trong cá thể JVM của riêng nó.
Steve Kuo

16
Không có bài kiểm tra nào là không hoàn hảo, sự khởi động đã được tính đến. Bài kiểm tra thứ hai là SLOWER, không nhanh hơn. Nếu không khởi động, bài kiểm tra thứ hai sẽ NGAY LẬP TỨC.
rustyx

6
Trong testFinal () tất cả thời gian trả về cùng một đối tượng, từ nhóm chuỗi, vì việc nối lại chuỗi cuối cùng và nối chuỗi bằng chữ được đánh giá tại thời điểm biên dịch. testNonFinal () mỗi khi trả về đối tượng mới, điều đó giải thích sự khác biệt về tốc độ.
anber

4
Điều gì khiến bạn nghĩ kịch bản là không thực tế? Stringnối là một hoạt động tốn kém hơn nhiều so với thêm Integers. Thực hiện tĩnh (nếu có thể) sẽ hiệu quả hơn, đó là những gì thử nghiệm cho thấy.
rustyx

62

CÓ nó có thể. Đây là một ví dụ nơi cuối cùng có thể tăng hiệu suất:

Biên dịch có điều kiện là một kỹ thuật trong đó các dòng mã không được biên dịch vào tệp lớp dựa trên một điều kiện cụ thể. Điều này có thể được sử dụng để loại bỏ hàng tấn mã gỡ lỗi trong một bản dựng sản xuất.

xem xét những điều sau đây

public class ConditionalCompile {

  private final static boolean doSomething= false;

    if (doSomething) {
       // do first part. 
    }

    if (doSomething) {
     // do second part. 
    }

    if (doSomething) {     
      // do third part. 
    }

    if (doSomething) {
    // do finalization part. 
    }
}

Bằng cách chuyển đổi thuộc tính doS Something thành thuộc tính cuối cùng, bạn đã nói với trình biên dịch rằng bất cứ khi nào nó thấy doS Something, nó sẽ thay thế nó bằng false theo quy tắc thay thế thời gian biên dịch. Pass đầu tiên của trình biên dịch thay đổi mã thành một cái gì đó như thế này:

public class ConditionalCompile {

  private final static boolean doSomething= false;

    if (false){
       // do first part. 
    }

    if (false){
     // do second part. 
    }

    if (false){
      // do third part. 
    }

    if (false){
    // do finalization part. 

    }
}

Một khi điều này được thực hiện, trình biên dịch sẽ có cái nhìn khác về nó và thấy rằng có các câu lệnh không thể truy cập được trong mã. Vì bạn đang làm việc với một trình biên dịch chất lượng hàng đầu, nên nó không giống như tất cả các mã byte không thể truy cập đó. Vì vậy, nó loại bỏ chúng, và bạn kết thúc với điều này:

public class ConditionalCompile {


  private final static boolean doSomething= false;

  public static void someMethodBetter( ) {

    // do first part. 

    // do second part. 

    // do third part. 

    // do finalization part. 

  }
}

do đó giảm bất kỳ mã quá mức, hoặc bất kỳ kiểm tra có điều kiện không cần thiết.

Chỉnh sửa: Ví dụ: hãy lấy đoạn mã sau:

public class Test {
    public static final void main(String[] args) {
        boolean x = false;
        if (x) {
            System.out.println("x");
        }
        final boolean y = false;
        if (y) {
            System.out.println("y");
        }
        if (false) {
            System.out.println("z");
        }
    }
}

Khi biên dịch mã này với Java 8 và dịch ngược với javap -c Test.classchúng tôi nhận được:

public class Test {
  public Test();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static final void main(java.lang.String[]);
    Code:
       0: iconst_0
       1: istore_1
       2: iload_1
       3: ifeq          14
       6: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
       9: ldc           #22                 // String x
      11: invokevirtual #24                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      14: iconst_0
      15: istore_2
      16: return
}

Chúng ta có thể lưu ý rằng mã được biên dịch chỉ bao gồm biến không phải là cuối cùng x. Điều này dự đoán rằng các biến cuối cùng có tác động đến hiệu suất, ít nhất là đối với trường hợp đơn giản này.


1
@ UkaszLech Tôi đã học được điều này từ một cuốn sách Oreilly: Hardcore Java, trên chương của họ liên quan đến từ khóa cuối cùng.
mel3kings

15
Điều này nói về tối ưu hóa tại thời điểm biên dịch, có nghĩa là nhà phát triển biết GIÁ TRỊ của biến boolean cuối cùng tại thời điểm biên dịch, toàn bộ điểm viết nếu các khối ở vị trí đầu tiên sau đó đối với kịch bản này khi mà IF-ĐIỀU KIỆN không cần thiết và không thực hiện có ý nghĩa gì không? Theo tôi, ngay cả khi điều này làm tăng hiệu suất, đây là mã sai ngay từ đầu và có thể được tối ưu hóa bởi chính nhà phát triển thay vì chuyển trách nhiệm cho trình biên dịch và câu hỏi chủ yếu là hỏi về các cải tiến hiệu suất trong các mã thông thường được sử dụng cuối cùng làm cho ý nghĩa lập trình.
Bhavesh Agarwal

7
Điểm của điều này sẽ là thêm các câu lệnh gỡ lỗi như mel3kings đã nêu. Bạn có thể lật biến trước khi xây dựng sản xuất (hoặc định cấu hình nó trong tập lệnh xây dựng của bạn) và tự động xóa tất cả mã đó khi phân phối được tạo.
Adam


16

Tôi ngạc nhiên rằng không ai đã thực sự đăng một số mã thực sự được biên dịch lại để chứng minh rằng có ít nhất một số khác biệt nhỏ.

Đối với các tài liệu tham khảo này đã được thử nghiệm chống lại javacphiên bản 8, 910 .

Giả sử phương pháp này:

public static int test() {
    /* final */ Object left = new Object();
    Object right = new Object();

    return left.hashCode() + right.hashCode();
}

Biên dịch mã này, tạo ra mã byte chính xác như khi finalcó mặt (final Object left = new Object(); ).

Nhưng cái này:

public static int test() {
    /* final */ int left = 11;
    int right = 12;
    return left + right;
}

Sản xuất:

   0: bipush        11
   2: istore_0
   3: bipush        12
   5: istore_1
   6: iload_0
   7: iload_1
   8: iadd
   9: ireturn

Rời khỏi finalđể có mặt sản xuất:

   0: bipush        12
   2: istore_1
   3: bipush        11
   5: iload_1
   6: iadd
   7: ireturn

Mã này khá tự giải thích, trong trường hợp có hằng số thời gian biên dịch, nó sẽ được tải trực tiếp vào ngăn toán hạng (nó sẽ không được lưu trữ vào mảng biến cục bộ như ví dụ trước đó thông qua bipush 12; istore_0; iload_0 ) - điều này có ý nghĩa vì không ai có thể thay đổi nó.

Mặt khác, tại sao trong trường hợp thứ hai, trình biên dịch không tạo ra istore_0 ... iload_0nằm ngoài tôi, nó không giống như khe đó0 được sử dụng theo bất kỳ cách nào (nó có thể thu nhỏ mảng biến theo cách này, nhưng có thể tôi thiếu một số chi tiết bên trong, không thể nói chắc chắn)

Tôi đã ngạc nhiên khi thấy một sự tối ưu hóa như vậy, xem xét làm thế nào những người nhỏ bé javaclàm. Như chúng ta nên luôn luôn sử dụng final? Tôi thậm chí sẽ không viết một JMHbài kiểm tra (mà tôi muốn ban đầu), tôi chắc chắn rằng khác biệt là theo thứ tự ns(nếu có thể được nắm bắt). Nơi duy nhất có thể là một vấn đề, là khi một phương thức không thể được nội tuyến vì kích thước của nó (và khai báo finalsẽ thu nhỏ kích thước đó bằng một vài byte).

Có hai finals nữa cần được giải quyết. Đầu tiên là khi một phương thức là final(từ một JITphối cảnh), một phương thức như vậy là đơn hình - và đây là những phương thức được yêu thích nhất bởi JVM.

Sau đó, có finalcác biến thể hiện (phải được đặt trong mọi hàm tạo); những điều này rất quan trọng vì chúng sẽ đảm bảo một tài liệu tham khảo được công bố chính xác, như được chạm một chút ở đây và cũng được chỉ định chính xác bởi JLS.


Tôi đã biên dịch mã bằng Java 8 (JDK 1.8.0_162) với các tùy chọn gỡ lỗi (javac -g FinalTest.java ), dịch ngược mã bằng cách sử dụng javap -c FinalTest.classvà không thu được kết quả tương tự (với final int left=12, tôi đã nhận được bipush 11; istore_0; bipush 12; istore_1; bipush 11; iload_1; iadd; ireturn). Vì vậy, có, phụ thuộc mã byte được tạo ra dựa trên nhiều yếu tố và rất khó để nói liệu có finalhay không tạo ra hiệu ứng trên hiệu suất. Nhưng vì mã byte là khác nhau, một số khác biệt hiệu suất có thể tồn tại.
Julien Kronegg


13

Bạn đang thực sự hỏi về hai (ít nhất) các trường hợp khác nhau:

  1. final cho các biến cục bộ
  2. final cho các phương thức / lớp

Jon Skeet đã trả lời 2). Khoảng 1):

Tôi không nghĩ rằng nó làm cho một sự khác biệt; đối với các biến cục bộ, trình biên dịch có thể suy ra xem biến đó có phải là cuối cùng hay không (đơn giản bằng cách kiểm tra xem nó có được gán nhiều lần không). Vì vậy, nếu trình biên dịch muốn tối ưu hóa các biến chỉ được gán một lần, nó có thể làm như vậy bất kể biến đó có thực sự được khai báo finalhay không.

final có thể tạo sự khác biệt cho các trường lớp được bảo vệ / công khai; ở đó trình biên dịch rất khó tìm ra trường có được đặt nhiều lần không, vì nó có thể xảy ra từ một lớp khác (thậm chí có thể chưa được tải). Nhưng ngay cả khi đó JVM có thể sử dụng kỹ thuật mà Jon mô tả (tối ưu hóa một cách lạc quan, hoàn nguyên nếu một lớp được tải mà thay đổi trường).

Tóm lại, tôi không thấy bất kỳ lý do tại sao nó sẽ giúp hiệu suất. Vì vậy, loại tối ưu hóa vi mô này không có khả năng giúp đỡ. Bạn có thể thử điểm chuẩn để đảm bảo, nhưng tôi nghi ngờ nó sẽ tạo ra sự khác biệt.

Biên tập:

Trên thực tế, theo câu trả lời của Timo Westkämper, final có thể cải thiện hiệu suất cho các trường lớp trong một số trường hợp. Tôi đứng sửa.


Tôi không nghĩ trình biên dịch có thể kiểm tra chính xác số lần một biến cục bộ được gán: còn các cấu trúc if-then-other với nhiều phép gán thì sao?
gyorgyabraham

1
@gyabraham: Trình biên dịch đã kiểm tra các trường hợp này nếu bạn khai báo một biến cục bộ là final, để đảm bảo bạn không gán nó hai lần. Theo như tôi có thể thấy, logic kiểm tra tương tự có thể (và có lẽ là) được sử dụng để kiểm tra xem một biến có thể không final.
sleske

Số cuối cùng của một biến cục bộ không được biểu thị bằng mã byte, do đó JVM thậm chí không biết rằng đó là cuối cùng
Steve Kuo

1
@SteveKuo: Ngay cả khi nó không được thể hiện bằng mã byte, nó có thể giúp javactối ưu hóa tốt hơn. Nhưng đây chỉ là suy đoán.
sleske

1
Trình biên dịch có thể tìm hiểu xem một biến cục bộ đã được gán chính xác một lần chưa, nhưng trong thực tế, nó không (ngoài việc kiểm tra lỗi). Mặt khác, nếu một finalbiến có kiểu hoặc kiểu nguyên thủy Stringvà được gán ngay lập tức với hằng số thời gian biên dịch như trong ví dụ của câu hỏi, trình biên dịch phải nội tuyến nó, vì biến đó là hằng số thời gian biên dịch cho mỗi đặc tả. Nhưng đối với hầu hết các trường hợp sử dụng, mã có thể trông khác nhau, nhưng nó vẫn không có sự khác biệt cho dù hằng số được nội tuyến hay đọc từ một biến cục bộ hiệu năng thông minh.
Holger

6

Lưu ý: Không phải là chuyên gia java

Nếu tôi nhớ chính xác java của mình, sẽ có rất ít cách để cải thiện hiệu suất bằng cách sử dụng từ khóa cuối cùng. Tôi đã luôn biết nó tồn tại vì "mã tốt" - thiết kế và dễ đọc.


1

Tôi không phải là chuyên gia nhưng tôi cho rằng bạn nên thêm finaltừ khóa vào lớp hoặc phương thức nếu nó không bị ghi đè và để lại các biến. Nếu có bất kỳ cách nào để tối ưu hóa những thứ như vậy, trình biên dịch sẽ làm điều đó cho bạn.


1

Trên thực tế, trong khi thử nghiệm một số mã liên quan đến OpenGL, tôi thấy rằng việc sử dụng công cụ sửa đổi cuối cùng trên một trường riêng có thể làm giảm hiệu suất. Đây là sự khởi đầu của lớp tôi đã kiểm tra:

public class ShaderInput {

    private /* final */ float[] input;
    private /* final */ int[] strides;


    public ShaderInput()
    {
        this.input = new float[10];
        this.strides = new int[] { 0, 4, 8 };
    }


    public ShaderInput x(int stride, float val)
    {
        input[strides[stride] + 0] = val;
        return this;
    }

    // more stuff ...

Và đây là phương thức tôi đã sử dụng để kiểm tra hiệu năng của các phương án khác nhau, trong đó có lớp ShaderInput:

public static void test4()
{
    int arraySize = 10;
    float[] fb = new float[arraySize];
    for (int i = 0; i < arraySize; i++) {
        fb[i] = random.nextFloat();
    }
    int times = 1000000000;
    for (int i = 0; i < 10; ++i) {
        floatVectorTest(times, fb);
        arrayCopyTest(times, fb);
        shaderInputTest(times, fb);
        directFloatArrayTest(times, fb);
        System.out.println();
        System.gc();
    }
}

Sau lần lặp thứ 3, với VM được làm nóng, tôi liên tục nhận được các số liệu này mà không có từ khóa cuối cùng:

Simple array copy took   : 02.64
System.arrayCopy took    : 03.20
ShaderInput took         : 00.77
Unsafe float array took  : 05.47

Với từ khóa cuối cùng:

Simple array copy took   : 02.66
System.arrayCopy took    : 03.20
ShaderInput took         : 02.59
Unsafe float array took  : 06.24

Lưu ý các số liệu cho thử nghiệm ShaderInput.

Không quan trọng tôi làm cho các lĩnh vực công khai hay riêng tư.

Ngẫu nhiên, có một vài điều khó hiểu hơn. Lớp ShaderInput vượt trội hơn tất cả các biến thể khác, ngay cả với từ khóa cuối cùng. Điều này rất đáng chú ý vì về cơ bản nó là một lớp bao bọc một mảng float, trong khi các thử nghiệm khác trực tiếp thao tác với mảng đó. Phải tìm ra cái này Có thể có một cái gì đó để làm với giao diện lưu loát của ShaderInput.

Ngoài ra System.arrayCopy thực sự rõ ràng là hơi chậm đối với các mảng nhỏ hơn là chỉ sao chép các phần tử từ mảng này sang mảng khác trong một vòng lặp for. Và sử dụng sun.misc.Unsafe (cũng như java.nio.FloatBuffer trực tiếp, không được hiển thị ở đây) thực hiện rất nhiều.


1
Bạn quên làm cho các tham số cuối cùng. <pre> <code> công khai ShaderInput x (int int stride, Final float val) {input [strides [stride] + 0] = val; trả lại cái này; } </ code> </ pre> Theo kinh nghiệm của tôi, thực hiện bất kỳ biến hoặc trường cuối cùng thực sự có thể tăng hiệu suất.
Anticro

1
Ồ, và cũng làm cho những người khác cuối cùng nữa: <pre> <code> Final int mảngSize = 10; float cuối cùng [] fb = new float [mảngSize]; for (int i = 0; i <mảngSize; i ++) {fb [i] = Random.nextFloat (); } lần int cuối cùng = 1000000000; for (int i = 0; i <10; ++ i) {floatVectorTest (lần, fb); mảngCopyTest (lần, fb); shaderInputTest (lần, fb); directFloatArrayTest (lần, fb); System.out.println (); System.gc (); } </ code> </ pre>
Anticro

1

Cuối cùng (Ít nhất là cho các biến thành viên và tham số) là nhiều hơn cho con người sau đó là cho máy.

Đó là thực hành tốt để làm cho các biến cuối cùng bất cứ nơi nào có thể. Tôi ước rằng Java đã thực hiện "biến" cuối cùng theo mặc định và có từ khóa "Mutable" để cho phép thay đổi. Các lớp không thay đổi dẫn đến mã luồng tốt hơn nhiều, và chỉ cần liếc vào một lớp có "cuối cùng" trước mỗi thành viên sẽ nhanh chóng cho thấy nó là bất biến.

Một trường hợp khác - Tôi đã chuyển đổi rất nhiều mã để sử dụng các chú thích @ NonNull / @ Nullable (Bạn có thể nói một tham số phương thức không được rỗng thì IDE có thể cảnh báo bạn mọi nơi bạn chuyển một biến không được gắn thẻ @ NonNull - toàn bộ sự việc lan truyền đến một mức độ lố bịch). Việc chứng minh một biến thành viên hoặc tham số dễ dàng hơn nhiều khi nó được gắn thẻ cuối cùng vì bạn biết nó không được gán lại ở bất kỳ nơi nào khác.

Theo gợi ý của tôi là hãy tập thói quen áp dụng cuối cùng cho các thành viên và tham số theo mặc định, Đó chỉ là một vài ký tự nhưng sẽ thúc đẩy bạn cải thiện phong cách mã hóa của mình nếu không có gì khác.

Cuối cùng cho các phương thức hoặc các lớp là một khái niệm khác vì nó không cho phép một hình thức tái sử dụng rất hợp lệ và không thực sự nói với người đọc nhiều. Cách sử dụng tốt nhất có lẽ là cách họ tạo ra String và các loại nội tại khác cuối cùng để bạn có thể dựa vào hành vi nhất quán ở mọi nơi - Điều đó đã ngăn chặn rất nhiều lỗi (mặc dù đôi khi tôi sẽ YÊU THÍCH để mở rộng chuỗi .... oh khả năng)


0

Như đã đề cập ở nơi khác, 'cuối cùng' cho một biến cục bộ, và ở mức độ thấp hơn một biến thành viên, là vấn đề về phong cách.

'Final' là một tuyên bố mà bạn dự định biến không thay đổi (nghĩa là biến sẽ không thay đổi!). Trình biên dịch sau đó có thể giúp bạn bằng cách phàn nàn nếu bạn vi phạm ràng buộc của chính mình.

Tôi chia sẻ cảm nghĩ rằng Java sẽ là ngôn ngữ tốt hơn nếu các định danh (tôi xin lỗi, tôi chỉ không thể gọi một thứ không thay đổi là "biến") là cuối cùng theo mặc định và yêu cầu bạn phải nói rõ ràng rằng chúng là các biến. Nhưng có nói rằng, tôi thường không sử dụng 'cuối cùng' cho các biến cục bộ được khởi tạo và không bao giờ được gán; nó chỉ có vẻ quá ồn ào

(Tôi sử dụng cuối cùng trên các biến thành viên)


-3

Các thành viên được tuyên bố cuối cùng sẽ có mặt trong suốt chương trình vì không giống như các thành viên không chính thức, nếu các thành viên này chưa được sử dụng trong chương trình, họ vẫn không được Garbage Collector chăm sóc nên có thể gây ra vấn đề về hiệu suất do quản lý bộ nhớ kém.


2
Đây là loại vô nghĩa gì? Bạn có nguồn nào tại sao bạn nghĩ đây là trường hợp không?
J. Doe

-4

final từ khóa có thể được sử dụng theo năm cách trong Java.

  1. Một lớp học là cuối cùng
  2. Một biến tham chiếu là cuối cùng
  3. Một biến cục bộ là cuối cùng
  4. Một phương pháp là cuối cùng

Một lớp là cuối cùng: một lớp là cuối cùng có nghĩa là chúng ta không thể được gia hạn hoặc thừa kế có nghĩa là không thể kế thừa.

Tương tự - Một đối tượng là cuối cùng: đôi khi chúng ta không sửa đổi trạng thái bên trong của đối tượng, vì vậy trong trường hợp đó chúng ta có thể chỉ định đối tượng là đối tượng cuối cùng. Đối tượng cuối cùng có nghĩa là không biến cũng là cuối cùng.

Khi biến tham chiếu được thực hiện cuối cùng, nó không thể được gán lại cho đối tượng khác. Nhưng có thể thay đổi nội dung của đối tượng miễn là các trường của nó không phải là cuối cùng


2
Bởi "đối tượng là cuối cùng", ý bạn là "đối tượng là bất biến".
gyorgyabraham

6
Bạn đúng, nhưng bạn không trả lời câu hỏi. OP không hỏi finalcó nghĩa là gì , nhưng liệu sử dụng có finalảnh hưởng đến hiệu suất.
Honza Zidek
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.