如何启动 Java 程序并精确控制执行时间?

How to launch a Java program with precisely controlled execution time?

据我所知,JVM 在启动 Java 应用程序之前会为其分配一些 RAM,并且该内存可以在启动前由用户控制。但是当我启动一个应用程序时还有另一个问题,它每次都有不同的时间执行。

这是一个非常简单的 for 循环示例:

package timespent;

public class Main {

    public static void main(String[] args) {

        long startTime = System.nanoTime();

        int j = 0;

        for (int i = 0; i < 1.E6; i++) {
            j = i + 1;
        }

        long endTime = System.nanoTime();

        long duration = (endTime - startTime);

        System.out.println("duration = " + duration);
    }
}

它打印出不同的结果:

duration = 5720542
duration = 7331307
duration = 7737946
duration = 6899173

假设我希望它在 10,000,000 纳秒或 10 毫秒内执行。

我想要什么?

我希望 java 应用以准确的时间执行。

我为什么需要这个?

当我启动一个应用程序时,我想在应用程序启动 window 之前显示它加载所有组件之前的确切执行时间。

我想这是一种 CPU 操纵,我想知道这是否可能。

Q1:是否可以在Java?
Q2: 如果在 Java 中不可能,那么有什么方法可以通过访问 OS 本机方法来实现。例如通过优先 Java 应用程序或其他东西?
Q3:如何将应用程序的状态保存到文件并加载到内存中?

任意代码的精确执行时间是不确定的,因为这取决于物理机同时做的其他事情。

即使您计划通过跟踪启动时间戳和计划的结束时间戳并在退出程序之间的持续时间内让主线程休眠来缩短执行时间 "constant",它仍然会有所不同,a相当大的数量。

线程执行或等待的时间和时间不在程序员的控制范围内。

这根本不可能。首先,以纳秒为单位测量时间并不准确。我觉得 this post 解释得很好。

其次,您无法控制 CPU 如何安排执行。可能有其他任务占用 CPU 时间,这会延迟程序的执行。

在您的时间测量中存在许多不确定性来源。所有这些来源不仅会影响您的测量结果,运行时间本身也是不确定的。不确定性来源包括:

  • 缓存使用(内存的哪些部分缓存在 CPU 中)。您的数据可以通过 CPU 执行后台任务从缓存中逐出。

  • 内存放置(内存是否直接连接到正在执行的CPU核心?)。这可能会随着时间的推移而改变,因为您的进程可能会随时迁移到另一个核心。

  • 软件中断(您的 OS 抢占您的进程 运行 另一个进程)。在安静的机器上 运行ning 可能会有所缓解,但不能保证您不会被打扰。

  • 热节流(您的 CPU 决定它太热并降低其时钟速度)。除非您准备在某些具有固定时钟速度的嵌入式处理器上工作,否则您对此无能为力。

  • 硬件中断(您的网络连接器从 Internet 上的另一台机器接收到一些数据)。不管什么时候发生,你都没有影响。

  • 不可预测的延迟(您正在从磁盘读取一些数据,但首先,磁盘必须等到数据到达读取头下方)。当您一遍又一遍地重复完全相同的操作时,这可能会遵循一些模式,但是一旦您遇到不相关的硬件中断,这可能会导致 1/7200 rpm * 60 s/min = 8.3 ms.

  • 的意外延迟
  • 垃圾收集(你问的是 Java,所以你在后台有一个 GC 运行ning)。即使是最好的、最现代的垃圾收集器也无法完全避免时不时地让世界停止运转。即使他们没有停止世界,他们仍然 运行 在后台,通过缓存、内存放置和软件中断引入 运行 时间噪音。

这些可能是最重要的来源,可能还有其他来源。关键是,您的进程 永远不会 在机器上单独存在。除非你 运行 没有 OS 并禁用所有硬件中断,否则你只需要忍受这样一个事实,即你的 运行 时间会因执行而异,而且根本没有办法解决这个问题。

[TL;DR] 非常 difficult/impossible。

较长的答案请参阅 Planarity Testing by Path Addition PhD thesis - Benchmarking Methodology Chapter 以了解一些问题,包括:

  1. 其他有资源的应用。 IE。没有足够的内存,操作系统必须分页应用程序;或代码 运行ning 变慢,因为另一个应用程序正在共享 CPU.
  2. 时钟分辨率 - 您的代码只会 运行 与您的 CPU 一样快。将它移植到另一台计算机上,您的基准测试可能会给您带来截然不同的结果,因此不要仅为您的系统优化它。
  3. Class 加载 - 当 JVM 首先 运行 加载代码时,它必须将 类 加载到内存(通常是从磁盘)并解析字节码,所以第一次它 运行s 将比后续时间慢得多。
  4. 即时编译 - 当 JVM 首次加载字节码时,它将 运行 以纯解释模式进行。一旦它多次(比如 10,000)运行 一段字节代码(即代码中的一个函数),它就可以将该函数编译为本机代码。编译会减慢执行速度,但随后的调用会更快,因为它是 运行ning 本机代码,而不是解释代码。然而,这不是一次性编译,如果 JVM 发现块中的某些执行路径受到青睐,那么它可能会重新编译字节代码以尝试优化这些路径,这可能会导致字节的多个本机版本代码,直到 JVM 根据其统计信息稳定代码。
  5. 垃圾收集 - 有时您的代码会在 Java 调用垃圾收集器时被中断。

