Java/Kotlin系统仿真时间戳的实现

仿真中的时间

系统仿真中,最少需要考虑两个时间。一个是仿真时间,一个是墙上时间。仿真时间是仿真系统中的时间推进过程,可能是均匀的,也可能是基于事件的非线性推进。墙上时间则是现实中的时间。仿真时间与墙上时间可能没有联系。在某些系统中,则必须维持二者之间的(比例)关系。

在分布式实时仿真或实物在回路的仿真系统中,需要维持仿真时间与墙上时间的同步关系。

仿真时间与墙上时间的关系是通过一个时钟同步算法来维持的。时钟同步算法的目的是维持仿真时间与墙上时间的关系,使得仿真时间与墙上时间的差值保持在一个可接受的范围内。

Java/Kotlin中的时间戳

实现任何时间同步算法,都离不开本地系统的时间戳。

在JVM中,时间戳的精度是毫秒级别的。

在Java/Kotlin中,一般通过System.currentTimeMillis()来获取当前时间戳,比如在计算算法的执行时间时,可以这样做:

val start = System.currentTimeMillis()
// do something
Thread.sleep(100)
val end = System.currentTimeMillis()
println("cost: ${end - start} ms")
cost: 113 ms
fun prod(n : Long): Long {
    var p = 1L
    for (i in 1..n) {
        p *= i
    }
    return p
}

fun timeIt(timeFunc: ()->Long, n: Int, func: ()->Unit): Double {
    var t = 0.0
    for (i in 1..n) {
        val t0 = timeFunc()
        func()
        val t1 = timeFunc()
        t += (t1 - t0).toDouble()
    }
    return t/n
}

fun timeHist(timeFunc: ()->Long, n: Int, func: ()->Unit): List<Double> {
    val t = mutableListOf<Double>()
    for (i in 1..n) {
        val t0 = timeFunc()
        func()
        val t1 = timeFunc()
        t.add((t1 - t0).toDouble())
    }
    return t
}

首先,进行prod(1000000)运行时间的比较,采用两种不同的计时方式。值得注意的是,可以用System::currentTimeMillis来引用方法,也可以构造lambda表达式{System.nanoTime()}来引用方法。

// measure time in milliseconds
val dtMillis = timeIt(System::currentTimeMillis, 10000) {
    prod(1000000)
}
// measure time in nanoseconds
val dtNano = timeIt(System::nanoTime, 10000) {
    prod(1000000)
}

println("${dtMillis} ms")

println("${dtNano} ns")
1.4356 ms
1435843.23 ns

基本上,JVM中所有的时间戳的精度都是相仿的,均试图通过调用最高精度的系统时间戳来实现。但是currentTimeMillis在某些时候精度是不足以表达,因此还需要利用nanoTime来获取更高精度的时间戳。

下面的比较可以更好的表明这一点。

%use lets-plot

获得数据,并都转换为毫秒:

// collect data for plotting
val data = mapOf(
    "ms" to timeHist(System::currentTimeMillis, 10000) {
        prod(1000000)
    },
    "ns" to timeHist({System.nanoTime()}, 10000) {
        prod(1000000)
    }.map { it/1000000.0 }
)

可以把上面的数据绘制成如下的图表:

// lets-plot: results
var p = letsPlot(data)
p += geomHistogram(binWidth = 0.2, color = "blue", fill="blue"){x="ms"}
p += geomHistogram(binWidth = 0.2, color = "red", fill="red"){x="ns"}
p += ggsize(600, 400)
p + ggtitle("Time in milliseconds") + xlab("Measured Time") + ylab("Count")

数据分布图

从上图可以看到,currentTimeMillis得到大概一定比例的不同值,最终平均得到结果。而nanoTime可以直接到结果。

Instant的使用

在实际开发中,并仅仅需要知道两个时间点的差,还要把时间戳转换成时间,这时候就需要用到Instant了。Instant是Java8中引入的一个时间戳类,它的分辨率是纳秒级别的。它所返回的时间戳是从1970年1月1日0时0分0秒开始的纳秒数。这个时间起点也称为UTC时间起点。

在此基础上,还需要定义时区的概念。时区是指相对于UTC时间起点的偏移量。比如北京时间是UTC时间起点的8小时偏移量,那么北京时间的起点就是1970年1月1日8时0分0秒。

import java.time.Instant
import java.time.ZoneId

在Java中,时区的定义是通过ZoneId来实现的。ZoneId是一个时区的标识符,比如Asia/Shanghai就是北京时间的时区标识符。通过下面的代码可以时区标识的列表:

val asiaZoneIds = ZoneId.getAvailableZoneIds().filter { it.startsWith("Asia/C") }.sorted()
println("Aisa Cities starting with 'C': $asiaZoneIds")
Aisa Cities starting with 'C': [Asia/Calcutta, Asia/Chita, Asia/Choibalsan, Asia/Chongqing, Asia/Chungking, Asia/Colombo]

时间戳和当地时间的转换代码如下:

val zone = ZoneId.of("Asia/Chongqing")
val now = Instant.now()
println("UTC时间零点  \t: ${Instant.EPOCH}")
println("UTC时间戳    \t: ${now.epochSecond} ms + ${now.nano} ns")
println("当前时间     \t: ${now.atZone(zone)}")

val sec = now.epochSecond
val nano = now.nano.toLong()
fun localTime(sec: Long, nano: Long, zone: ZoneId) = Instant.ofEpochSecond(sec, nano).atZone(zone) 
println("时间戳->时间 \t: ${localTime(sec, nano, zone)}")
UTC时间零点  	: 1970-01-01T00:00:00Z
UTC时间戳    	: 1682221288 ms + 773663800 ns
当前时间     	: 2023-04-23T11:41:28.773663800+08:00[Asia/Chongqing]
时间戳->时间 	: 2023-04-23T11:41:28.773663800+08:00[Asia/Chongqing]

有上面的例子,在仿真程序中就可以这样做:

fun timeStamp() : Long {
    val now = Instant.now()
    return now.toEpochMilli() * 1000000 + now.nano
} 

fun timeFromStamp(stamp: Long) : Instant {
    val ms = stamp / 1000000
    val ns = stamp % 1000000
    return Instant.ofEpochMilli(ms).plusNanos(ns)
}

val stamp = timeStamp()
println("时间->时间戳 \t: ${stamp}")
println("时间戳->时间 \t: ${timeFromStamp(stamp).atZone(zone)}")
时间->时间戳 	: 1682221291064901100
时间戳->时间 	: 2023-04-23T11:41:31.064901100+08:00[Asia/Chongqing]

总结

  1. JVM有一个高精度的时钟,能给出纳秒为单位的结果,但是实际精度不好说;
  2. 本地时间和时间戳采用UTC时间零点可以进行转换。
  3. 在仿真中,可以用纳秒单位的Long来表达。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大福是小强

除非你钱多烧得慌……

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值