2014-10-28 9 views
12

IDEA gợi ý để thay thế, ví dụ, điều này:IntelliJ IDEA đề xuất thay thế cho các vòng lặp bằng phương pháp foreach. Tôi có nên luôn làm điều đó khi có thể không?

for (Point2D vertex : graph.vertexSet()) { 
    union.addVertex(vertex); 
} 

với điều này:

graph.vertexSet().forEach(union::addVertex); 

Phiên bản mới này chắc chắn nhiều hơn nữa có thể đọc được. Nhưng có những tình huống nào khi tôi nên gắn bó với cấu trúc ngôn ngữ cũ tốt cho các iteratables thay vì sử dụng phương thức foreach mới? Ví dụ, nếu tôi hiểu chính xác, cơ chế tham chiếu phương thức ngụ ý việc xây dựng một đối tượng vô danh Consumer nếu không (với cấu trúc ngôn ngữ for) sẽ không được xây dựng. Điều đó có thể trở thành một nút cổ chai hiệu suất đối với một số hành động không?

Vì vậy, tôi đã viết chuẩn không phải là rất đầy đủ này:

package org.sample; 

import org.openjdk.jmh.annotations.Benchmark; 
import org.openjdk.jmh.annotations.Fork; 
import org.openjdk.jmh.annotations.Threads; 
import org.openjdk.jmh.infra.Blackhole; 
import org.tendiwa.geometry.Point2D; 

import java.util.ArrayList; 
import java.util.List; 
import java.util.stream.Collectors; 
import java.util.stream.IntStream; 

public class LanguageConstructVsForeach { 
    private static final int NUMBER_OF_POINTS = 10000; 
    private static final List<Point2D> points = IntStream 
     .range(0, NUMBER_OF_POINTS) 
     .mapToObj(i -> new Point2D(i, i * 2)) 
     .collect(Collectors.toList()); 

    @Benchmark 
    @Threads(1) 
    @Fork(3) 
    public void languageConstructToBlackhole(Blackhole bh) { 
     for (Point2D point : points) { 
      bh.consume(point); 
     } 
    } 
    @Benchmark 
    @Threads(1) 
    @Fork(3) 
    public void foreachToBlackhole(Blackhole bh) { 
     points.forEach(bh::consume); 
    } 
    @Benchmark 
    @Threads(1) 
    @Fork(3) 
    public List<Point2D> languageConstructToList(Blackhole bh) { 
     List<Point2D> list = new ArrayList<>(NUMBER_OF_POINTS); 
     for (Point2D point : points) { 
      list.add(point); 
     } 
     return list; 
    } 
    @Benchmark 
    @Threads(1) 
    @Fork(3) 
    public List<Point2D> foreachToList(Blackhole bh) { 
     List<Point2D> list = new ArrayList<>(NUMBER_OF_POINTS); 
     points.forEach(list::add); 
     return list; 
    } 

} 

Và có:

Benchmark              Mode Samples  Score  Error Units 
o.s.LanguageConstructVsForeach.foreachToBlackhole    thrpt  60 33693.834 ± 894.138 ops/s 
o.s.LanguageConstructVsForeach.foreachToList     thrpt  60 7753.941 ± 239.081 ops/s 
o.s.LanguageConstructVsForeach.languageConstructToBlackhole thrpt  60 16043.548 ± 644.432 ops/s 
o.s.LanguageConstructVsForeach.languageConstructToList   thrpt  60 6499.527 ± 202.589 ops/s 

Làm thế nào đến foreach là hiệu quả hơn trong cả hai trường hợp: khi tôi làm hầu như không có gì và khi tôi làm một số công việc thực tế? Không foreach chỉ cần đóng gói Iterator? Điểm chuẩn này có đúng không? Nếu có, hôm nay có lý do gì để sử dụng cấu trúc ngôn ngữ cũ với Java 8 không?

+2

Các câu hỏi liên quan: http://stackoverflow.com/q/23265855/1441122 và http://stackoverflow.com/q/16635398/1441122 –

+0

