【问题标题】:Java : Issue with capturing execution time per iteration in a MapJava:在 Map 中捕获每次迭代的执行时间的问题
【发布时间】:2016-12-15 20:44:04
【问题描述】:

我需要在迭代中捕获某些代码的执行时间。我决定使用Map<Integer,Long> 来捕获这些数据,其中 Integer(key) 是迭代次数,Long(value) 是该迭代所消耗的时间毫秒

我编写了下面的 java 代码来计算每次迭代所花费的时间。我想确保在调用实际代码之前所有迭代所花费的时间为零。令人惊讶的是,以下代码在每次执行时表现不同。

有时,我会得到所需的输出(所有迭代为零毫秒),但有时我会得到一些随机迭代的正值甚至负值。

我尝试用以下代码替换System.currentTimeMillis();

new java.util.Date().getTime();

System.nanoTime();

org.apache.commons.lang.time.StopWatch

但仍然没有运气。

关于为什么某些迭代需要额外时间以及如何消除它的任何建议?

package com.stackoverflow.programmer;

import java.util.HashMap;
import java.util.Map;

public class TestTimeConsumption {

    public static void main(String[] args) {
        Integer totalIterations = 100000;
        Integer nonZeroMilliSecondsCounter = 0;
        Map<Integer, Long> timeTakenMap = new HashMap<>();
        for (Integer iteration = 1; iteration <= totalIterations; iteration++) {
            timeTakenMap.put(iteration, getTimeConsumed(iteration));
            if (timeTakenMap.get(iteration) != 0) {
                nonZeroMilliSecondsCounter++;
                System.out.format("Iteration %6d has taken %d millisecond(s).\n", iteration,
                        timeTakenMap.get(iteration));
            }
        }
        System.out.format("Total non zero entries : %d", nonZeroMilliSecondsCounter);
    }

    private static Long getTimeConsumed(Integer iteration) {
        long startTime = System.currentTimeMillis();
        // Execute code for which execution time needs to be captured
        long endTime = System.currentTimeMillis();
        return (endTime - startTime);
    }
}

这是同一代码的 5 次不同执行的示例输出:

执行 #1(不正常)

Iteration  42970 has taken 1 millisecond(s).
Total non zero entries : 1

执行 #2(正常)

Total non zero entries : 0

执行 #3(正常)

Total non zero entries : 0

执行 #4(不正常)

Iteration  65769 has taken -1 millisecond(s).
Total non zero entries : 1

执行 #5(不正常)

Iteration    424 has taken 1 millisecond(s).
Iteration  33053 has taken 1 millisecond(s).
Iteration  76755 has taken -1 millisecond(s).
Total non zero entries : 3

我正在寻找一种基于 Java 的解决方案,以确保所有 迭代持续消耗零毫秒。我更喜欢 使用纯 Java 代码完成此操作,而无需使用分析器。

注意:我也可以通过 C 代码完成此操作。

【问题讨论】:

  • 如果你必须使用并行集合,那么你做错了什么......
  • 是的,我同意。我将消除并行集合。不过,我需要所有迭代的执行时间一致。
  • 正如您在这里所看到的,Java 中的微基准测试并不像它们第一次出现那样简单。请参阅 stackoverflow.com/questions/504103/…

标签: java time output


【解决方案1】:

如果调整大小,您的HashMap 性能可能会下降。默认容量为 16,您超出了该容量。如果您事先知道预期容量,请创建具有适当大小的 HashMap,同时考虑到默认负载因子 0.75

如果您在未定义新映射的情况下重新运行迭代,并且 Integer 键未从零重新开始,则您需要调整映射的大小,同时考虑所有可能迭代的总数。

int capacity = (int) ((100000/0.75)+1);
Map<Integer, Long> timeTakenMap = new HashMap<>(capacity);

【讨论】:

  • 这似乎有效。但是,如果我在另一个迭代中重复这个过程(没有 HashMap),它会失败。
  • 你用什么来代替HashMap?
  • 没什么,只是一个循环和上面的代码。主要方法移至另一个方法code(),当前代码为while(true){ code();}
  • 像这样重新运行 HashMap put 迭代意味着您可能会遇到更多影响性能的未知数,例如不断创建对象和拆解/可能的 GC,因此它变成了一个需要优化的灰色区域。
【解决方案2】:

正如您在这里开始学习的那样,用 Java 编写微基准并不像人们最初想象的那么容易。每个人都会在某个时候被咬伤,即使是多年来一直在做这件事的顽固的性能专家。

在 JVM 和操作系统中发生了很多事情,导致结果出现偏差,例如 GC、动态热点优化、重新编译、时钟更正、线程争用/调度、内存争用和缓存未命中。仅举几例。遗憾的是,这些偏差并不一致,它们很容易支配微基准。

