gpt4 book ai didi

Rxjava2、Java 8 Streams、Plain Old Iteration 之间的性能比较

转载 作者:行者123 更新时间:2023-12-04 19:28:45 26 4
gpt4 key购买 nike

我已经成为 Java 8 和 Rx java 中的函数式编程的忠实粉丝。但最近一位同事指出,使用这些会影响性能。所以决定运行 JMH 基准标记,但似乎他是对的。无论我做什么,我都无法获得流版本来提供更好的性能。下面是我的代码

@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
@OperationsPerInvocation(StreamVsVanilla.N)
public class StreamVsVanilla {
public static final int N = 10000;

static List<Integer> sourceList = new ArrayList<>(N);
static {
for (int i = 0; i < N; i++) {
sourceList.add(i);
}
}

@Benchmark
public List<Double> vanilla() {
List<Double> result = new ArrayList<Double>(sourceList.size() / 2 + 1);
for (Integer i : sourceList) {
if (i % 2 == 0){
result.add(Math.sqrt(i));
}
}
return result;
}

@Benchmark
public List<Double> stream() {
return sourceList.stream().parallel()
.mapToInt(Integer::intValue)
.filter(i -> i % 2 == 0)
.mapToDouble(i->(double)i)
.map(Math::sqrt)
.boxed()
.collect(Collectors.toList());
}

@Benchmark
public List<Double> rxjava2(){
return Flowable.fromIterable(sourceList)
.parallel()
.runOn(Schedulers.computation())
.filter(i->i%2==0)
.map(Math::sqrt)
.collect(()->new ArrayList<Double>(sourceList.size()/2+1),ArrayList::add)
.sequential()
.blockingFirst();

}

public static void main(String[] args) throws RunnerException {

Options options = new OptionsBuilder()
.include(StreamVsVanilla.class.getSimpleName()).threads(1)
.forks(1).shouldFailOnError(true).shouldDoGC(true)
.jvmArgs("-server").build();
new Runner(options).run();

}
}

上述代码的结果:
# Run complete. Total time: 00:03:16

Benchmark Mode Cnt Score Error Units
StreamVsVanilla.rxjava2 avgt 20 1179.733 ± 322.421 ns/op
StreamVsVanilla.stream avgt 20 10.556 ± 1.195 ns/op
StreamVsVanilla.vanilla avgt 20 8.220 ± 0.705 ns/op

即使我删除并行运算符并使用如下顺序版本:
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
@OperationsPerInvocation(StreamVsVanilla.N)
public class StreamVsVanilla {
public static final int N = 10000;

static List<Integer> sourceList = new ArrayList<>(N);
static {
for (int i = 0; i < N; i++) {
sourceList.add(i);
}
}

@Benchmark
public List<Double> vanilla() {
List<Double> result = new ArrayList<Double>(sourceList.size() / 2 + 1);
for (Integer i : sourceList) {
if (i % 2 == 0){
result.add(Math.sqrt(i));
}
}
return result;
}

@Benchmark
public List<Double> stream() {
return sourceList.stream()
.mapToInt(Integer::intValue)
.filter(i -> i % 2 == 0)
.mapToDouble(i->(double)i)
.map(Math::sqrt)
.boxed()
.collect(Collectors.toList());
}

@Benchmark
public List<Double> rxjava2(){
return Observable.fromIterable(sourceList)
.filter(i->i%2==0)
.map(Math::sqrt)
.collect(()->new ArrayList<Double>(sourceList.size()/2+1),ArrayList::add)
.blockingGet();

}

public static void main(String[] args) throws RunnerException {

Options options = new OptionsBuilder()
.include(StreamVsVanilla.class.getSimpleName()).threads(1)
.forks(1).shouldFailOnError(true).shouldDoGC(true)
.jvmArgs("-server").build();
new Runner(options).run();

}
}

结果不是很理想:
# Run complete. Total time: 00:03:16

Benchmark Mode Cnt Score Error Units
StreamVsVanilla.rxjava2 avgt 20 12.226 ± 0.603 ns/op
StreamVsVanilla.stream avgt 20 13.432 ± 0.858 ns/op
StreamVsVanilla.vanilla avgt 20 7.678 ± 0.350 ns/op

有人可以帮我弄清楚我做错了什么吗?

编辑:

akarnokd 指出我正在使用额外的阶段在顺序版本期间在我的流版本中拆箱和装箱(我添加它是为了避免过滤器和映射方法中的隐式装箱拆箱),但是它变慢了,所以我尝试不使用下面的代码
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
@OperationsPerInvocation(StreamVsVanilla.N)
public class StreamVsVanilla {
public static final int N = 10000;

static List<Integer> sourceList = new ArrayList<>(N);
static {
for (int i = 0; i < N; i++) {
sourceList.add(i);
}
}

@Benchmark
public List<Double> vanilla() {
List<Double> result = new ArrayList<Double>(sourceList.size() / 2 + 1);
for (Integer i : sourceList) {
if (i % 2 == 0){
result.add(Math.sqrt(i));
}
}
return result;
}

@Benchmark
public List<Double> stream() {
return sourceList.stream()
.filter(i -> i % 2 == 0)
.map(Math::sqrt)
.collect(Collectors.toList());
}

@Benchmark
public List<Double> rxjava2(){
return Observable.fromIterable(sourceList)
.filter(i->i%2==0)
.map(Math::sqrt)
.collect(()->new ArrayList<Double>(sourceList.size()/2+1),ArrayList::add)
.blockingGet();

}

public static void main(String[] args) throws RunnerException {

Options options = new OptionsBuilder()
.include(StreamVsVanilla.class.getSimpleName()).threads(1)
.forks(1).shouldFailOnError(true).shouldDoGC(true)
.jvmArgs("-server").build();
new Runner(options).run();

}
}

结果仍然或多或少相同:
# Run complete. Total time: 00:03:16

Benchmark Mode Cnt Score Error Units
StreamVsVanilla.rxjava2 avgt 20 10.864 ± 0.555 ns/op
StreamVsVanilla.stream avgt 20 10.466 ± 0.050 ns/op
StreamVsVanilla.vanilla avgt 20 7.513 ± 0.136 ns/op

最佳答案

对于并行版本

向多个线程启动和分派(dispatch)值相对昂贵。为了抵消这一点,并行计算的成本通常是基础设施开销的几倍。但是,对于您在 RxJava 中的情况,Math::sqrt 是如此微不足道,并行开销支配了性能。

那为什么 Stream 速度要快两个数量级呢?我只能假设线程窃取出现在基准线程完成大部分实际工作的地方,也许一个后台线程完成了少量其余工作,因为当后台线程启动时,主线程已经窃取了大部分任务背部。因此,您没有像 RxJava 的并行那样严格的并行执行,其中运算符(operator)调度以循环方式工作,因此所有并行轨道可以大致相等地变得繁忙。

对于顺序版本

我认为您在 Stream 版本中有额外的拆箱和装箱阶段这一事实增加了一点开销。尝试不使用它:

   return  sourceList.stream()
.filter(i -> i % 2 == 0)
.map(Math::sqrt)
.collect(Collectors.toList());

关于Rxjava2、Java 8 Streams、Plain Old Iteration 之间的性能比较,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48032728/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com