microbenchmarks chỉ chính xác và hữu ích như người sáng tạo của họ chuyên môn trong việc tạo ra chúng. microbenchmarks thường chơi trực tiếp với hiệu ứng Dunning-Kruger với kết quả mong đợi. –

+1

@JarrodRoberson Nhưng cái này đang sử dụng JMH, trong đó có một cách tiếp cận tốt và các công cụ cho benchamrking. Không có gì trong tiêu chuẩn này đến từ chuyên môn của tôi và không phải từ hướng dẫn sử dụng cho JMH. – gvlasov

Trả lời

15

Bạn đang so sánh vòng lặp "nâng cao cho" của ngôn ngữ với phương pháp Iterable.forEach(). Điểm chuẩn không rõ ràng là sai, và kết quả có vẻ đáng ngạc nhiên, cho đến khi bạn đào sâu vào các triển khai.

Lưu ý rằng danh sách points là phiên bản ArrayList vì đó là những gì được tạo bởi bộ sưu tập Collectors.toList().

Việc tăng cường-cho vòng lặp trên một Iterable được một Iterator từ nó và sau đó gọi hasNext()next() tục cho đến khi không có nhiều yếu tố. (Điều này khác với vòng lặp tăng cường cho một mảng, truy cập phần tử số học và mảng trực tiếp.) Vì vậy, khi lặp qua một Iterable, vòng lặp này sẽ thực hiện tối thiểu hai cuộc gọi phương thức cho mỗi lần lặp.

Ngược lại, gọi ArrayList.forEach() chạy vòng lặp thông thường, int-based trên mảng chứa các phần tử danh sách và gọi lambda một lần cho mỗi lần lặp. Chỉ có một cuộc gọi cho mỗi lần lặp lại ở đây, thay vì hai cuộc gọi trên mỗi lần lặp cho vòng lặp nâng cao. Điều đó có thể giải thích tại sao ArrayList.forEach() nhanh hơn trong trường hợp này.

Trường hợp lỗ đen dường như hoạt động rất ít ngoài việc chạy vòng lặp, vì vậy những trường hợp này dường như đang đo chi phí vòng lặp thuần túy. Đó có thể là lý do tại sao ArrayList.forEach() cho thấy một lợi thế lớn như vậy ở đây.

Khi vòng lặp chỉ thực hiện một chút công việc (thêm vào danh sách đích), vẫn có lợi thế về tốc độ cho ArrayList.forEach(), nhưng đó là sự khác biệt nhỏ hơn nhiều. Tôi nghi ngờ rằng nếu bạn phải làm nhiều công việc bên trong vòng lặp, lợi thế sẽ nhỏ hơn. Điều này cho thấy rằng chi phí vòng lặp cho một trong hai cấu trúc là rất nhỏ. Thử sử dụng BlackHole.consumeCPU() trong vòng lặp. Tôi sẽ không ngạc nhiên nếu kết quả giữa hai cấu trúc trở nên không thể phân biệt được.

Lưu ý rằng lợi thế tốc độ lớn xảy ra vì Iterable.forEach() kết thúc bằng việc triển khai chuyên biệt trong ArrayList.forEach(). Nếu bạn chạy forEach() qua cấu trúc dữ liệu khác, bạn có thể nhận được các kết quả khác nhau.

Tôi sẽ không sử dụng điều này làm lý do để thay thế một cách mù quáng tất cả các vòng nâng cao cho các cuộc gọi đến Iterable.forEach(). Viết mã rõ ràng nhất và có ý nghĩa nhất. Nếu bạn đang viết mã quan trọng hiệu suất, chuẩn nó! Các biểu mẫu khác nhau sẽ có hiệu suất khác nhau, tùy thuộc vào khối lượng công việc, cấu trúc dữ liệu nào đang được duyệt qua, v.v.

2

Một lý do rõ ràng để sử dụng kiểu cũ hơn là bạn sẽ tương thích với Java 7. Nếu mã của bạn sử dụng rất nhiều tính năng mới Các tính năng của Java 8 không phải là một lựa chọn, nhưng nếu bạn chỉ sử dụng một vài tính năng mới, đây có thể là một lợi thế, đặc biệt nếu mã của bạn nằm trong một thư viện đa năng.

Các vấn đề liên quan