java một cuộc gọi phương thức đắt như thế nào


81

Tôi là người mới bắt đầu và tôi luôn đọc rằng thật tệ khi lặp lại mã. Tuy nhiên, có vẻ như để không làm như vậy, bạn sẽ phải có thêm các cuộc gọi phương thức. Giả sử tôi có lớp học sau

public class BinarySearchTree<E extends Comparable<E>>{
    private BinaryTree<E> root;
    private final BinaryTree<E> EMPTY = new BinaryTree<E>();
    private int count;
    private Comparator<E> ordering;

    public BinarySearchTree(Comparator<E> order){
        ordering = order;
        clear();
    }

    public void clear(){
        root = EMPTY;
        count = 0;
    }
}

Sẽ tối ưu hơn cho tôi nếu chỉ sao chép và dán hai dòng trong phương thức clear () của tôi vào hàm tạo thay vì gọi phương thức thực? Nếu có thì nó tạo ra sự khác biệt bao nhiêu? Điều gì sẽ xảy ra nếu hàm tạo của tôi thực hiện 10 lệnh gọi phương thức với mỗi lệnh chỉ đơn giản đặt một biến thể hiện thành một giá trị? Thực hành lập trình tốt nhất là gì?


4
Nhưng chờ đã, nếu bạn gọi phương thức ngay bây giờ, chúng tôi sẽ thực hiện một cuộc gọi phương thức THỨ HAI, hoàn toàn miễn phí! Chỉ trả tiền vận chuyển và xử lý! Nghiêm túc đấy. Có chi phí cao trong các cuộc gọi phương thức, cũng như có chi phí để tải nhiều mã hơn. Tại một số thời điểm, một cái trở nên đắt hơn cái kia. Cách duy nhất để nhận biết là đo điểm chuẩn cho mã của bạn.
Marc B

11
dấu ngoặc kép tối ưu hóa quá sớm trong 3 ... 2 ... 1

21
Tôi không thấy lý do cho sự phản đối về điều này - anh chàng đang hỏi một câu hỏi hoàn toàn chính đáng. Nó có thể là một câu trả lời rõ ràng cho một số người nhưng điều đó không làm cho nó trở thành một câu hỏi tồi!
Michael Berry

3
Thật vậy, đó là một câu hỏi rất chính đáng, lý do duy nhất cho một cuộc bỏ phiếu xuống có thể là nếu có một bản sao chính xác.
Arafangion

1
yea xin lỗi nếu đó là điều hiển nhiên, nhưng tôi đang tự học và tôi chỉ mới học được vài tháng. Một số điều tôi đã thấy trong mã mẫu trực tuyến, tôi thấy không phải là thông lệ tốt nên tôi chỉ muốn kiểm tra lại.
jhlu87

Câu trả lời:


74

Sẽ tối ưu hơn cho tôi nếu chỉ sao chép và dán hai dòng trong phương thức clear () của tôi vào hàm tạo thay vì gọi phương thức thực?

Trình biên dịch có thể thực hiện tối ưu hóa đó. Và JVM cũng vậy. Thuật ngữ được sử dụng bởi người viết trình biên dịch và các tác giả JVM là "mở rộng nội tuyến".

Nếu có thì nó tạo ra sự khác biệt bao nhiêu?

Đo lường nó. Thông thường, bạn sẽ thấy rằng nó không có gì khác biệt. Và nếu bạn tin rằng đây là một điểm phát sóng hiệu suất, thì bạn đang tìm nhầm chỗ; đó là lý do tại sao bạn sẽ cần phải đo lường nó.

Điều gì sẽ xảy ra nếu hàm tạo của tôi thực hiện 10 lệnh gọi phương thức với mỗi lệnh chỉ đơn giản đặt một biến thể hiện thành một giá trị?

Một lần nữa, điều đó phụ thuộc vào mã bytecode được tạo và bất kỳ tối ưu hóa thời gian chạy nào được thực hiện bởi máy ảo Java. Nếu trình biên dịch / JVM có thể nội tuyến các cuộc gọi phương thức, nó sẽ thực hiện tối ưu hóa để tránh phí tạo khung ngăn xếp mới trong thời gian chạy.

Thực hành lập trình tốt nhất là gì?

Tránh tối ưu hóa quá sớm. Cách tốt nhất là viết mã có thể đọc được và được thiết kế tốt, sau đó tối ưu hóa cho các điểm phát hiệu suất trong ứng dụng của bạn.


cách tốt để đánh giá điểm chuẩn là gì? có một số phần mềm tôi có thể tải xuống hay ý bạn là chỉ cần sử dụng System.nanoTime () ở đầu và cuối và in ra sự khác biệt?
jhlu87

System.nanoTime()hoặc System.currentTimeMillislà một cách làm hồ sơ kém. Bạn có thể nhận danh sách những người làm hồ sơ từ câu trả lời tại câu hỏi Stackoverflow này . Tôi muốn giới thiệu VisualVM, vì nó đi kèm với JDK ngay bây giờ.
Vineet Reynolds

