惊艳的时间轮定时器

2023-11-18

问题引入:游戏里面每个Player身上有很多buffs,在每一个tick(最小时间段)都要去检查buff里面的每一个buff是不是过期,产生的效果如何,造成在每个tick里面都去遍历一个长list,明显很不好。

怎么优化?

1.原始模型:

model1_thumb19

buff的状态在每一个tick里面都要更新!可以想象指针每移动一下,都会非常沉重地拖着所有的BuffList,好可怕……

2. 优化模型1:

我们要避免的是:原始模型在每一个tick里面都要遍历List,那么我们试下以Times为key,在加入buff里时分配好它的结束和启作用的时间属于哪一个Time,

model2_thumb14

这个模型要注意的问题:当要加的Buff起效果已超过了一轮Tick总数时! 比如时间轮总Tick数为12个,现在指针到了tick=2处,要加一个再经过tick为15(起效果)的buff,怎么办?

可以算得:2 + 15%12 = 5,把此buff放到tick=5的槽里面(每个buff都会记录下它的结束时间的),待tick从2跳一轮回到2再跳3下到tick=5,这个buff就会执行。

这个模型完美解决原始模型(每个Tick都遍历整个BuffList)的问题,似乎很完美哦,但是却引入了新的问题,我们的最小tick明显不可能以小时计算,如果我们把Tick划分到秒级别, 一轮就有12*3600 = 43200个key,

假如有一个Buff在每3s就起一个效果:(每3秒加一次血),那么这个buff就会被储存43200/3 = 14400个!!!!如果有大量这种buff,数据冗余就会非常大,储存空间随之也非常大……

要做到保证精度的同时,又保证效率,怎么两全呢?请看模型3.

3. 优化模型3

clock4_thumb14

网上下了个非常cool的时钟图做示例啦:

这就是要用分层时间轮模型:

%%% 1.时钟原理说明:
%%% 1.1. 初始化一个三层时间轮:秒刻盘:0~59个SecList, 分刻盘:0~59个MinList, 时刻盘:0~12个HourList;
%%% 1.2. SecTick由外界推动,每跳一轮(60格),SecTick复位至0,同时MinTick跳1格;
%%% 1.3. 同理MinTick每跳一轮(60格),MinTick复位至0,同时HourTick跳1格;
%%% 1.4. 最高层:HourTick跳一轮(12格),HourTick复位至0,一个时间轮完整周期完成.
%%% 2.事件原理说明:
%%% 2.1. 设置时间为TimeOut的事件时,根据TimeOut算出发生此事件时刻的指针位置{TriggerHour,TriggerMin,TriggerSec};
%%% 2.2. 用{TriggerHour,TriggerMin,TriggerSec}与当前指针{NowHour,NowMin,NowSec}对比得出事件存放在哪一个指针(Tick);
%%% 2.3. 所有层的指针每跳到下一格(Tick01)都会触发格子的事件列表,处理每一个事件Event01:
%%% 2.3.1 根据事件Event01的剩余TimeOut算出Event01应该存在上一层(跳得更快)层的位置Pos;
%%% 2.3.2 把事件更新到新的Pos(更新TimeOut);
%%% 2.3.3 重复处理完Tick01里面所有的事件;
%%% 2.3.4 清空Tick01的事件;
%%% 2.3.5 最底层(跳最快)层所有的事件遇到指针Tick都会立即执行;

我自己用Erlang实现了一个分层时间轮,有兴趣也可以参观下:)知易行难 欢迎大家用自己善长的语言造个漂亮的轮子,自己亲手写还是可以发现里面很多有意思的细节啦.

https://gist.github.com/zhongwencool/eca6609b59ed635de164




译文:Real-Time Concepts for Embedded Systems Chapter 11 Timer and Timer Services

http://www.embeddedlinux.org.cn/RTConforEmbSys/5107final/LiB0071.html

上面buff的优化思路也是来源于此,非常简单易懂的时间轮概念.

11.6 时间轮(time wheel)

如下图Figure11.11所示:时间轮是一个固定大小的数组结构,这个数组的每一个槽(元素)代表着软定时器的精度,(类似于时钟的最小刻度).时间轮的优点:通过排序的时间列表来有效的更新timers.它能非常效率地安装(instaallation),取消(cancellation)timer.(这2个操作的时间复杂度o(1)).

Figrue11.6_thumb5

Figure 11.11 timing wheel.

