• 框架中实现 小堆顶高性能定时器 10W计时器耗时1.9ms


    1 小顶堆计时器概要

     小根堆定时器的优点如下:

    a.添加时间复杂度为O(1);

    b.删除时间复杂度为O(1);

    c.执行一个定时器的时间复杂度为O(1);

    2 代码设计

            之前写的服务器定时器是全部轮询更新,这种计时器性能太差,每一帧都要全部迭代一次,客户端层应该把CPU性能压榨到极致,能少循环的尽量少循环尽可能的减少CPU循环次数,所以优化了算法,使用了小堆顶定时器。小顶堆是基于二叉树的排序算法,将剩余时间最小的节点交换到树的根节点。每次更新的时候只取树的根节点,判断是否超时,如果超时会对树重新进行排序,排序完成后继续轮询,查询到根节点无超时为止。

    Timer.cs代码实现

    1. using UnityEngine;
    2. using System;
    3. using Object = UnityEngine.Object;
    4. namespace UnityTimer
    5. {
    6. public class Timer
    7. {
    8. public enum SCOPE
    9. {
    10. eLocal,
    11. eGlobal,
    12. }
    13. #region Public Properties/Fields
    14. ///
    15. /// 计时器回调时间持续时间
    16. ///
    17. public float duration { get; private set; }
    18. ///
    19. /// 执行完成后是否循环执行.
    20. ///
    21. public bool isLooped { get; set; }
    22. ///
    23. /// 本帧是否完成
    24. ///
    25. public bool isCompletedThisFrame { get; private set; }
    26. ///
    27. /// 是否完成
    28. ///
    29. public bool isCompleted { get; private set; }
    30. ///
    31. /// 计时器使用的是实时时间还是游戏时间
    32. ///
    33. public bool usesRealTime { get; private set; }
    34. ///
    35. /// 计时器是否暂停
    36. ///
    37. public bool isPaused
    38. {
    39. get { return this._timeElapsedBeforePause.HasValue; }
    40. }
    41. ///
    42. /// 是否取消了
    43. ///
    44. public bool isCancelled
    45. {
    46. get { return this._timeElapsedBeforeCancel.HasValue; }
    47. }
    48. ///
    49. /// 是否完成
    50. ///
    51. public bool isDone
    52. {
    53. get { return this.isCompleted || this.isCancelled || this.isOwnerDestroyed; }
    54. }
    55. #endregion
    56. #region Public Static Methods
    57. ///
    58. /// 注册一个新的计时器,在一定时间后触发一个事件
    59. /// 在切换场景的时候,注册的计时器会被销毁
    60. ///
    61. /// 在一定秒后执行事件
    62. /// 计时器执行完回调事件.
    63. /// 每次执行计时器执行的回调
    64. /// 计时器在执行后是否重复执行
    65. /// 是否使用实时时间
    66. /// 此计时器附加到的对象,物体被摧毁后,定时器将不执行
    67. /// 一个计时器对象
    68. public static Timer Regist(float duration, Action onComplete, Action<float> onUpdate = null,
    69. bool isLooped = false, bool useRealTime = false, MonoBehaviour autoDestroyOwner = null)
    70. {
    71. if (Timer._manager == null)
    72. {
    73. TimerManager managerInScene = Object.FindObjectOfType();
    74. if (managerInScene != null)
    75. {
    76. Timer._manager = managerInScene;
    77. }
    78. else
    79. {
    80. GameObject managerObject = new GameObject { name = "TimerManager" };
    81. Timer._manager = managerObject.AddComponent();
    82. }
    83. }
    84. Timer timer = new Timer(duration, onComplete, onUpdate, isLooped, useRealTime, autoDestroyOwner);
    85. Timer._manager.RegisterTimer(timer);
    86. return timer;
    87. }
    88. ///
    89. /// 作用同上,不同的是此API创建的计时器在程序的生命周期内都有效,不会随着场景的销毁而销毁
    90. ///
    91. public static Timer RegistGlobal(float duration, Action onComplete, Action<float> onUpdate = null,
    92. bool isLooped = false, bool useRealTime = false, MonoBehaviour autoDestroyOwner = null)
    93. {
    94. if (Timer._globalManager == null)
    95. {
    96. GlobalTimerManager globalManager = Object.FindObjectOfType();
    97. if (globalManager != null)
    98. {
    99. Timer._globalManager = globalManager;
    100. }
    101. else
    102. {
    103. GameObject globalManagerObject = new GameObject { name = "GlobalTimerManager" };
    104. Timer._globalManager = globalManagerObject.AddComponent();
    105. GameObject.DontDestroyOnLoad(Timer._globalManager);
    106. }
    107. }
    108. Timer timer = new Timer(duration, onComplete, onUpdate, isLooped, useRealTime, autoDestroyOwner);
    109. Timer._globalManager.RegisterTimer(timer);
    110. return timer;
    111. }
    112. public static void Cancel(Timer timer)
    113. {
    114. if (timer != null)
    115. {
    116. timer.Cancel();
    117. }
    118. }
    119. public static void Pause(Timer timer)
    120. {
    121. if (timer != null)
    122. {
    123. timer.Pause();
    124. }
    125. }
    126. public static void Resume(Timer timer)
    127. {
    128. if (timer != null)
    129. {
    130. timer.Resume();
    131. }
    132. }
    133. public static void CancelAllRegisteredTimers(SCOPE eScope = SCOPE.eLocal)
    134. {
    135. //如果计时器不存在,不需要做任何事情
    136. if (eScope == SCOPE.eLocal)
    137. {
    138. if (Timer._manager != null)
    139. {
    140. Timer._manager.CancelAllTimers();
    141. }
    142. }
    143. else if (eScope == SCOPE.eGlobal)
    144. {
    145. if (Timer._globalManager != null)
    146. {
    147. Timer._globalManager.CancelAllTimers();
    148. }
    149. }
    150. }
    151. public static void PauseAllRegisteredTimers(SCOPE eScope = SCOPE.eLocal)
    152. {
    153. //如果计时器不存在,不需要做任何事情
    154. if (eScope == SCOPE.eLocal)
    155. {
    156. if (Timer._manager != null)
    157. {
    158. Timer._manager.PauseAllTimers();
    159. }
    160. }
    161. else if (eScope == SCOPE.eGlobal)
    162. {
    163. if (Timer._globalManager != null)
    164. {
    165. Timer._globalManager.PauseAllTimers();
    166. }
    167. }
    168. }
    169. public static void ResumeAllRegisteredTimers(SCOPE eScope = SCOPE.eLocal)
    170. {
    171. //如果计时器不存在,不需要做任何事情
    172. if (eScope == SCOPE.eLocal)
    173. {
    174. if (Timer._manager != null)
    175. {
    176. Timer._manager.ResumeAllTimers();
    177. }
    178. }
    179. else if (eScope == SCOPE.eGlobal)
    180. {
    181. if (Timer._globalManager != null)
    182. {
    183. Timer._globalManager.ResumeAllTimers();
    184. }
    185. }
    186. }
    187. #endregion
    188. #region Public Methods
    189. public void Cancel()
    190. {
    191. if (this.isDone)
    192. {
    193. return;
    194. }
    195. this._timeElapsedBeforeCancel = this.GetTimeElapsed();
    196. this._timeElapsedBeforePause = null;
    197. }
    198. public void Pause()
    199. {
    200. if (this.isPaused || this.isDone)
    201. {
    202. return;
    203. }
    204. this._timeElapsedBeforePause = this.GetTimeElapsed();
    205. }
    206. public void Resume()
    207. {
    208. if (!this.isPaused || this.isDone)
    209. {
    210. return;
    211. }
    212. this._timeElapsedBeforePause = null;
    213. }
    214. public float GetTimeElapsed()
    215. {
    216. if (this.isCompleted || this.GetWorldTime() >= this.GetFireTime())
    217. {
    218. return this.duration;
    219. }
    220. return this._timeElapsedBeforeCancel ??
    221. this._timeElapsedBeforePause ??
    222. this.GetWorldTime() - this._startTime;
    223. }
    224. public float GetTimeRemaining()
    225. {
    226. return this.duration - this.GetTimeElapsed();
    227. }
    228. public float GetRatioComplete()
    229. {
    230. return this.GetTimeElapsed() / this.duration;
    231. }
    232. public float GetRatioRemaining()
    233. {
    234. return this.GetTimeRemaining() / this.duration;
    235. }
    236. #endregion
    237. #region Private Static Properties/Fields
    238. private static TimerManager _manager;
    239. private static GlobalTimerManager _globalManager;
    240. #endregion
    241. #region Private Properties/Fields
    242. private bool isOwnerDestroyed
    243. {
    244. get { return this._hasAutoDestroyOwner && this._autoDestroyOwner == null; }
    245. }
    246. private readonly Action _onComplete;
    247. private readonly Action<float> _onUpdate;
    248. private float _startTime;
    249. private float _endTime;
    250. private float _lastUpdateTime;
    251. private float? _timeElapsedBeforeCancel;
    252. private float? _timeElapsedBeforePause;
    253. private readonly MonoBehaviour _autoDestroyOwner;
    254. private readonly bool _hasAutoDestroyOwner;
    255. #endregion
    256. #region 属性区
    257. public float EndTime { get { return _endTime; } }
    258. #endregion
    259. #region Private Constructor (use static Register method to create new timer)
    260. private Timer(float duration, Action onComplete, Action<float> onUpdate,
    261. bool isLooped, bool usesRealTime, MonoBehaviour autoDestroyOwner)
    262. {
    263. this.duration = duration;
    264. this._onComplete = onComplete;
    265. this._onUpdate = onUpdate;
    266. this.isLooped = isLooped;
    267. this.usesRealTime = usesRealTime;
    268. this._autoDestroyOwner = autoDestroyOwner;
    269. this._hasAutoDestroyOwner = autoDestroyOwner != null;
    270. this._startTime = this.GetWorldTime();
    271. this._lastUpdateTime = this._startTime;
    272. _endTime = _startTime + duration;
    273. }
    274. #endregion
    275. #region Methods
    276. public float GetWorldTime()
    277. {
    278. return this.usesRealTime ? Time.realtimeSinceStartup : Time.time;
    279. }
    280. private float GetFireTime()
    281. {
    282. return this._startTime + this.duration;
    283. }
    284. private float GetTimeDelta()
    285. {
    286. return this.GetWorldTime() - this._lastUpdateTime;
    287. }
    288. public void Update()
    289. {
    290. isCompletedThisFrame = false;
    291. if (this.isDone)
    292. {
    293. return;
    294. }
    295. if (this.isPaused)
    296. {
    297. this._startTime += this.GetTimeDelta();
    298. this._lastUpdateTime = this.GetWorldTime();
    299. return;
    300. }
    301. this._lastUpdateTime = this.GetWorldTime();
    302. if (this._onUpdate != null)
    303. {
    304. this._onUpdate(this.GetTimeElapsed());
    305. }
    306. if (this.GetWorldTime() >= this.GetFireTime())
    307. {
    308. isCompletedThisFrame = true;
    309. if (this._onComplete != null)
    310. {
    311. this._onComplete();
    312. }
    313. if (this.isLooped)
    314. {
    315. this._startTime = this.GetWorldTime();
    316. _endTime = _startTime + duration;
    317. }
    318. else
    319. {
    320. this.isCompleted = true;
    321. }
    322. }
    323. }
    324. #endregion
    325. }
    326. }

    TimerMgr.cs代码实现

    1. using System.Collections.Generic;
    2. using UnityEngine;
    3. using System;
    4. using Object = UnityEngine.Object;
    5. namespace UnityTimer
    6. {
    7. public class TimerManager : MonoBehaviour
    8. {
    9. //protected List _timers = new List();
    10. //初始化默认new 1个空间出来
    11. protected Timer[] m_szTimers = new Timer[1];
    12. //已使用的空间
    13. protected uint m_iUsedSize = 0;
    14. //protected List _timersToAdd = new List();
    15. protected void Resize()
    16. {
    17. int iOldCapacity = m_szTimers.Length;
    18. int iNewCapacity = iOldCapacity * 2;
    19. Timer[] szTempTimer = new Timer[iNewCapacity];
    20. //尾部全部设置成null
    21. for (int i = iOldCapacity; i < iNewCapacity; ++i)
    22. {
    23. szTempTimer[i] = null;
    24. }
    25. //copy oldData -> newData
    26. Array.Copy(m_szTimers, szTempTimer, m_szTimers.Length);
    27. //指向新地址
    28. m_szTimers = szTempTimer;
    29. //解除引用
    30. szTempTimer = null;
    31. }
    32. ///
    33. /// 小顶堆排序
    34. ///
    35. public void HeapAdjustSmall(int parent)
    36. {
    37. if (parent >= m_szTimers.Length)
    38. {
    39. return;
    40. }
    41. Timer tmp = m_szTimers[parent];
    42. //时间复杂度应该在O(LogN)附近
    43. for (int child = parent * 2 + 1; child < m_iUsedSize; child = child * 2 + 1)
    44. {
    45. if (child + 1 < m_iUsedSize && m_szTimers[child].EndTime > m_szTimers[child + 1].EndTime)
    46. {
    47. child++;
    48. }
    49. if (tmp.EndTime > m_szTimers[child].EndTime)
    50. {
    51. m_szTimers[parent] = m_szTimers[child];
    52. parent = child;
    53. }
    54. else
    55. {
    56. break;
    57. }
    58. }
    59. m_szTimers[parent] = tmp;
    60. }
    61. public void AddTimer(Timer timer)
    62. {
    63. if (null == timer)
    64. {
    65. return;
    66. }
    67. if (m_iUsedSize >= m_szTimers.Length)
    68. {
    69. Resize();
    70. }
    71. uint hole = m_iUsedSize;
    72. ++m_iUsedSize;
    73. // 由于新结点在最后,因此将其进行上滤,以符合最小堆
    74. for (uint parent = (hole - 1) / 2; hole > 0; parent = (hole - 1) / 2)
    75. {
    76. //把时间最短的计时器交换到树根节点
    77. if (m_szTimers[parent].EndTime > timer.EndTime)
    78. {
    79. m_szTimers[hole] = m_szTimers[parent];
    80. hole = parent;
    81. }
    82. else
    83. {
    84. break;
    85. }
    86. }
    87. m_szTimers[hole] = timer;
    88. }
    89. public void PopTimer()
    90. {
    91. if (0 == m_iUsedSize)
    92. {
    93. return;
    94. }
    95. if (null != m_szTimers[0])
    96. {
    97. m_szTimers[0] = m_szTimers[--m_iUsedSize];
    98. HeapAdjustSmall(0);
    99. }
    100. }
    101. public void RegisterTimer(Timer timer)
    102. {
    103. AddTimer(timer);
    104. }
    105. public void CancelAllTimers()
    106. {
    107. Timer timer = null;
    108. for (int i = 0; i < m_szTimers.Length; ++i)
    109. {
    110. timer = m_szTimers[i];
    111. if (null != timer)
    112. {
    113. timer.Cancel();
    114. m_szTimers[i] = null;
    115. }
    116. }
    117. m_iUsedSize = 0;
    118. }
    119. public void PauseAllTimers()
    120. {
    121. Timer timer = null;
    122. for (int i = 0; i < m_szTimers.Length; ++i)
    123. {
    124. timer = m_szTimers[i];
    125. if (null != timer)
    126. timer.Pause();
    127. }
    128. }
    129. public void ResumeAllTimers()
    130. {
    131. Timer timer = null;
    132. for (int i = 0; i < m_szTimers.Length; ++i)
    133. {
    134. timer = m_szTimers[i];
    135. if (null != timer)
    136. timer.Resume();
    137. }
    138. }
    139. protected void Update()
    140. {
    141. UpdateAllTimers();
    142. }
    143. protected void UpdateAllTimers()
    144. {
    145. Timer tm = null;
    146. //for (int i = 0; i < m_szTimers.Length; ++i)
    147. //{
    148. // tm = m_szTimers[i];
    149. // if (null != tm)
    150. // tm.Update();
    151. //}
    152. Timer tmp = null;
    153. tmp = m_szTimers[0];
    154. while (m_iUsedSize > 0)
    155. {
    156. if (null == tmp)
    157. break;
    158. tmp.Update();
    159. //循环类型的计时器,如果到了时间,重新排序,而不清理
    160. if (tmp.isCompletedThisFrame && tmp.isLooped)
    161. {
    162. HeapAdjustSmall(0);
    163. tmp = m_szTimers[0];
    164. continue;
    165. }
    166. if (!tmp.isDone)
    167. break;
    168. PopTimer();
    169. tmp = m_szTimers[0];
    170. }
    171. }
    172. }
    173. public class GlobalTimerManager : TimerManager
    174. {
    175. }
    176. }


    3 测试数据对比

    在更新所有timer的地方,开启10w定时器测试使用小顶堆和不使用小顶堆做了对比:

    计时器不使用小堆顶:更新全部,cpu main 在 14.1左右浮动;

    计时器使用小堆顶:计时器timeout时间取的是1-10w,cpu mian 平均 在1.6左右浮动,在雪崩(全部更新的情况)情况下 cpuMian会突然上升到9.6左右;

    通过数据比对,使用了小顶堆比不使用小顶堆,在综合情况下效率要快将近8.8倍
     

    引用

    文章:基于STL的小根堆定时器实现(C++)_AlwaysSimple的博客-CSDN博客_小根堆定时器

  • 相关阅读:
    npm作用域包和版本
    【算法|动态规划No.25】leetcode LCR 020. 回文子串
    【云原生 · Kubernetes】KubeVirt热迁移
    【最长上升子序列】【博弈论】Game on Permutation—CF1860C
    【机器学习技巧】之特征工程:数字编码以及One-hot独热编码的几种方式(sklearn与pandas处理方式)
    Windows编程dll基本知识点
    JOSEF约瑟 JHOK-ZBM1;JHOK-ZBL1多档切换式漏电(剩余)继电器 面板导轨安装
    ES6中新增加的Symbol数据类型及其使用场景
    头歌——Linux——下的 c 编程
    Leetcode 440. 字典序的第K小数字
  • 原文地址:https://blog.csdn.net/qq_33531923/article/details/127895768