2
@ jhlu87: Tôi nghĩ rằng bạn sẽ rất khó để có được ước tính chính xác về chi phí của một lệnh gọi phương thức. Microbenchmarking là rất khó để làm cho đúng và thậm chí sau đó nói chung là không hữu ích lắm trong kế hoạch lớn của mọi thứ. Đọc cái này .
ColinD

@VineetReynolds câu hỏi được liên kết đã chết.
dim8

19

Những gì mọi người khác đã nói về tối ưu hóa là hoàn toàn đúng.

Không có lý do gì từ quan điểm hiệu suất để nội dòng phương pháp. Nếu đó là một vấn đề về hiệu suất, JIT trong JVM của bạn sẽ nội tuyến nó. Trong java, các cuộc gọi phương thức gần như miễn phí nên không đáng để suy nghĩ về nó.

Điều đó đang được nói, có một vấn đề khác ở đây. Cụ thể, nó thực hành lập trình xấu để gọi một phương pháp ghi đè (ví dụ, một trong đó là không final, statichoặcprivate ) từ các nhà xây dựng. (Java hiệu quả, Lần xuất bản thứ 2, trang 89 trong mục có tiêu đề "Thiết kế và tài liệu để kế thừa hoặc người khác cấm nó")

Điều gì sẽ xảy ra nếu ai đó thêm một lớp con của BinarySearchTreeđược gọi LoggingBinarySearchTreeghi đè tất cả các phương thức công khai với mã như:

public void clear(){
  this.callLog.addCall("clear");
  super.clear();
}

Vậy thì LoggingBinarySearchTreeý chí không bao giờ có thể xây dựng được! Vấn đề là this.callLogsẽ nullkhi BinarySearchTreenhà xây dựng đang chạy, nhưng clearmà được gọi là một ghi đè, và bạn sẽ nhận được một NullPointerException.

Lưu ý rằng Java và C ++ khác nhau ở đây: trong C ++, một hàm tạo siêu lớp gọi hàm virtual phương thức sẽ kết thúc gọi phương thức được xác định trong lớp cha, không phải phương thức bị ghi đè. Những người chuyển đổi giữa hai ngôn ngữ đôi khi quên điều này.

Do đó, tôi nghĩ rằng nó có thể rõ ràng hơn trong trường hợp của bạn khi nội tuyến clearphương thức khi được gọi từ phương thức khởi tạo , nhưng nói chung trong Java, bạn nên tiếp tục và thực hiện tất cả các lệnh gọi phương thức bạn muốn.


1
Tôi không nghĩ rằng ông đã yêu cầu mã hóa lời khuyên phong cách mà là biết nếu một phương pháp gọi là tốn kém hay không
Asaf Mesika

3
Anh ấy hỏi "Phương pháp lập trình tốt nhất là gì?" - như một vấn đề của các phương pháp hay nhất, điều này hoàn toàn phù hợp.
Daniel Martin

2
Nếu bạn chỉ lấy câu cuối cùng, bạn hoàn toàn mất ngữ cảnh của câu hỏi này. Anh ta muốn biết liệu có tốn kém khi chia nhỏ phương thức lớn của bạn thành nhiều phương thức nhỏ hơn hay không, bởi vì một cuộc gọi phương thức có một cái giá. Việc thêm một câu trả lời mô tả một mẫu phản để gọi một phương thức không phải là phương thức cuối cùng từ một hàm tạo không được coi là câu trả lời cho toàn bộ câu hỏi của anh ta. Oh và kiểm tra các tiêu đề của câu hỏi "làm thế nào đắt tiền là một phương pháp gọi"
Asaf Mesika

6

Tôi chắc chắn sẽ để nó như vậy. Nếu bạn thay đổi clear()logic thì sao? Sẽ không thực tế nếu tìm thấy tất cả những nơi bạn đã sao chép 2 dòng mã.


4

Nói chung (và với tư cách là người mới bắt đầu, điều này có nghĩa là luôn luôn!), Bạn không bao giờ nên đưa ra những tối ưu hóa vi mô như cách mà bạn đang xem xét. Luôn ưu tiên khả năng đọc của mã hơn những thứ như thế này.

Tại sao? Bởi vì trình biên dịch / điểm phát sóng sẽ tạo ra các loại tối ưu hóa này cho bạn một cách nhanh chóng, và nhiều hơn nữa. Nếu có bất cứ điều gì, khi bạn cố gắng và tối ưu hóa theo những dòng này (mặc dù không phải trong trường hợp này), bạn có thể sẽ làm mọi thứ chậm hơn. Hotspot hiểu các thành ngữ lập trình phổ biến, nếu bạn tự mình cố gắng tối ưu hóa nó có thể sẽ không hiểu bạn đang cố gắng làm gì nên sẽ không thể tối ưu hóa được.

Ngoài ra còn có chi phí bảo trì lớn hơn nhiều. Nếu bạn bắt đầu lặp lại mã thì bạn sẽ phải nỗ lực nhiều hơn để duy trì, điều này có thể sẽ phức tạp hơn bạn tưởng rất nhiều!