软时间设备(soft-timer facility)使用硬时间(hadware timer)来确定一个最小的刻度(tick).基于硬时间周期的timer,驱动着所有安装在这上面的软时间. 超时(timeout)的频率决定着软时间的精度,比如:如果定义tick精度为50ms,每个槽(slot)就代表50ms,这也是可以在这个timer里面安装的最小timeout事件了. 同时,一个双向链表会把timeout的事件处理(event handlers)(也叫callback funciton or callbacks)保存在每一个槽中,当timer 过期(expired)时会触发callbacks调用。所以timers列表也代表着过期时间处理事件列表。每个时间槽描述如图Figure11.12

1112_thumb2

Figure 11.12: Timeout event handlers.

时钟转盘每过一个tick就会指向下一时间(next time),当指针指到数组的最后一个槽时,下一时间又会回到指针最开始的槽。时间轮的概念就来自于此。因此:当安装一个新的事件(time event)时,转盘当前的位置决定了这个新事件到底应该放在哪一个槽,如下图Figure11.13所描述,每经过一个槽代表过去50ms

1113_thumb3

Figure 11.13: Installing a timeout event.

这个时间槽标记了如果有人想安装一个200ms的timeout事件,就可以把这个事件放在++200的槽中,当然这个转盘的起始位置在槽的开始位置(图中clock dial指的位置),换句话说:当转盘(clock dial)指向最开始的槽时,这个事件处理就会返回应该是数组的下标(index).

11.6.1 问题(Issues)

上面这个时间轮方法存在的系列的问题:

问题1: 槽的数量是有限的(也许不同的系统会有不同的限制),比如:你可以在Figure11.13非常明显地看出来:最大的可处理长度(总槽度)是350ms,当要安装一个400ms的事件时怎么办?这个会引起时间轮溢出,为了解决这个问题:一种方法就是禁止超过范围的事件安装,另一个更好的方法:把这些溢出的事件放在一个统一的事件缓冲(event buffer)里面,等转盘转到下一刻度时就从buffer中取出符合范围的事件,这样这些事件也可以被处理了,你可仔细研究Figure11.14得到答案:

1114_thumb1

Figure 11.14: Timing wheel overflow event buffer.

比如:当转盘位置在0刻度(图中位置1)处时,同时要安装一个400ms的timeout,这个事件必须暂时存在溢出缓冲buffer里面,随着转盘转到+50ms(图中位置2处),就会从缓冲区取出这个事件安装. 同理:500ms的事件也只能是在转盘到+150ms(图中位置3)处才能安装。转盘每指向下一刻时,都会检查这个事件缓冲区,这就要求缓冲区里面的事件列表是正增长,如果这个列表很长,那么新的事件插入时代价会非常大。

问题2:这个时间轮的精度,试想一下:当tick指到time wheel 到开始指向下一个时间刻度前,如又安装一个150ms的事件,那么这个事件是安装在+150ms,还是在+200呢?按平均来讲,出错的概率均等的情况下,那么这个出错可能会延迟或提前最小刻度的一半,在这里就是50ms/2=25ms.

问题3:非常重要的问题:关于callbacks的安装.理论上,每一个Callback都应该在时间过期时同时发生,但是在现实中,这是不可能的,每一个Callback的工作状态都不可预测,因此,执行的每一个callback的长度也不可预测,导致没有方法可以保证一个在很长列表后面的callback会被马上执行,这个问题是不合需求的,不能引放到系统里面。Figure11.15描述了这个问题:

1115_thumb4

Figure 11.15: Unbounded soft-timer handler invocation.

当t1 timeout刚过期时,事件处理函数1马上发生,类似,事件处理函数n会在到过t(n-1)时被触发,由于每一个处理函数的执行长度是不确定的,所有图中x,y是长度也是不定的。

理论上(Ideally),这个时间处理会规定一个处理事件的上限值;比如:当执行到事件处理函数n时距离事件处理函数1开始已超过200ms时,会把没有执行的其它事件忽略掉。这个问题很难,解决方法也是应用程序自己特定的[译注:可以点这里参见Linux下的实现]。

11.6.2 分层时间轮(Hierarchical Timing Wheels)

Figure11.14里面的问题1:溢出问题可以使用分层时间轮的方法解决。