因此,如果您想对您的应用程序进行基准测试以查看它运行 的最佳表现,那么:

  • 尽可能多地停止其他应用程序;
  • 运行代码好几万遍;
  • 忽略前 10,000 - 20,000 次执行(以缓解 Class 加载和 JIT 编译);和
  • 在发生垃圾回收时忽略迭代(这比听起来更难确定)。

它会让您了解最佳性能,但最佳和现实世界是两个截然不同的东西。

它不会按照你的方式工作。这取决于系统状态,例如有多少系统资源在您的程序上工作。

据我所知,您想要显示打开应用程序的剩余时间。在那种情况下,假设两个用户是 运行 您在不同机器上的程序,它们具有不同的核心结构和时钟频率 ...

但我可以给个建议,你可以只读你的程序的数据并在此基础上调整时间,就像其他应用程序一样,它显示 ..% loaded 或与显示 .. 的下载管理器功能相同下载百分比。

接近这一点的唯一方法是在专门设计用于支持实时执行的操作系统上使用 real time Java

正如其他人所说,由于影响程序速度的其他因素,无法知道确切的剩余时间。但是,您可以将里程碑放入并参考过去的 运行 秒,以获得根据到目前为止的实际时间差异计算的半准确时间 运行 与之前的 运行 秒相比,就像完成的那样例如在 Windows 上复制大文件目录或在 Chrome.

上下载大文件时

所以您没有具体说明您的具体问题,但假设它是处理 100,000 个操作,需要联系互联网上的第 3 方系统,通常需要大约 15 分钟才能完成。您可以跟踪 1) 开始时间、2) 预计结束时间和 3) 部分完成。所以说当你完成 1/2 时,你可以计算经过的时间,然后说这就是剩余的时间。基本上得到每秒操作的速率并将剩余的操作除以它以获得剩余的秒数。

double speed = completedOperations / elapsedSeconds;
double remainingSeconds = remainingOperations / speed;

但这可以改变。假设你开始了这个过程,并在 5 分钟内完成了 1/4 之后,异地备份开始了,这不仅会破坏计算机的磁盘,还会破坏你的互联网连接。现在事情的处理速度是原来的 1/10。您的估计完成时间将开始为 20 分钟,然后在 5 分钟后为 15 分钟。然而,它在那个时候变慢了,所以你在 30 分钟后只完成了 1/2,你在那个时候的剩余时间将是 30 分钟。现在说备份完成,实际上你会在 10 分钟内完成,但它说还剩下 30 分钟。

像这样不受您控制的问题无法解决。你可以做一些事情来减轻它。您可能只想在处理的最后 30 秒内提高速度。如果事情以目前的速度继续下去,那将是最准确的。如果这是一个问题,您可以历史记录一天中的平均速度。如果速度波动,您可以对总 运行 速度和最后一分钟速度进行平均。

另一件可能导致它失败的事情是数据的差异。例如,如果您正在查看客户并根据他们成为客户的日期对其进行处理,那么您的前 10,000 个操作可能针对已经陪伴您多年的忠实客户,并且有大量数据需要处理,而最后 10,000 个操作可能是新客户处理速度更快的数据很少。然后,您可以使用基础数据量而不是客户数量...


但是,如果出于某种原因(大多数情况下)你想要准确,你可以以时间为代价来伪造它。取最大的正常 运行 时间,仅使用自开始以来所用的时间来提供进度和剩余时间。然后当所有实际工作完成后,输入 sleep() 命令等待剩余时间。尽管系统负载或其他原因总是有可能使它花费特别长的时间,但您可以随后将最长时间更改为该新值。

你的 运行 的时间可能在某种曲线上,时间越长,任何单个 运行 完成的可能性就越大,但随后更多运行 将无所事事地等待:

         #            ^
         #            |
        ###           |
        ###           |
       #####          R
      #######         U
    ###########       N
###################   S

Time-------------->

虽然这看起来很愚蠢,但由于您无法控制的变量,您的应用程序将 运行 以不同的速度运行,如果接近恒定的 运行 时间对您很重要,这是唯一的方法。