System.getCurrentTimeMillis() 带来的性能开销

5
我正在制作一种概率模拟器,它可以在特定的时间内运行,或者进行特定次数的重复运算。我正在寻求优化方法,目前已经使用多线程,每个ProbabilityWorker都扩展了Thread,并且主程序会自动分配n个线程,其中n是可用线程数量(例如,在我的Core i3-7100U上,这是4)。
我正在分析性能,并意识到我使用的获取与结束时间相关的方法会导致很大的开销。
对于“在特定时间内运行”的模式,我在循环条件中使用new Date对象,然后将其更改为更快的System.currentTimeMillis()来尝试节省时间,但我注意到即使这样也会引起开销。
我的run函数如下:
public void run() {
    if (mode) {
        while (completed < repitions) {
            resultSet[randy.nextInt(o)]++;
            completed++;
        }
    } else {
        while (System.currentTimeMillis() < endTime) {
            resultSet[randy.nextInt(o)]++;
            completed++;
        }
    }
    done = true;
}

mode 为真时表示运行指定次数,randy 是一个随机数生成器,o 表示可能的结果数量,endTime 表示以毫秒为单位的终点时间,系统时间(可以被修改,程序接受一定量的秒数,endTime 被计算为当前时间加上 secondsInput * 1000)。

此外,在同一台 Core i3-7100U 上,这是我的性能统计数据:

DE-WEY-LAPTOP:/mnt/c/Users/danny/Documents/Programming/Data Structures/Probability$ java Main -n 10000000000

Running 10000000000 repitions of the probability simulator with 2 possible outcomes.
4 threads detected on system; doing 2500000000 repitions per thread.
Done. Gathering results from worker threads...
Done. Printing results...
Outcome 1: 4999997330 out of 10000000000 (49.9999733%)
Outcome 2: 5000002670 out of 10000000000 (50.0000267%)
Time taken: 43.443 seconds (2.301866813986143E8 ops/sec)

DE-WEY-LAPTOP:/mnt/c/Users/danny/Documents/Programming/Data Structures/Probability$ java Main -t 44

Running the probability simulator for 44 seconds using 4 threads.
Done. Gathering results from worker threads...
Done. Printing results...
Outcome 1: 141568074 out of 283130850 (50.000935609807264%)
Outcome 2: 141562776 out of 283130850 (49.999064390192736%)
Time taken: 44 seconds (6434792.045454546 ops/sec)

我的问题是,有没有一种优化System.currentTimeMillis()方法调用的方法,可以不用或减少它所需的时间?是否有其他更快的调用可以使用?


4
System.nanoTime()更适合您的目的(计时某些操作所需的时间)。另请参见Java中的时间测量开销 - Mick Mnemonic
3
你可以检查每秒执行的操作次数,并记录任何N次操作所需的时间。你可能会偶尔损失半秒钟,但这将有所改善 ;) 例如,如果你的目标是百万级别,你可以每进行50,000次尝试进行一次计时检查。代码如下:while (你的条件) { for 1:50000 { 执行该操作 } } - Veselin Davidov
你在循环中调用了 System.currentTimeMillis() 283130850 次?我真的认为有更好的方法 - 实现 tick 并选择何时检查给定时间是否小于当前时间。 - zlakad
1
“我使用获取相对于结束时间的当前时间,这会导致很多开销。” - 定义开销; 这就是所谓的“繁忙循环”,一个循环会尽可能地调用许多次,以利用分配的CPU时间。您可能需要引入Thread.sleep()的调用,使该循环少一些CPU负担-并减少同时进行的方法调用数量。 - Gimby
生成随机整数比currentTimeMillis()要昂贵得多。尝试对您的代码进行分析。 - LMC
1个回答

3
你应该真正研究一下 System.nanoTime(并坚持使用它)- 在JVM中,这是我所知道的最好的选择。除了它测量 经过时间 而没有任何时钟时间概念之外,它也是最快的 - 这就是 JMH 使用它的原因(或者我希望任何其他理智的微基准测试都会使用它)。
此外,System.currentTimeMillis 返回 ms 精度(有些事情比 1ms 更快),而且两次调用此方法之间的差异 可能返回负值
不过要记住两件事,第一,每次调用 System.nanoTime 也会对性能产生影响,平均每次调用需要(在我的 CPU 和 JVM-9 上接近你的情况)25 ns
最后一点是 System.nanoTime 具有纳秒精度,但不具有纳秒准确性。这意味着当您调用以下内容时:
long start = System.nanoTime();
long end = System.nanoTime();

两个结果都会返回一个具有纳秒精度的数字,也就是说它们将有多个数字。

但是这个数字可能不太准确。准确性根据您可能会问什么而定。由于System.nanoTime返回的值是任意的,除非调用System.nanoTime的其他调用,否则没有可以进行比较的对象,因此:

long result = end - start;

这可能不是纳秒级别的精确结果。其不准确度约为1微秒,在我的笔记本电脑上约为0.2-0.5微秒。


使用System.nanoTime,没有比它更快或更精细的东西。

此外,在这种情况下,实现System.nanoTime()而不是System.getCurrentTimeMillis()实际上要慢得多 - 通过使用System.getCurrentTimeMillis(),我可以获得每秒6,438,567个操作,而System.nanoTime()仅获得1,771个操作/秒。 - DDPWNAGE
@DDPWNAGE,我只是想知道(插入讽刺的表情),是否你的代码在做一些可疑的事情,与nanoTimegetCurrentTimeMillis相比。 - Eugene
我自己也尝试过,nanoTime() 实际上比 currentTimeMillis() 更慢。原因在这里解释:https://dev59.com/XWIk5IYBdhLWcg3wr_9o。在我的系统中,差异并不是那么大;currentTimeMillis() 大约快 3 倍。 - Mick Mnemonic
1
显然,这个差异在很大程度上取决于操作系统;我正在使用的是 Windows,其中 currentTimeMillis()(显然)很快。 - Mick Mnemonic
我自己在运行Windows,@Mnemonic。Eugene,我发布了整个循环 - 我正在使用时间作为中断条件。 - DDPWNAGE
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接