软时间设备需要适应事件在跨越在不同范围的值,这个跨度可以非常大,比如:适应timers 范畴从100ms到5 分钟需要3000((5 × 60 × 10)跨度的时间轮,因为这个时间轮的精度最少要100ms,这也是此时间轮的最小精度啦:

 10 × 100ms = 1 sec
    10 entries/sec
    60 sec = 1 minute
    60 × 10 entries / min 
    因此: 
    5 × 60 × 10 =需要3000个刻度(entries).

一个分层的时间轮就好像一个数字刻盘指针型时钟,用多个时间轮安装在这个分层结构里面,取代上面单一的时间轮。这里面每个时间轮都有自己的粒度(granularity)精度,时间转盘与所有的时间轮联系在一起,当最低层的时间轮转一轮时,上一层的时间轮就转一个单位。使用分层时间轮刚上面的需要3000entries的现在仅需要75(10 + 60 + 5)entries就可以保证timeout从100ms到5分钟。这里用到的多维数组:

 

 10 × 100ms = 1 sec
    10 entries/sec
    60 sec = 1 minute
    60 entries / min
    5 entries for 5 minutes
    因此:
    5 + 60 + 10 =只需要75个刻度(entries)

1116_thumb4

Figure 11.16: A hierarchical timing wheel

这个模型不仅节省了大量的空间,并且保持着很高的精度和跨度, Figure11.16说明了这一点。
举个例子:它可能会安装一个2分4秒300ms处timeout事件。首先安装2min,当2分钟发生时,它检查还有4.3s的事件才能timeout,所以它又安装了4s的timeout事件槽,当4s过去后,检查到还有300ms才能timeout,又安装了一个300ms事件,再过300ms,真正的timeout才会被执行.

如果你觉得上面意犹未尽:这里面还有一个大餐哦:

1.关于Linux 下定时器的实现方式分析 http://www.ibm.com/developerworks/cn/linux/l-cn-timers/

2. 浅析 Linux 中的时间编程和实现原理 http://www.ibm.com/developerworks/cn/linux/1308_liuming_linuxtime3/


时间轮是不是很神奇:上面译文有说到:分层模型Figure11.16节省了大量的空间?能说说是怎么做到的么,想想,事件假如说有1000个事件,这些事件的空间怎么也不可以被减少,那么它指的是什么空间呢?

如果你知道,请不要吝啬 :)

转载于:https://www.cnblogs.com/zhongwencool/p/timing_wheel.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