要回答您的直接问题,即为什么有时时间会变为负数,这是因为 currentTimeMillis 旨在捕获挂钟时间而不是经过的时间。计算机上没有挂钟是准确的,有时时钟会被调整..很可能倒退。有关 Java 时钟的更多详细信息,请参阅以下 Oracle 博客Inside the Oracle Hotspot VM clocks

nanoTime verses currentTimeMillis 的更多细节和支持可以阅读here

在继续您自己的基准测试之前,我强烈建议您阅读how do I write a currect micro benchmark in java。快速概要是 1) 在获取结果之前预热 JVM,2) 跳过循环以避免死代码消除,3) 确保在同一台机器上没有其他任何东西在运行,但接受会有线程调度。您甚至可能想要将线程固定到内核上,这取决于您想采取多远,4) 使用专门为微基准测试设计的框架,例如 JMH 或用于快速轻量级峰值 JUnitMosaic 会产生良好的结果。

【讨论】:

  • 感谢 Chris 提供的宝贵意见和链接。我会推荐他们的。
【解决方案3】:

我不确定我是否理解您的问题。 您正在尝试执行一组特定的语句 S,并期望执行时间为零。然后,您通过执行多次并验证结果来测试这个前提。

这是一个奇怪的期望:任何事情都会消耗一些时间,甚至可能更多。因此,尽管可以成功测试,但这并不能证明没有使用任何时间,因为您的程序是save_time();execute(S);compare_time()。即使execute(S) 什么都不是,您的时间也是离散的,因此,您的挂钟的“滴答”可能恰好发生在save_timecompare_time 之间,导致一段时间已经明显过去.
因此,我希望您的 C 程序的行为完全相同。你跑过多次吗?当您将迭代次数增加到数百万时会发生什么?如果它仍然没有发生,那么显然你的 C 编译器已经以不测量时间的方式优化了代码,而 Java 显然没有。

还是我理解错了?

【讨论】:

  • 抱歉,我在理解您的帖子时也遇到了问题。基本上我在尝试的是,我测量的时间消耗应该只包括我的计算逻辑,而不是我的逻辑之外的任何东西(内存分配开销等,.)。
  • 由于时间是离散的,在任意时刻,时间将从 1 变为 2,从 2 变为 3,依此类推(对于任何可辨别的时刻和精确性)。所以想一想当你的时钟几乎准备好从 1 到 2 时会发生什么。你如何在没有任何测量错误的情况下测量时间,当以下序列可能发生时: 1) 测量开始。 2)时钟滴答作响。 3)您的代码执行。 4. 您的测量结束。并且也是相同的测量,其中 2) 不会发生。这两个测量值会有所不同。那只是因为你无法无限精确地测量时间。
【解决方案4】:

你的暗示是对的……System.currentTimeMillis(); 在这种情况下是要走的路。

不保证在任何系统中增加整数对象 i 的值代表毫秒或周期时间...

您应该使用 System.currentTimeMillis() 并计算经过的时间

示例:

public static void main(String[] args) {
    long lapsedTime = System.currentTimeMillis();
    doFoo();
    lapsedTime -= System.currentTimeMillis();
    System.out.println("Time:" + -lapsedTime);
}

【讨论】:

  • System.nanoTime() 更适合进行基准测试。最好不要自己做。
  • 这段代码不能得到一致的输出吗?
  • 那是 Java 的一部分还是外部库?我更喜欢在纯 Java 中执行此操作。
  • JMH 是 OpenJDK 的一部分,但被打包为一个单独的项目。由于 Java 的性质,即 JIT 编译的 JVM 语言,如果没有一些非常广泛的测试工具,几乎不可能获得可用的基准测试结果。
【解决方案5】:

我也不确定,您正在尝试执行某个代码,并尝试获取每次执行迭代的执行。

我希望我理解正确,如果这样比我建议的请使用 System.nanoTime() 而不是 System.currentTimeMillis(); 因为如果你的块语句足够小,你总是在毫秒内得到零。

简单的 Ex 可以是:

public static void main(String[] args) {
    long lapsedTime = System.nanoTime();
    //do your stuff here.  
    lapsedTime -= System.nanoTime();
    System.out.println("Time Taken" + -lapsedTime);
}

如果System.nanoTime() and System.currentTimeMillis(); 没有太大区别。但是,如果您的语句集在每次迭代中都没有更多,那么您需要多少准确的结果以及以毫秒为单位的一些时间差,您可能会得到零。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-09-11
    • 2011-11-13
    • 1970-01-01
    • 1970-01-01
    • 2011-10-30
    • 2016-10-31
    • 2018-02-17
    相关资源
    最近更新 更多