JDK8 LocalDate.toEpochDay的奇怪性能下降 [英] Strange performance drop of JDK8 LocalDate.toEpochDay

查看:1410
本文介绍了JDK8 LocalDate.toEpochDay的奇怪性能下降的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我很好奇,如果我们终于得到一个快速的datetime库与JDK8。几乎所有的 LocalDate 计算使用 toEpochDay ,所以我看了是非常简单的,只是迭代一个随机的数组 LocalDate ,并将其中的每个转换为EpochDay 。尽管随机性,结果是非常一致的。数组的大小是一个参数,而我的主要问题是2000年和30000年之间的巨大减速来自。应该有一些减速,因为数据不再适合L1高速缓存,但是这两种算法的内存访问是完全相同的(也就是说,没有一个从...获取日期数组)。



仍然是开放的问题是:如何在迭代过程中同一功能的两个简单的无内存访问实现的行为发生变化一个数组?原始算法比我的减慢了一倍。



我的算法可能不值得在这里复制,它是无证的,和作为原始的隐藏,只有一个非常基本的测试

解决方案

我没有直接抓住原因,但它肯定是一个基准框架的缺点。与GC和每个调用成本有关的内容。我与JMH有相同的性能下降,除了100个日期的长凳比2000年的日期显示更好的perf。我试图创建一个总是最大大小的日期数组,并且只迭代前100,2000,30000个元素。在这种情况下,所有版本的版本都相同(在我的机器上为15.3 + - 0.3 ns)。

  import org.openjdk.jmh.annotations 。*; 

import java.time.LocalDate;
import java.util。*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;


@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OperationsPerInvocation(LocalDateBenchmark.ITERATIONS)
@OutputTimeUnit(TimeUnit。 NANOSECONDS)
public class LocalDateBenchmark {
public static final int MAX_ITERATIONS = 1000000;
public static final int ITERATIONS = 30000;

private static final LocalDate MIN_DATE = LocalDate.of(1900,1,1);
private static final LocalDate MAX_DATE = LocalDate.of(2100,1,1);
private static final int DAYS_BETWEEN =(int)(MAX_DATE.toEpochDay() - MIN_DATE.toEpochDay());

public LocalDate [] dates = new LocalDate [MAX_ITERATIONS];
私有随机随机;

@Setup(Level.Trial)
public void setUpAll(){
随机r = ThreadLocalRandom.current(); (int i = 0; i< dates.length; ++ i){
dates [i] = MIN_DATE.plusDays(r.nextInt(DAYS_BETWEEN));

}
}

@Setup(Level.Iteration)
public void setUpRandom(){
random = new Random();
}

@GenerateMicroBenchmark
public int timeToEpochDay(LocalDateBenchmark state){
int result = 0;
LocalDate [] dates = state.dates;
int offset = random.nextInt(MAX_ITERATIONS - ITERATIONS); (int i = offset; i< offset + ITERATIONS; i ++){
LocalDate date = dates [i];

result + = date.toEpochDay();
}
返回结果;
}
}


I was curious if we finally get a fast datetime library with JDK8. Nearly all LocalDate computations use toEpochDay so I looked at the source and the large number of divisions and branches made me curious if I could do better.

I eliminated some branching and all but one division, however the speedup is worse than expected. So my first question how is it possible that an algorithm using multiple division takes only about 30 cycles (throughput). Holger's comments seem to have answered this: Division by a small constant gets JIT-ed to multiplication. I did it manually and now I'm consistently beating the original implementation by a factor of 2.

The benchmark is pretty straightforward, just iterate though an array of random LocalDates and convert each of them toEpochDay. Despite the randomness, the results are pretty consistent. The size of the array is a parameter and my main question is where the big slowdown between 2000 and 30000 comes from. There's should be some slowdown as the data no more fit in the L1 cache, but the memory accesses of both algorithms are exactly the same (i.e., none but fetching the date from the array).

The still open question is: How it comes that the behaviour of two simple memory-access-free implementations of the same function change when iterating over an array? The original algorithm suffers a much bigger slowdown than mine.

My algorithm is probably not worth copying here, it's undocumented and about as cryptic as the original, and there's only a very rudimentary test.

解决方案

I haven't catched the reason directly, but it is certainly a benchmarking framework shortcoming. Something related to GC and per-invocation costs. I have the same performance degradation with JMH, except bench with 100 dates shows better perf than with 2000 dates, too. I've tried to create the dates array always of maximum size, and iterate just first 100, 2000, 30000 elements. In this case all versions performed equally (15.3 +- 0.3 ns on my machine).

import org.openjdk.jmh.annotations.*;

import java.time.LocalDate;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;


@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OperationsPerInvocation(LocalDateBenchmark.ITERATIONS)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class LocalDateBenchmark {
    public static final int MAX_ITERATIONS = 1000000;
    public static final int ITERATIONS = 30000;

    private static final LocalDate MIN_DATE = LocalDate.of(1900, 1, 1);
    private static final LocalDate MAX_DATE = LocalDate.of(2100, 1, 1);
    private static final int DAYS_BETWEEN = (int) (MAX_DATE.toEpochDay() - MIN_DATE.toEpochDay());

    public LocalDate[] dates = new LocalDate[MAX_ITERATIONS];
    private Random random;

    @Setup(Level.Trial)
    public void setUpAll() {
        Random r = ThreadLocalRandom.current();
        for (int i=0; i< dates.length; ++i) {
            dates[i] = MIN_DATE.plusDays(r.nextInt(DAYS_BETWEEN));
        }
    }

    @Setup(Level.Iteration)
    public void setUpRandom() {
        random = new Random();
    }

    @GenerateMicroBenchmark
    public int timeToEpochDay(LocalDateBenchmark state) {
        int result = 0;
        LocalDate[] dates = state.dates;
        int offset = random.nextInt(MAX_ITERATIONS - ITERATIONS);
        for (int i = offset; i < offset + ITERATIONS; i++) {
            LocalDate date = dates[i];
            result += date.toEpochDay();
        }
        return result;
    }
}

这篇关于JDK8 LocalDate.toEpochDay的奇怪性能下降的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