Ngoài ra, bạn có thể đạt đến một số điểm trong cuộc sống mã hóa của mình mà bạn cần phải thực hiện tối ưu hóa ở mức độ thấp - nhưng nếu bạn đạt được những điểm đó, bạn chắc chắn, chắc chắn biết khi nào là thời điểm đến. Và nếu không, bạn luôn có thể quay lại và tối ưu hóa sau nếu cần.


3

Cách tốt nhất là đo hai lần và cắt một lần.

Một khi bạn đã lãng phí thời gian tối ưu hóa, bạn không bao giờ có thể lấy lại được nữa! (Vì vậy, hãy đo lường nó trước và tự hỏi bản thân xem nó có đáng để tối ưu hóa không. Bạn sẽ tiết kiệm được bao nhiêu thời gian thực tế?)

Trong trường hợp này, máy ảo Java có thể đã thực hiện tối ưu hóa mà bạn đang đề cập.


3

Các chi phí của một cuộc gọi phương pháp là việc tạo ra (và xử lý) của một stack frame và một số biểu thức mã byte thêm nếu bạn cần phải vượt qua giá trị cho phương pháp này.


1

Mô hình mà tôi tuân theo, là liệu phương pháp được đề cập này có đáp ứng một trong những điều sau đây hay không:

  • Sẽ hữu ích nếu có phương pháp này bên ngoài lớp học này?
  • Sẽ hữu ích nếu có phương pháp này trong các phương pháp khác?
  • Có bực bội khi viết lại điều này mỗi khi tôi cần không?
  • Có thể tăng tính linh hoạt của phương pháp khi sử dụng một vài tham số không?

Nếu bất kỳ điều nào ở trên là đúng, nó nên được gói gọn trong phương pháp riêng của nó.


Sẽ dễ dàng hơn khi không hỏi những câu hỏi này và chỉ cần đặt mã darn vào phương thức riêng của nó rồi!
Arafangion

2
Người hỏi tò mò về mức độ chi tiết của các lệnh gọi phương thức của mình. Không cần để tạo ra một phương pháp để tăng một số nguyên nếu bạn chỉ có thể sử dụngi++;
Peaches491

Trên thực tế, có rất nhiều giá trị trong việc tạo ra một phương thức ngay cả khi không có cơ hội để nó được sử dụng lại. Chỉ cần đặt tên cho một khối mã và làm cho cấu trúc tổng thể xuất hiện đã là một lợi ích lớn.
Joffrey

1

Giữ clear()phương pháp khi nó giúp dễ đọc. Có mã không thể xác nhận được thì đắt hơn.


1

Việc tối ưu hóa các trình biên dịch thường thực hiện khá tốt việc loại bỏ phần dư thừa từ các hoạt động "bổ sung" này; trong nhiều trường hợp, sự khác biệt giữa mã "được tối ưu hóa" và mã được viết đơn giản theo cách bạn muốn và chạy qua trình biên dịch tối ưu hóa là không có; có nghĩa là, trình biên dịch tối ưu hóa thường thực hiện tốt công việc như bạn làm và nó thực hiện mà không gây ra bất kỳ sự xuống cấp nào của mã nguồn. Trên thực tế, nhiều lần, mã "được tối ưu hóa bằng tay" có hiệu quả ÍT NHẤT, bởi vì trình biên dịch xem xét nhiều thứ khi thực hiện tối ưu hóa. Để mã của bạn ở định dạng có thể đọc được và đừng lo lắng về việc tối ưu hóa cho đến một thời gian sau.

"Tối ưu hóa sớm là gốc rễ của mọi điều xấu xa." - Donald Knuth


0

Tôi sẽ không lo lắng về cuộc gọi phương thức nhiều nhưng logic của phương thức. Nếu đó là các hệ thống quan trọng và hệ thống cần "nhanh" thì tôi sẽ xem xét việc tối ưu hóa các mã mất nhiều thời gian để thực thi.


0

Với bộ nhớ của máy tính hiện đại, điều này rất rẻ. Tốt hơn hết là chia nhỏ mã của bạn thành các phương thức để ai đó có thể nhanh chóng đọc được những gì đang diễn ra. Nó cũng sẽ giúp thu hẹp lỗi trong mã nếu lỗi được giới hạn trong một phương thức duy nhất với nội dung là một vài dòng.


0

Như những người khác đã nói, chi phí của cuộc gọi phương thức là nhỏ so với nada, vì trình biên dịch sẽ tối ưu hóa nó cho bạn.

Điều đó nói rằng, có những nguy hiểm khi thực hiện các cuộc gọi phương thức đến các phương thức thể hiện từ một phương thức khởi tạo. Bạn gặp rủi ro khi cập nhật phương thức phiên bản sau này để nó có thể cố gắng sử dụng một biến cá thể chưa được khởi tạo bởi phương thức khởi tạo. Có nghĩa là, bạn không nhất thiết muốn tách các hoạt động xây dựng ra khỏi trình xây dựng.

Một câu hỏi khác - phương thức clear () của bạn đặt gốc thành EMPTY, được khởi tạo khi đối tượng được tạo. Sau đó, nếu bạn thêm các nút vào EMPTY, rồi gọi clear (), bạn sẽ không đặt lại nút gốc. Đây có phải là hành vi bạn muốn?

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.