惊艳的时间轮定时器 的相关文章

  • 以OpenGL/ES视角介绍gfx-hal(Vulkan) Shader/Program接口使用

    文档列表见 Rust 移动端跨平台复杂图形渲染项目开发系列总结 目录 背景 The right way to tackle this in Vulkan is to use resource descriptors A descriptor
  • 50个c/c++源代码网站

    C C 是最主要的编程语言 这里列出了50名优秀网站和网页清单 这些网站提供c c 源代码 这份清单提供了源代码的链接以及它们的小说明 我已尽力包括最佳的C C 源代码的网站 这不是一个完整的清单 您有建议可以联系我 我将欢迎您的建 议 以
  • Sort List

    Sort a linked list in O n log n time using constant space complexity 题目要求用 O n log n 的时间复杂度和常数的空间复杂度来进行链表排序 O nlogn 的排序算
  • 直线检测方法—LSD论文翻译

    附原文链接 LSD a Line Segment Detector 摘 要 LSD是一个线段检测器 能够在线性时间内得到亚像素级精度的检测结果 它无需调试参数就可以适用于任何数字图像上 并且能够自我控制错误数量的检测 平均来说 一个图像中允
  • 第二十八节、基于深度学习的目标检测算法的综述(附代码,并附有一些算法英文翻译文章链接))...

    在前面几节中 我们已经介绍了什么是目标检测 以及如何进行目标检测 还提及了滑动窗口 bounding box 以及IOU 非极大值抑制等概念 这里将会综述一下当前目标检测的研究成果 并对几个经典的目标检测算法进行概述 本文内容来自基于深度学
  • (笔试前准备)字符串匹配算法总结

    我想说一句 我日 我讨厌KMP KMP虽然经典 但是理解起来极其复杂 好不容易理解好了 便起码来巨麻烦 老子就是今天图书馆在写了几个小时才勉强写了一个有bug的 效率不高的KMP 特别是计算next数组的部分 其实 比KMP算法速度快的算法
  • Hash table and application in java

    查找的效率取决于在查找是比较的次数 次数越少效率越高 反之越低 最理想的情况是无需比较 一次存取便能找到所查找的记录 根据对应关系f找到给定值K的像f K hash function 应运而生 由此思想建的表称为hash table 集合h
  • findBug 错误修改指南

    FindBugs错误修改指南 1 EC UNRELATED TYPES Bug Call to equals comparing different types Pattern id EC UNRELATED TYPES type EC c
  • 链表和线性表的优缺点

    链表和线性表的优缺点 作为我们最先接触的两个数据结构 链表和线性表的优缺点都较为明显 并且二者互相补足 文章目录 链表和线性表的优缺点 线性表 线性表的组成 线性表的缺点 线性表的优点 链表 链表的组成 链表的优点 链表的缺点 总结 线性表
  • 逆波兰表达式求值(C语言实现)

    实验项目 从文本文件输入任意一个语法正确的 中缀 表达式 显示并保存该表达式 利用栈结构 把上述 中缀 表达式转换成后缀表达式 并显示栈的状态变化过程和所得到的后缀表达式 利用栈结构 对上述后缀表达式进行求值 并显示栈的状态变化过程和最终结
  • 数据结构与算法书籍推荐

    学习数据结构与算法 还是很有必要看几本相关的书籍 但根据不同基础的人 合适看的书也不一样 因此 针对不同层次 不同语言的人 推荐几本市面上口碑不错的书 1 入门级 针对刚入门的同学 建议不要急着去看那些经典书 像 算法导论 算法 这些比较经
  • 图 - Java实现无向带权图的邻接矩阵表示法

    图 Java实现无向带权图的邻接矩阵表示法 1 图 1 1 图的介绍 图 Graph 是一种复杂的非线性表结构 图中的元素我们就叫做顶点 vertex 图中的一个顶点可以与任意其他顶点建立连接关系 我们把这种建立的关系叫做边 edge 跟顶
  • 数据结构——计算节点个数和二叉树高度(C语言版)

    摘自 数据结构 计算节点个数和二叉树高度 C语言版 作者 正弦定理 发布时间 2020 12 12 23 27 09 网址 https blog csdn net chinesekobe article details 111086664
  • OJ-合并两个有序链表

    题目描述 代码如下 Definition for singly linked list struct ListNode int val struct ListNode next struct ListNode mergeTwoLists s
  • Linux 内核中的 Device Mapper 机制

    Linux 内核中的 Device Mapper 机制 尹 洋 在读博士生 尹洋 中科院计算所国家高性能计算机工程技术研究中心的在读博士生 主要从事服务部署和存储资源管理以及Linux块设备一级的开发和研究工作 简介 本文结合具体代码对 L
  • Linux下进程退出的几种形式

    进程退出 Linux 下进程的退出分为正常退出和异常退出两种 1 正常退出 a 在main 函数中执行return b 调用exit 函数 c 调用 exit 函数 2 异常退出 a 调用about函数 b 进程收到某个信号 而该信号使程序
  • 基数排序代码实现

    详情请看排序总结 传送门 https blog csdn net m0 52711790 article details 121914543 基数排序的知识点我就不贴出来 相信都能搜到对应概念解释 下面就直接上代码 代码解释其实也很清晰了
  • Leetcode2661. 找出叠涂元素

    Every day a Leetcode 题目来源 2661 找出叠涂元素 解法1 哈希 题目很绕 理解题意后就很简单 由于矩阵 mat 中每一个元素都不同 并且都在数组 arr 中 所以首先我们用一个哈希表 hash 来存储 mat 中每
  • 浅谈归并排序:合并 K 个升序链表的归并解法

    在面试中遇到了这道题 如何实现多个升序链表的合并 这是 LeetCode 上的一道原题 题目具体如下 用归并实现合并 K 个升序链表 LeetCode 23 合并K个升序链表 给你一个链表数组 每个链表都已经按升序排列 请你将所有链表合并到
  • 最大流-Dinic算法,原理详解,四大优化,详细代码

    文章目录 零 前言 一 概念回顾 可略过 1 1流网络 1 2流 1 3最大流 1 4残留网络 1 5增广路

