3. Erlang中的时间和时间校正 | 3. Time and Time Correction in Erlang
3 Erlang中的时间和时间校正
3.1新的扩展时间功能
注意
从Erlang/OTP 18(ERTS 7.0)开始,时间功能已经扩展。这包括一段new API
时间,并time warp modes
在系统时间更改时更改系统行为。
default time warp mode
像以前一样具有相同的行为,而旧的API仍然有效。因此,除非你想要,否则你不需要改变任何东西。不过,强烈建议您使用新API
而不是基于旧API erlang:now/0
。erlang:now/0
已被弃用,因为它将是一个可扩展性瓶颈。
通过使用新的API,您可以自动获得可伸缩性和性能改进。这也使您能够使用该功能multi-time warp mode
提高时间测量的准确度和精度。
3.2术语
为了便于理解本节,我们定义了一些术语。这是我们自己的术语(Erlang/OS系统时间,Erlang/OS单调时间,时间扭曲)和全球接受的术语的混合体。
单调递增
在单调递增的值序列中,具有前任的所有值都大于或等于其前任。
严格单调递增
在严格单调递增的值序列中,所有具有前任的值都大于其前任。
UT1
世界时间。UT1基于地球的旋转,概念上意味着0°经度的太阳时间。
世界协调时
协调世界时。UTC几乎符合UT1
。但是,UTC使用秒的SI定义,其长度与UT1
使用的秒不完全相同。这意味着UTC从UT1
慢慢漂移。为了使UTC相对与UT1
同步,闰秒被插入,并且可能也被删除。也就是说,UTC日期可以是86400,86401或86399秒。
POSIX时间
时间自从。时期定义为00 :00:00,1970-01-01。被定义为正好86400秒。奇怪的是,Epoch被定义为UTC的一个时间,而UTC有另一个定义一天有多长的时间。引用开放小组。这样做的效果是,当插入UTC闰秒时,POSIX时间要么停止一秒钟,要么重复最后一秒。如果UTC闰秒会被删除(这还没有发生),POSIX时间将会让我们有一秒的飞跃。 EpochUTC
A day in POSIX time
"POSIX time is therefore not necessarily UTC, despite its appearance"
时间分辨率
读取时间值时可以区分的最短时间间隔。
时间精度
读取时间值时可以重复和可靠地区分的最短时间间隔。精度受到限制resolution
,但分辨率和精度可能会有很大差异。
时间精度
时间值的正确性。
时间翘曲
时间扭曲是一种向前或向后的跳跃。也就是说,在时间扭曲之前和之后拍摄的时间值的差异并不对应于实际的经过时间。
OS系统时间
操作系统视图POSIX time
。要检索它,请致电os:system_time()
。这可能是也可能不是POSIX时间的准确观点。这段时间通常可以不受限制地向前和向后调整。也就是说,time warps
可以观察到。
要获得有关Erlang运行时系统的OS系统时间源的信息,请致电erlang:system_info(os_system_time_source)
。
OS单调时间
OS提供的单调递增时间。虽然不完全正确,但这次并没有飞跃并且具有相对稳定的频率。但是,如果系统暂停,OS单调时间停止并不少见。自未连接的某个未指定时间点以来,此时间通常会增加OS system time
。这种类型的时间不一定由所有操作系统提供。
要获得关于Erlang运行时系统的操作系统单调时间源的信息,请致电erlang:system_info(os_monotonic_time_source)
。
Erlang系统时间
Erlang的运行时系统视图POSIX time
。要检索它,请致电erlang:system_time()
。
这次可能会或可能不是POSIX时间的准确视图,可能会或可能不会一致OS system time
。运行时系统正在调整两个系统时间。根据time warp mode
使用情况,这可以通过让Erlang系统时间执行一次来实现time warp
。
Erlang单调时间
Erlang运行时系统提供的单调递增时间。Erlang单调时间自一些未指定的时间点以来增加。要检索,请致电erlang:monotonic_time()
。
在accuracy
和precision
二郎山单调时间在很大程度上取决于以下几点:
- 准确度和准确度
OS monotonic time
- 精度和精度
OS system time
time warp mode
用过的
在没有OS单调时间的系统上,Erlang单调时间保证单调性,但不能给出其他保证。对Erlang单调时间进行的频率调整取决于使用的时间扭曲模式。
在运行时系统内部,Erlang单调时间就是“时间引擎”,它用于或多或少与时间有关的所有事情。所有定时器,无论是receive ... after
定时器,BIF定时器还是timer(3)
模块中的定时器,都会触发相关的Erlang单调时间。甚至Erlang system time
是基于Erlang单调时间。通过将当前Erlang单调时间与当前时间偏移相加,即可得到当前的Erlang系统时间。
要检索当前时间偏移量,请调用erlang:time_offset/0
。
3.3介绍
时间对Erlang程序至关重要,更重要的是,正确的
时间对Erlang程序至关重要。由于Erlang是一种具有软实时属性的语言,我们可以在程序中表达时间,因此虚拟机和语言必须注意什么被视为正确的
时间以及函数的时间表现。
当设计Erlang时,假定系统中的挂钟时间显示出与时间定义完全相同的单调时间。这或多或少意味着一个原子钟(或更好的时间源)预计将附加到您的硬件,并且硬件被期望永远锁定在任何人的修补之下。虽然这可能是一个引人注目的想法,但事实根本不是这样。
一台“普通”的现代计算机无法保持时间,而不是本身,除非你有一个芯片级原子钟连接到它。计算机感知的时间通常必须更正。因此,网络时间协议(NTP)协议与ntpd
过程一起尽最大努力使您的计算机时间与正确的时间保持同步。在NTP校正之间,通常使用比原子钟少的有效时间守护者。
但是,NTP不是故障安全的。NTP服务器可能不可用,ntp.conf
可能配置错误,或者您的计算机有时可能与Internet断开连接。此外,您可以让用户(甚至系统管理员)认为正确处理夏令时的方法是每年调整时钟两次(这是不正确的方式)。使事情进一步复杂化,这个用户从互联网上获取你的软件,并没有考虑到计算机所感知的正确时间。用户不关心保持挂钟与正确的时间同步。用户期望您的程序对时间有无限的知识。
大多数程序员也希望时间可靠,至少在他们意识到他们的工作站上的挂钟时间不到一分钟。然后,他们将其设置到正确的时间,但很可能不是一帆风顺的。
当您始终期望系统中的挂钟时间正确时,出现的问题数量可能非常巨大。因此Erlang介绍了多年前的“时间校正估计”或“时间校正”。时间校正依赖于以下事实:大多数操作系统具有某种单调时钟,可以是实时扩展,也可以是一些独立于挂钟设置的内置“滴答计数器”。该计数器可以具有微秒级的分辨率或更少的分辨率,但它具有不可忽视的漂移。
3.4时间修正
如果启用了时间校正,则Erlang运行时系统将同时使用OS system time
和OS monotonic time
调整Erlang单调时钟的频率。时间校正确保Erlang monotonic time
不会扭曲,并且频率相对准确。频率调整的类型取决于使用的时间扭曲模式。本节Time Warp Modes
提供了更多细节。
默认情况下,如果特定平台上存在支持,则会启用时间更正。对它的支持包括OS提供的OS单调时间和使用OS单调时间的Erlang运行时系统的实现。要检查您的系统是否支持操作系统单调时间,请致电erlang:system_info(os_monotonic_time_source)
。要检查系统上是否启用时间校正,请致电erlang:system_info(time_correction)
。
要启用或禁用时间校正,请将命令行参数传递+c [true|false]
给erl(1)
。
如果禁用时间校正,Erlang单调时间可能会前移或停止,甚至会长时间冻结。那么就不能保证Erlang单调时钟的频率是准确的或稳定的。
你通常不想禁用时间校正
。以前的性能损失与时间纠正有关,但现在通常是相反的。如果时间校正被禁用,您可能会遇到可扩展性差,性能差和时间测量不良的情况。
3.5时间翘曲安全码
时间隧道安全的代码可以处理time warp
的Erlang system time
。
erlang:now/0
当Erlang系统时间扭曲时表现不佳。当Erlang系统时间做一个时间倒退时,从erlang:now/0
操作系统系统时间到达最后一个返回值的点时,从冻结返回的值(如果忽略由于实际调用而产生的微秒增量)erlang:now/0
。这种冻结可能会持续很长时间。它可能需要几年,几十年甚至更长的时间才会冻结。
所有用途erlang:now/0
不一定是时间不安全的。如果你不用它来获得时间,那是时间安全的。然而,从性能和可伸缩性的角度来看,所有的使用
都不是最理想的
。所以你真的想用其他功能取代它的使用。有关如何替换使用的示例,请参见部分。erlang:now/0
erlang:now/0How to Work with the New API
3.6时经模式
电流Erlang system time
是通过将电流Erlang monotonic time
与电流相加来确定的time offset
。根据您使用的时间扭曲模式,时间偏移的管理方式会有所不同。
要设置时间扭曲模式,请将命令行参数传递+C [no_time_warp|single_time_warp|multi_time_warp]
给erl(1)
。
无时间翘曲模式
时间偏移量在运行时系统启动时确定,并且稍后不会更改。这是默认行为,但不是因为它是最好的模式(它不是)。这是默认只
因为这是运行系统表现如何,直到ERTS 7.0。确保在时间扭曲期间可以执行的Erlang代码time warp safe
在启用其他模式之前。
由于时间偏移不允许改变,所以时间校正必须调整Erlang单调时钟的频率,以使Erlang系统时间与OS系统时间平滑对齐。这种方法的一个重大缺点是,如果需要进行调整,我们故意在Erlang单调时钟上使用错误的频率。这个错误可能高达1%。运行系统中的所有时间测量都会显示此错误。
如果未启用时间校正,则当OS系统时间向后跳跃时Erlang单调时间会冻结。单调时间的冻结一直持续到OS系统时间赶上为止。冻结可能会持续很长时间。当操作系统系统时间跃升时,Erlang单调时间也向前跳跃。
单时间翘曲模式
从介绍开始,这种模式或多或少是一种向后兼容模式。
在嵌入式系统中,系统在关闭时没有电源,甚至没有电池。系统引导时,系统时钟通常会关闭。如果no time warp mode
被使用,并且Erlang运行时系统在OS系统时间被纠正之前启动,那么Erlang的系统时间可能会出现很长时间,几个世纪甚至更长的时间。
如果您需要使用不是的Erlang代码time warp safe
,并且需要在更正OS系统时间之前启动Erlang运行时系统,则可能需要使用单时间跳跃模式。
注
当您使用此模式执行时间扭曲不安全代码时,存在限制。如果仅可以使用时间扭曲安全代码,则使用该代码要
好得多multi-time warp mode
。
使用单时间扭曲模式,时间偏移分两个阶段处理:
初步阶段
该阶段在运行系统启动时开始。确定基于当前OS系统时间的初步时间偏移。这一抵消从现在开始在整个初步阶段得到解决。
如果启用了时间校正,则需要对Erlang单调时钟进行调整,以保持其频率尽可能正确。但是,未
尝试调整Erlang系统时间和OS系统时间。也就是说,在初步阶段Erlang系统时间和OS系统时间可能会相互分离,并且不会尝试阻止这种情况发生。
如果禁用时间校正,OS系统时间的变化会影响单调时钟,与no time warp mode
使用时相同。
最后阶段
当用户通过调用完成时间偏移时,此阶段开始erlang:system_flag(time_offset, finalize)
。完成只能执行一次。
在完成期间,时间偏移量将被调整并固定,以便当前的Erlang系统时间与当前的OS系统时间保持一致。由于在完成过程中时间偏移可能会发生变化,因此Erlang系统时间可能会在此时发生时间扭曲。时间偏移从现在开始固定直到运行时系统终止。如果时间校正已启用,则从现在开始的时间校正也会进行调整,以使Erlang系统时间与OS系统时间保持一致。当系统处于最后阶段时,其行为完全如同no time warp mode
。
为了使其正常工作,用户必须确保满足以下两个要求:
前向时间翘曲
完成时间偏移时所做的时间扭曲只能向前完成而不会遇到问题。这意味着在启动Erlang运行时系统之前,用户必须确保OS系统时间早于或等于实际POSIX时间。
如果您不确定操作系统系统时间是否正确,请在启动Erlang运行时系统之前将其设置为保证早于实际POSIX时间的时间,以保证安全。
最后确定正确的操作系统系统时间
当用户完成时间偏移时,OS系统时间必须正确。
如果这些要求未得到满足,系统可能表现非常糟糕。
假设满足这些要求,则启用时间校正,并且使用时间调整协议(如NTP)调整OS系统时间,只需要对Erlang单调时间进行小调整,以便在完成后保持系统时间对齐。只要系统没有挂起,所需的最大调整就是插入(或删除)闰秒。
警告
要使用这种模式,确保所有将在两个阶段执行的Erlang代码都是time warp safe
。
仅在最后阶段执行的代码不一定能够应付时间扭曲。
多次翘曲模式
多时间扭曲模式结合时间校正是首选配置
。作为Erlang运行时系统,它几乎在所有平台上都有更好的性能,更好的扩展性和更好的性能。而且,时间测量的准确度和精确度也更好。只有在古老平台上执行的Erlang运行时系统受益于另一种配置。
时间偏移可以随时更改,没有限制。也就是说,Erlang系统时间可以在任何
时间向前和向后执行时间扭曲。当我们通过改变时间偏移将Erlang系统时间与OS系统时间对齐时,我们可以启用时间校正,尝试将Erlang单调时钟的频率调整为尽可能正确。这使得使用Erlang单调时间的时间测量更加准确和精确。
如果禁用时间校正,则在OS系统时间向前跳跃时,Erlang单调时间会跳跃。如果OS系统时间向后跳,Erlang单调时间会暂时停止,但它不会长时间冻结。这是因为时间偏移量被更改为将Erlang系统时间与OS系统时间对齐。
警告
要使用此模式,请确保在运行系统上执行的所有Erlang代码都是time warp safe
。
3.7新时间API
旧时间API基于erlang:now/0
。erlang:now/0
旨在用于许多不相关的事情。这将这些不相关的操作绑定在一起,并导致不需要存在此类问题的操作的性能,可伸缩性,准确性和精度方面的问题。为了改善这一点,新的API在多个功能上传播不同的功能。
为了向后兼容,erlang:now/0
保持“原样”,但是强烈建议您不要使用它
。许多使用情况erlang:now/0
阻止您使用新功能multi-time warp mode
,这是新时间功能改进的重要部分。
某些系统上的一些新BIF可能令人惊讶地在新启动的运行时系统上返回负整数值。这不是一个错误,而是一个内存使用优化。
新API由以下新的BIF组成:
erlang:convert_time_unit/3
erlang:monotonic_time/0
erlang:monotonic_time/1
erlang:system_time/0
erlang:system_time/1
erlang:time_offset/0
erlang:time_offset/1
erlang:timestamp/0
erlang:unique_integer/0
erlang:unique_integer/1
os:system_time/0
os:system_time/1
新API还包括以下现有BIF的扩展:
erlang:monitor(time_offset, clock_service)
erlang:system_flag(time_offset, finalize)
erlang:system_info(os_monotonic_time_source)
erlang:system_info(os_system_time_source)
erlang:system_info(time_offset)
erlang:system_info(time_warp_mode)
erlang:system_info(time_correction)
erlang:system_info(start_time)
erlang:system_info(end_time)
新的Erlang单调时间
Erlang的单调时间从ERTS 7.0开始是新的。它用于分离时间测量,例如从日历时间开始经过的时间。在许多使用情况下,需要测量已用时间或指定相对于另一时间点的时间,而不需要知道UTC中涉及的时间或任何其他全局定义的时间范围。通过引入一个时间范围和本地定义的起始位置,可以在该时间范围内管理与日历时间无关的时间。厄兰单调时间使用这样一个时间尺度和一个本地定义的开始。
Erlang单调时间的引入使我们可以分别调整两个Erlang时间(Erlang单调时间和Erlang系统时间)。通过这样做,经过时间的准确性不会因为系统时间在某个时间点发生错误而受到影响。两次单独调整只能在时间扭曲模式下进行,并且只能在时间扭曲模式中完全分开multi-time warp mode
。除了多次扭曲模式以外的所有其他模式都是出于向后兼容的原因。在使用这些模式时,Erlang单调时间的准确性会受到影响,因为这些模式中Erlang单调时间的调整或多或少与Erlang系统时间相关。
系统时间的调整可能比使用时间扭曲方法更令人窒息,但我们认为这将是一个不好的选择。由于我们可以通过使用Erlang单调时间来表示和测量与日历时间无关的时间,因此最好立即将Erlang系统时间的变化暴露出来。这在系统上执行的Erlang应用程序可以尽快对系统时间的变化做出反应。这或多或少地是大多数操作系统如何处理这种情况(OS单调时间和OS系统时间)。通过平稳调整系统时间,我们只会隐藏系统时间改变的事实,并且使Erlang应用程序更难以以合理的方式对变化做出反应。
为了能够对Erlang系统时间的变化做出反应,您必须能够检测到它发生了。Erlang系统时间的变化发生在当前时间偏移改变时。因此,我们引入了使用监视时间偏移的可能性erlang:monitor(time_offset, clock_service)
。监控时间偏移量的过程在时间偏移量发生更改时以下列格式发送消息:
{'CHANGE', MonitorReference, time_offset, clock_service, NewTimeOffset}
独特的价值
除了报告时间外,erlang:now/0
还会产生独特且严格单调递增的值。为了从时间测量中分离出这个功能,我们已经介绍了erlang:unique_integer()
。
如何使用新API
以前erlang:now/0
是做很多事情的唯一选择。本节介绍一些erlang:now/0
可用于的内容,以及如何使用新的API。
检索Erlang系统时间
Don't
使用erlang:now/0
检索当前的Erlang系统时间。
做
用于根据您的选择erlang:system_time/1
检索当前的Erlang系统时间time unit
。
如果您想要与返回的格式相同erlang:now/0
,请使用erlang:timestamp/0
。
度量值耗用时间
别
拿时间戳erlang:now/0
和计算与时间的差异timer:now_diff/2
。
做
使用时间戳erlang:monotonic_time/0
并使用普通减法计算时间差。结果是native
time unit
。如果您想将结果转换为另一个时间单位,则可以使用erlang:convert_time_unit/3
。
更简单的方法是使用erlang:monotonic_time/1
所需的时间单位。但是,您可能会失去准确性和准确性。
确定事件的顺序
别
通过erlang:now/0
在事件发生时保存时间戳来确定事件顺序。
做
通过保存erlang:unique_integer([monotonic])
事件发生时返回的整数来确定事件的顺序。这些整数在与创建时间相对应的当前运行时系统实例上严格单调排序。
根据事件的时间确定事件的顺序
别
通过erlang:now/0
在事件发生时保存时间戳来确定事件顺序。
做
通过保存包含monotonic time
a和a 的元组来确定事件的顺序,strictly monotonically increasing integer
如下所示:
Time = erlang:monotonic_time(),
UMI = erlang:unique_integer([monotonic]),
EventTag = {Time, UMI}
根据创建时间,这些元组在当前运行时系统实例上严格单调排序。单调时间在第一个元素(比较二元组时最重要的元素)是很重要的。使用元组中的单调时间,可以计算事件之间的时间。
如果您在事件发生时对Erlang系统时间感兴趣,您还可以在保存使用事件之前或之后保存时间偏移erlang:time_offset/0
。与时间偏移相加的Erlang单调时间对应于Erlang系统时间。
如果您在时间偏移量可以更改的模式下执行,并且您希望在事件发生时获取实际的Erlang系统时间,则可以将时间偏移量另存为元组中的第三个元素(比较三维元素时最不重要的元素)元组)。
创建唯一的名称
别
使用返回的值erlang:now/0
在当前运行时系统实例上创建唯一的名称。
做
使用返回的值erlang:unique_integer/0
在当前运行时系统实例上创建唯一的名称。如果你只想要正整数,你可以使用erlang:unique_integer([positive])
。
具有唯一值的种子随机数生成
别
使用种子随机数生成erlang:now()
。
做
种子的随机数生成使用的组合erlang:monotonic_time()
,erlang:time_offset()
,erlang:unique_integer()
,和其他功能。
总结本节:不要使用
erlang:now/0**
。**
3.8支持新旧OTP版本
可能需要您的代码必须在不同的OTP版本的各种OTP安装上运行。如果是这样,那么您不能直接使用新的API,因为它在OTP 18之前的版本中不可用。解决方案不是
避免使用新的API,因为您的代码不会从可伸缩性和准确性改进中受益制作。相反,在可用时使用新API,并erlang:now/0
在新API不可用时再使用。
幸运的是,大多数新的API可以使用现有的基元轻松实现,除了:
erlang:system_info(start_time)
erlang:system_info(end_time)
erlang:system_info(os_monotonic_time_source)
erlang:system_info(os_system_time_source)
%29
通过erlang:now/0
在新API不可用时使用函数包装API ,并使用这些包装器而不是直接使用API,问题就解决了。例如,这些包装可以按照实现$ERL_TOP/erts/example/time_compat.erl
。