Java 如何使用System.nanoTime()将循环迭代精确延迟到每秒1M的频率?

Java 如何使用System.nanoTime()将循环迭代精确延迟到每秒1M的频率?,java,performance,delay,benchmarking,timedelay,Java,Performance,Delay,Benchmarking,Timedelay,我创建了一个Java程序,以特定频率发出事件。我使用的是System.nanoTime()而不是Thread.sleep(),因为根据许多引用和示例,第一个提供了更高的间隔精度。然而,我想当我试图将其设置为每秒发送100万条记录的数据速率时,它并没有达到目标。这是我的代码: long delayInNanoSeconds = 1000000; private void generateTaxiRideEvent(SourceContext<TaxiRide> sourceConte

我创建了一个Java程序,以特定频率发出事件。我使用的是
System.nanoTime()
而不是
Thread.sleep()
,因为根据许多引用和示例,第一个提供了更高的间隔精度。然而,我想当我试图将其设置为每秒发送100万条记录的数据速率时,它并没有达到目标。这是我的代码:

long delayInNanoSeconds = 1000000;

private void generateTaxiRideEvent(SourceContext<TaxiRide> sourceContext) throws Exception {
    gzipStream = new GZIPInputStream(new FileInputStream(dataFilePath));
    reader = new BufferedReader(new InputStreamReader(gzipStream, StandardCharsets.UTF_8));
    String line;
    TaxiRide taxiRide;
    while (reader.ready() && (line = reader.readLine()) != null) {
        taxiRide = TaxiRide.fromString(line);
        sourceContext.collectWithTimestamp(taxiRide, getEventTime(taxiRide));
        // sleep in nanoseconds to have a reproducible data rate for the data source
        this.dataRateListener.busySleep();
    }
}

public void busySleep() {
    final long startTime = System.nanoTime();
    while ((System.nanoTime() - startTime) < this.delayInNanoSeconds) ;
}
long delayinnanoses=1000000;
私有void generateTaxiRideEvent(SourceContext SourceContext)引发异常{
gzipStream=newgzipinputstream(newfileinputstream(dataFilePath));
reader=新的BufferedReader(新的InputStreamReader(gzipStream,StandardCharsets.UTF_8));
弦线;
出租车;
while(reader.ready()&&(line=reader.readLine())!=null){
滑行=滑行。从字符串(行);
collectWithTimestamp(滑行,getEventTime(滑行));
//以纳秒为单位睡眠,使数据源具有可再现的数据速率
this.dataRateListener.busySleep();
}
}
公共空间busySleep(){
final long startTime=System.nanoTime();
而((System.nanoTime()-startTime)
因此,当我在
delayInNanoSeconds
变量中等待10000纳秒时,我将得到100K的工作负载 记录/秒(1_000_000_000/10_000=100K r/s)。当我在
delayInNanoSeconds
变量中等待2000纳秒时,我的工作负载将为 500K记录/秒(1_000_000_000/2_000=500K r/s)。在1000纳秒的时间里,我的工作量将达到1百万 记录/秒(1_000_000/1000=1M r/s)。对于500纳秒,工作负载为2M rec/sec(1_000_000_000/500=2M r/s)


我发现最好使用
double
而不是
long
来提高精度。这有什么关系吗?或者问题只是操作系统的限制(我使用的是Linux Ubuntu 18)?或者可能是因为我使用了
readLine()
方法,并且有一种更快的方式来发出这些事件?我认为当我使用
GZIPInputStream
类时,我正在将整个文件加载到内存中,
readLine()
不再访问磁盘。如何提高我的应用程序的数据速率?

@TobiasGeiselmann提出了一个很好的观点:您的延迟计算没有考虑到调用
busySleep

您应该计算一个相对于最后一个截止日期的截止日期,而不是日志记录后的当前时间。也不要使用上一个
System.nanoTime()
的结果;这将是一段时间>=实际的截止日期(因为纳米时间本身需要时间,至少几纳秒,所以它不可避免地会睡过头)。这样会累积错误

在第一次迭代之前,找到当前时间并设置
long deadline=System.nanoTime()。在每次迭代结束时,do
deadline+=1000并使用忙等待循环旋转到现在>=截止日期


如果
截止日期-现在
足够大,请使用将CPU分配给其他线程的方法,直到接近唤醒截止日期
。根据评论,
LockSupport.parknonas(…)
对于现代Java来说是一个不错的选择,实际上可能需要等待足够短的睡眠时间(?)。我真的不懂Java。如果是这样的话,你应该检查一下现在的时间,计算到截止日期的时间,然后打一次电话

(对于未来的CPU,如Intel Tremont(下一代Goldmont),
锁定支持。Parknos
可移植地公开诸如在给定TSC截止日期之前闲置CPU内核的功能。不通过操作系统,只需一个超线程友好的截止日期暂停,适合SMT CPU上的短期休眠。)

繁忙等待通常不好,但适用于高精度极短延迟。1微秒的时间不足以有效地让操作系统上下文在具有当前操作系统的当前硬件上切换到其他内容并返回。但是较长的睡眠时间间隔(当您选择较低的频率时)应该睡眠,让操作系统在这个核心上做一些有用的事情,而不是只是忙着等待这么长时间

理想情况下,当您在时间检查上旋转时,您应该在延迟循环中执行类似x86的
pause
指令,以便对共享同一物理核的其他逻辑核(超线程/SMT)更友好。Java9
Thread.onSpinWait()
应该在自旋等待循环中调用(特别是在等待内存时),这样JVM就可以以可移植的方式公开这个概念。(我想这就是它的目的。)


如果您的系统速度足够快,能够在每次迭代运行一次时间获取函数时跟上进度,那么这将起作用。如果没有,那么您可以每4次迭代检查一个截止日期(循环展开),以分摊
nanoTime()
的成本,这样您就可以连续登录4次


当然,如果您的系统在没有延迟调用的情况下速度不够快,您需要优化一些东西来解决这个问题。您不能延迟负时间量,检查时钟本身需要时间。

您的假设是代码在0ns内执行,但这绝对不是真的。试着毫不拖延地运行代码,看看你得到的频率是多少。然后你需要从延迟时间中减去你的逻辑所花费的时间。你有没有在没有任何延迟的情况下达到超过1M r/s的速度?但是我想要一些代码,我可以控制频率而不需要重新编译。因此,我可以在测试中改变数据速率到我想要的任何频率。我理解这个想法,但还不足以看到它的实现。在Java代码中会是什么样子?看到代码试图延迟读取文件是非常奇怪的。但无论如何,只要将循环更改为
/*外部循环*/{final long startTime=System.nanoTime();/*执行操作*/while((System.nanoTime()-startTime)