随机推荐

  • Java中创建事件监听器的五种方法

    在Java中处理事件的办法最原始的方法如下 一 使用内部类 一个个设置Button然后创建一个内部类 用ActionPerformed来实现按钮事件内容 import javax swing import java awt import j
  • jdk下载

    需要注册oracle官网的账号 下载地址如下所示 https www oracle com technetwork java javase downloads jdk8 downloads 2133151 html
  • 全面讲解 C 语言的结构体(struct),一网打尽

    点击蓝字 关注我们 因公众号更改推送规则 请点 在看 并加 星标 第一时间获取精彩技术分享 来源于网络 侵删 结构体的定义 结构体 struct 是由一系列具有相同类型或不同类型的数据构成的数据集合 也叫结构 结构体和其他类型基础数据类型一
  • Unity 3D 游戏中实现人物头上血条(血条是在 overlay 的 canvas 下)

    UI 层的血条http www manew com home php mod space uid 234410 do blog quickforward 1 id 43799
  • 给缺少Python项目实战经验的人

    我们在学习过程中最容易犯的一个错误就是 看的多动手的少 特别是对于一些项目的开发学习就更少了 没有一个完整的项目开发过程 是不会对整个开发流程以及理论知识有牢固的认知的 对于怎样将所学的理论知识应用到实际开发中更是不得而知了 以上就是我们在
  • 入门知识(一)矢量图与位图的区别

    矢量图与位图有什么区别 转自https jingyan baidu com article 54b6b9c0dbef682d583b4722 html 分步阅读 前几日有同事总是不时的问我什么是矢量图什么是位图及它们之间有什么区别 今天咱们
  • 打印1-100中3的倍数 (C语言)

    代码 include
  • MySQL安装时出现无法正常启动的问题

    我刚在官网下载了MySQL8 0 18的最新压缩包版本 跟着网络上的安装教程走 发现在cmd窗口用net start mysql命令无法正常启动 在查看my ini文件和环境变量配置没有问题之后 重新以管理员身份打开cmd窗口 仍然失败 百
  • LeetCode 2011. 执行操作后的变量值

    存在一种仅支持 4 种操作和 1 个变量 X 的编程语言 X 和 X 使变量 X 的值 加 1 X 和 X 使变量 X 的值 减 1 最初 X 的值是 0 给你一个字符串数组 operations 这是由操作组成的一个列表 返回执行所有操作
  • python--socket(套接字/插口)

    socket是什么 是进程间通信的一种方式 它与其他进程间通信的一个主要不同是 它能实现不同主机之间的进程通信 我们网络上各种各样的服务大多都是基于socket来完成通信的 例如我们浏览网页 qq聊天 收发emil Socket是应用层与T
  • 民营经济挑战未来发展

    上周末 一场 中国民营经济六十年研讨会 在北京聚集了改革领域的多位高官和专家 曲折和成就 经验和教训 理论问题和现实问题 都在会议上碰撞 此次会议由中央社会主义学院 中国经济体制改革研究会 中国民 私 营经济研究会 北京开达经济学家咨询中心
  • 创建git项目并提交

    1 创建仓库 2 点击创建 3复制gitee码云的HttpS连接 4 提交上传 打开项目并点击菜单栏上的 CVS Import into version control Create Git Repository 创建本地仓库 在打开的 C
  • 小米笔记本Pro安装Win+Mac双系统,时间同步不一致问题!

    安装win和Mac 双系统 时间同步不一样的问题 可以通过补丁解决 Win注册表CMD注入或Mac下安装注入 二选一打补丁 1 Win下操作以管理员运行CMD命令行Reg add HKLM SYSTEM CurrentControlSet
  • 基于时空网络的出租车OD需求预测-简介

    最近单曲循环的一首歌 分享给大家 1 文章信息 Contextualized Spatial Temporal Network for Taxi rigin Destination Demand Prediction 2019发在IEEE
  • RecyclerView应用复习

    导包 implementation androidx recyclerview recyclerview 1 1 0 recyclerview implementation com zhy base rvadapter 3 0 3 adap
  • AttributeError: module ‘torch.cuda.amp‘ has no attribute ‘autocast‘

    参考 https zhuanlan zhihu com p 165152789 https zhuanlan zhihu com p 176998729 https pytorch org docs stable amp html http
  • 渠道系统和 OA系统待办事项接口

    OA待办 已办 以及通过ltpatoken查找用户拼音接口 接口采用http get方式 将需要的参数传入 Content Type application json charset UTF 8 getMethod addRequestHe
  • 错误: 无法从静态上下文中引用非静态 变量 this

    JAVA菜鸟笔记 错误 无法从静态上下文中引用非静态 变量 this 1 09 17 Hello java 错误 无法从静态上下文中引用非静态 变量 this 错误原因 main方法是一个静态方法 而静态方法中无法引用非静态变量 因为静态方
  • STC单片机 延时 那点事,DS18B20的苦

    DS18B20采用 一线总线 对时序的要求是特高啊 要想精准延时 有两个选择 其一当属定时器 其二用汇编一条一条的来算 但 DS18B20延时的时候 以上两条都不会选 还有其他选择 第三方的Delay函数 比如STC ISP VXX X提供
  • 惊艳的时间轮定时器

    问题引入 游戏里面每个Player身上有很多buffs 在每一个tick 最小时间段 都要去检查buff里面的每一个buff是不是过期 产生的效果如何 造成在每个tick里面都去遍历一个长list 明显很不好 怎么优化 1 原始模型 buf