Trước khi chúng tôi nhận được vào câu trả lời thực tế, một vài điều bạn nên biết:
Kết quả thử nghiệm của bạn có thể thay đổi khá mạnh mẽ, tùy thuộc vào nhiều yếu tố (ví dụ: máy tính bạn đang chạy). Dưới đây là kết quả của một lần chạy thử trên máy tính 8 lõi của tôi:
Sum of ages = 260000000 and it took : 94 ms with collectors
Sum of ages = 260000000 and it took : 61 ms with collectors and parallel stream
Sum of ages = 260000000 and it took : 70 ms with map and sum
Sum of ages = 260000000 and it took : 94 ms with map and sum and parallel stream
Và rồi trong một hoạt động sau:
Sum of ages = 260000000 and it took : 68 ms with collectors
Sum of ages = 260000000 and it took : 67 ms with collectors and parallel stream
Sum of ages = 260000000 and it took : 66 ms with map and sum
Sum of ages = 260000000 and it took : 109 ms with map and sum and parallel stream
Micro điểm chuẩn không phải là một chủ đề dễ dàng. Có những phương pháp để làm điều đó (và tôi sẽ nhận được vào một số sau này) nhưng chỉ cố gắng sử dụng System.currentTimeMillies()
sẽ không hoạt động đáng tin cậy trong hầu hết các trường hợp.
Chỉ vì Java 8 làm cho hoạt động song song dễ dàng, điều đó không có nghĩa là chúng nên được sử dụng ở mọi nơi. Hoạt động song song có ý nghĩa trong một số trường hợp và không ở trong các trường hợp khác.
OK, bây giờ chúng ta hãy xem xét các phương pháp khác nhau mà bạn đang sử dụng.
nhà sưu tập Sequential: Các nhà sưu tập summingInt
bạn sử dụng có thực hiện như sau:
public static <T> Collector<T, ?, Integer> summingInt(ToIntFunction<? super T> mapper) {
return new CollectorImpl<>(
() -> new int[1],
(a, t) -> { a[0] += mapper.applyAsInt(t); },
(a, b) -> { a[0] += b[0]; return a; },
a -> a[0], Collections.emptySet());
}
Vì vậy, trước hết là một mảng mới với một phần tử sẽ được tạo ra. Sau đó, đối với mọi phần tử Person
trong luồng của bạn, hàm collect
sẽ sử dụng hàm Person#getAge()
để truy lục tuổi là Integer
(không phải là int
!) Và thêm độ tuổi đó vào các phần trước đó (trong mảng 1D). Cuối cùng, khi toàn bộ luồng đã được xử lý, nó sẽ trích xuất giá trị từ mảng đó và trả về nó. Vì vậy, có rất nhiều auto-boxing và -unboxing đang diễn ra ở đây.
- nhà sưu tập Parallel: này sử dụng
ReferencePipeline#forEach(Consumer)
chức năng để tích lũy các lứa tuổi nó nhận được từ các chức năng lập bản đồ. Một lần nữa có rất nhiều auto-boxing và -unboxing.
Bản đồ tuần tự và tổng: Đây là bản đồ số Stream<Person>
của bạn đến số IntStream
. Một điều này có nghĩa là không có auto-boxing hoặc -unboxing được yêu cầu nữa; điều này có thể trong một số trường hợp tiết kiệm rất nhiều thời gian.Sau đó, tổng hợp luồng kết quả bằng cách triển khai sau:
@Override
public final int sum() {
return reduce(0, Integer::sum);
}
Chức năng reduce
ở đây sẽ gọi ReduceOps#ReduceOp#evaluateSequential(PipelineHelper<T> helper, Spliterator<P_IN> spliterator)
. này sẽ, trong bản chất, sử dụng Integer::sum
chức năng trên tất cả các con số của bạn, bắt đầu với 0 và số đầu tiên và sau đó là kết quả của việc đó với số thứ hai và vân vân.
- đồ Parallel và sum: Ở đây mọi thứ trở nên thú vị. Nó sử dụng cùng chức năng
sum()
, tuy nhiên, mức giảm sẽ trong trường hợp này gọi là ReduceOps#ReduceOp#evaluateParallel(PipelineHelper<T> helper, Spliterator<P_IN> spliterator)
thay vì tùy chọn tuần tự. Điều này về cơ bản sẽ sử dụng một phương pháp phân chia và chinh phục để thêm các giá trị. Bây giờ, lợi thế lớn của phân chia và chinh phục là tất nhiên, rằng nó có thể dễ dàng được thực hiện song song. Tuy nhiên, nó đòi hỏi phải tách và tái tham gia vào dòng suối nhiều lần, điều này sẽ tốn thời gian. Vì vậy, tốc độ của nó có thể thay đổi khá nhiều, tùy thuộc vào độ phức tạp của nhiệm vụ thực tế mà nó phải làm với các phần tử. Trong trường hợp thêm, nó có thể không có giá trị trong hầu hết các trường hợp; như bạn có thể thấy từ kết quả của tôi, nó luôn là một trong những phương pháp chậm hơn.
Bây giờ, để có được một ý tưởng thực sự về việc phải mất bao lâu, chúng ta hãy làm một điểm chuẩn thích hợp. Tôi sẽ sử dụng JMH với mã chuẩn sau:
package com.stackoverflow.user2352924;
import org.openjdk.jmh.annotations.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MINUTES)
@Warmup(iterations = 5, time = 5, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 10, time = 10, timeUnit = TimeUnit.SECONDS)
@State(Scope.Benchmark)
@Fork(1)
@Threads(2)
public class MicroBenchmark {
private static List<Person> persons = new ArrayList<>();
private int test;
static {
for(int i=0; i < 10000000; i++){
persons.add(new Person("random", 26));
}
}
@Benchmark
public void sequentialCollectors() {
test = 0;
test += persons.stream().collect(Collectors.summingInt(p -> p.getAge()));
}
@Benchmark
public void parallelCollectors() {
test = 0;
test += persons.parallelStream().collect(Collectors.summingInt(p -> p.getAge()));
}
@Benchmark
public void sequentialMapSum() {
test = 0;
test += persons.stream().mapToInt(p -> p.getAge()).sum();
}
@Benchmark
public void parallelMapSum() {
test = 0;
test += persons.parallelStream().mapToInt(p -> p.getAge()).sum();
}
}
Các pom.xml
cho dự án maven này trông như thế này:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.stackoverflow.user2352924</groupId>
<artifactId>StackOverflow</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<name>Auto-generated JMH benchmark</name>
<prerequisites>
<maven>3.0</maven>
</prerequisites>
<dependencies>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>${jmh.version}</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>${jmh.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jmh.version>0.9.5</jmh.version>
<javac.target>1.8</javac.target>
<uberjar.name>benchmarks</uberjar.name>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<compilerVersion>${javac.target}</compilerVersion>
<source>${javac.target}</source>
<target>${javac.target}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.2</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<finalName>microbenchmarks</finalName>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.openjdk.jmh.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>2.5</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.1</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9.1</version>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>2.6</version>
</plugin>
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.3</version>
</plugin>
<plugin>
<artifactId>maven-source-plugin</artifactId>
<version>2.2.1</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.17</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
Hãy chắc chắn rằng Maven đang chạy với Java 8 quá, nếu không bạn' sẽ gặp lỗi xấu.
tôi sẽ không đi vào chi tiết về cách sử dụng JMH đây (có những nơi khác mà làm điều đó) nhưng đây là kết quả tôi nhận:
# Run complete. Total time: 00:08:48
Benchmark Mode Samples Score Score error Units
c.s.u.MicroBenchmark.parallelCollectors thrpt 10 3658,949 775,115 ops/min
c.s.u.MicroBenchmark.parallelMapSum thrpt 10 2616,905 221,109 ops/min
c.s.u.MicroBenchmark.sequentialCollectors thrpt 10 5502,160 439,024 ops/min
c.s.u.MicroBenchmark.sequentialMapSum thrpt 10 6120,162 609,232 ops/min
Vì vậy, trên hệ thống của tôi vào thời điểm đó tôi chạy các bài kiểm tra đó, tổng số bản đồ tuần tự nhanh hơn đáng kể, quản lý để thực hiện trên 6100 hoạt động trong thời gian mà tổng bản đồ song song (sử dụng phương pháp phân chia và chinh phục) chỉ hoạt động hơn 2600. Thực tế, các phương pháp tuần tự đều đáng kể nhanh hơn so với những cái song song.
Bây giờ, trong một tình huống mà có thể dễ dàng được chạy song song - ví dụ trong đó hàm Person#getAge()
phức tạp hơn nhiều so với chỉ là một trình thu thập dữ liệu - các phương thức song song cũng có thể là một giải pháp tốt hơn nhiều. Cuối cùng, tất cả phụ thuộc vào hiệu quả của các hoạt động song song trong trường hợp được kiểm tra.
Một điều cần nhớ: nếu nghi ngờ, làm một chuẩn mực vi thích hợp. ;-)
Hãy cẩn thận với _Micro Benchmarks_ trong Java. Xem ví dụ: http://stackoverflow.com/questions/504103/how-do-i-write-a-correct-micro-benchmark-in-java – nosid
parallelStream phụ thuộc vào số lượng CPU. Và luôn luôn ở cuối nó phải hợp nhất các kết quả - trong chuỗi đơn. –