Performance Rxjava2、Java8流和普通旧迭代之间的性能比较
我已经成为Java8和RXJava中java函数编程的忠实粉丝。但一位同事最近指出,使用这些工具会影响性能。所以决定进行JMH基准测试,但似乎他是对的。无论我做什么,我都无法让streams版本提供更好的性能。下面是我的代码Performance Rxjava2、Java8流和普通旧迭代之间的性能比较,performance,java-8,java-stream,rx-java2,Performance,Java 8,Java Stream,Rx Java2,我已经成为Java8和RXJava中java函数编程的忠实粉丝。但一位同事最近指出,使用这些工具会影响性能。所以决定进行JMH基准测试,但似乎他是对的。无论我做什么,我都无法让streams版本提供更好的性能。下面是我的代码 @OutputTimeUnit(TimeUnit.NANOSECONDS) @BenchmarkMode(Mode.AverageTime) @OperationsPerInvocation(StreamVsVanilla.N) public class StreamVsV
@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();
}
}
即使我删除parellal运算符并使用如下顺序版本:
@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();
}
}
有人能帮我找出我做错了什么吗
编辑:
akarnokd指出,在顺序转换过程中,我在流版本中使用额外的stage来取消装箱和装箱(我添加它是为了避免过滤器和映射方法中的隐式装箱取消装箱),但是速度变慢了,所以我尝试不使用下面的代码
@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();
}
}
对于并行版本
启动并向多个线程分派值的成本相对较高。为了抵消这一点,并行计算的成本通常是基础设施开销的几倍。然而,在RxJava中,Math::sqrt非常简单,并行开销决定了性能
那么,为什么流速度要快两个数量级呢?我只能假设线程窃取发生在基准线程完成大部分实际工作的地方,可能一个后台线程完成了一些剩余的工作,因为当后台线程启动时,主线程已经窃取了大部分任务。因此,这里没有严格的并行执行,就像RxJava的并行一样,操作员以循环方式分派工作,这样所有并行rails都可以大致相同地忙碌
对于顺序版本
我认为在流版本中有额外的拆箱和装箱阶段这一事实增加了一点开销。尝试不使用它:
return sourceList.stream()
.filter(i -> i % 2 == 0)
.map(Math::sqrt)
.collect(Collectors.toList());
你对平行版本的解释听起来很合理,事实上我也这么想。但我尝试并行只是因为我想证明使用流是合理的。我主要关心的是,除了语法上的好处外,使用streams或RXJava比普通迭代有什么优势吗?事实证明,从性能角度看,情况更糟。另外,移除装箱拆箱确实会稍微改进它,但仍然比迭代版本慢两倍。我在filter和map中添加了装箱拆箱,以避免隐式装箱拆箱(我认为这会提高性能)。RxJava被设计为同时使用同步和异步模式,因为阶段可以是它们中的任何一个,然后混合在一起。我花了大量时间优化同步行为,以便同步开销尽可能接近流。然而,如果您有Java8流(在大多数Android上不是本机提供的),或者只是简单地使用传统的for循环,则通常可以避免以纯同步方式使用RxJava?我发现Try有时比Java8流的开销要小。流,甚至是顺序流,都比普通循环慢得多。如果你需要性能,不要使用它们。然而,它们增加了(如果使用得当的话)易维护性、代码可读性等,对于当今大多数用例来说,这远比性能更重要。
@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
return sourceList.stream()
.filter(i -> i % 2 == 0)
.map(Math::sqrt)
.collect(Collectors.toList());