U3D客户端框架之小堆顶高性能定时器测试10W计时器耗时1.9ms

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

1 小顶堆计时器概要

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


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


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


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


2 代码设计


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


Timer.cs代码实现


using UnityEngine;
using System;
using Object = UnityEngine.Object;
namespace UnityTimer
{
    public class Timer
    {
        public enum SCOPE
        {
            eLocal,
            eGlobal,
        }
        #region Public Properties/Fields
        /// <summary>
        /// 计时器回调时间持续时间
        /// </summary>
        public float duration { get; private set; }
        /// <summary>
        /// 执行完成后是否循环执行.
        /// </summary>
        public bool isLooped { get; set; }
        /// <summary>
        /// 本帧是否完成
        /// </summary>
        public bool isCompletedThisFrame { get; private set; }
        /// <summary>
        /// 是否完成
        /// </summary>
        public bool isCompleted { get; private set; }
        /// <summary>
        /// 计时器使用的是实时时间还是游戏时间
        /// </summary>
        public bool usesRealTime { get; private set; }
        /// <summary>
        /// 计时器是否暂停
        /// </summary>
        public bool isPaused
        {
            get { return this._timeElapsedBeforePause.HasValue; }
        }
        /// <summary>
        /// 是否取消了
        /// </summary>
        public bool isCancelled
        {
            get { return this._timeElapsedBeforeCancel.HasValue; }
        }
        /// <summary>
        /// 是否完成
        /// </summary>
        public bool isDone
        {
            get { return this.isCompleted || this.isCancelled || this.isOwnerDestroyed; }
        }
        #endregion
        #region Public Static Methods
        /// <summary>
        /// 注册一个新的计时器,在一定时间后触发一个事件
        /// 在切换场景的时候,注册的计时器会被销毁
        /// </summary>
        /// <param name="duration">在一定秒后执行事件</param>
        /// <param name="onComplete">计时器执行完回调事件.</param>
        /// <param name="onUpdate">每次执行计时器执行的回调</param>
        /// <param name="isLooped">计时器在执行后是否重复执行</param>
        /// <param name="useRealTime">是否使用实时时间</param>
        /// <param name="autoDestroyOwner">此计时器附加到的对象,物体被摧毁后,定时器将不执行</param>
        /// <returns>一个计时器对象</returns>
        public static Timer Regist(float duration, Action onComplete, Action<float> onUpdate = null,
            bool isLooped = false, bool useRealTime = false, MonoBehaviour autoDestroyOwner = null)
        {
            if (Timer._manager == null)
            {
                TimerManager managerInScene = Object.FindObjectOfType<TimerManager>();
                if (managerInScene != null)
                {
                    Timer._manager = managerInScene;
                }
                else
                {
                    GameObject managerObject = new GameObject { name = "TimerManager" };
                    Timer._manager = managerObject.AddComponent<TimerManager>();
                }
            }
            Timer timer = new Timer(duration, onComplete, onUpdate, isLooped, useRealTime, autoDestroyOwner);
            Timer._manager.RegisterTimer(timer);
            return timer;
        }
        /// <summary>
        /// 作用同上,不同的是此API创建的计时器在程序的生命周期内都有效,不会随着场景的销毁而销毁
        /// </summary>
        public static Timer RegistGlobal(float duration, Action onComplete, Action<float> onUpdate = null,
            bool isLooped = false, bool useRealTime = false, MonoBehaviour autoDestroyOwner = null)
        {
            if (Timer._globalManager == null)
            {
                GlobalTimerManager globalManager = Object.FindObjectOfType<GlobalTimerManager>();
                if (globalManager != null)
                {
                    Timer._globalManager = globalManager;
                }
                else
                {
                    GameObject globalManagerObject = new GameObject { name = "GlobalTimerManager" };
                    Timer._globalManager = globalManagerObject.AddComponent<GlobalTimerManager>();
                    GameObject.DontDestroyOnLoad(Timer._globalManager);
                }
            }
            Timer timer = new Timer(duration, onComplete, onUpdate, isLooped, useRealTime, autoDestroyOwner);
            Timer._globalManager.RegisterTimer(timer);
            return timer;
        }
        public static void Cancel(Timer timer)
        {
            if (timer != null)
            {
                timer.Cancel();
            }
        }
        public static void Pause(Timer timer)
        {
            if (timer != null)
            {
                timer.Pause();
            }
        }
        public static void Resume(Timer timer)
        {
            if (timer != null)
            {
                timer.Resume();
            }
        }
        public static void CancelAllRegisteredTimers(SCOPE eScope = SCOPE.eLocal)
        {
            //如果计时器不存在,不需要做任何事情
            if (eScope == SCOPE.eLocal)
            {
                if (Timer._manager != null)
                {
                    Timer._manager.CancelAllTimers();
                }
            }
            else if (eScope == SCOPE.eGlobal)
            {
                if (Timer._globalManager != null)
                {
                    Timer._globalManager.CancelAllTimers();
                }
            }
        }
        public static void PauseAllRegisteredTimers(SCOPE eScope = SCOPE.eLocal)
        {
            //如果计时器不存在,不需要做任何事情
            if (eScope == SCOPE.eLocal)
            {
                if (Timer._manager != null)
                {
                    Timer._manager.PauseAllTimers();
                }
            }
            else if (eScope == SCOPE.eGlobal)
            {
                if (Timer._globalManager != null)
                {
                    Timer._globalManager.PauseAllTimers();
                }
            }
        }
        public static void ResumeAllRegisteredTimers(SCOPE eScope = SCOPE.eLocal)
        {
            //如果计时器不存在,不需要做任何事情
            if (eScope == SCOPE.eLocal)
            {
                if (Timer._manager != null)
                {
                    Timer._manager.ResumeAllTimers();
                }
            }
            else if (eScope == SCOPE.eGlobal)
            {
                if (Timer._globalManager != null)
                {
                    Timer._globalManager.ResumeAllTimers();
                }
            }
        }
        #endregion
        #region Public Methods
        public void Cancel()
        {
            if (this.isDone)
            {
                return;
            }
            this._timeElapsedBeforeCancel = this.GetTimeElapsed();
            this._timeElapsedBeforePause = null;
        }
        public void Pause()
        {
            if (this.isPaused || this.isDone)
            {
                return;
            }
            this._timeElapsedBeforePause = this.GetTimeElapsed();
        }
        public void Resume()
        {
            if (!this.isPaused || this.isDone)
            {
                return;
            }
            this._timeElapsedBeforePause = null;
        }
        public float GetTimeElapsed()
        {
            if (this.isCompleted || this.GetWorldTime() >= this.GetFireTime())
            {
                return this.duration;
            }
            return this._timeElapsedBeforeCancel ??
                   this._timeElapsedBeforePause ??
                   this.GetWorldTime() - this._startTime;
        }
        public float GetTimeRemaining()
        {
            return this.duration - this.GetTimeElapsed();
        }
        public float GetRatioComplete()
        {
            return this.GetTimeElapsed() / this.duration;
        }
        public float GetRatioRemaining()
        {
            return this.GetTimeRemaining() / this.duration;
        }
        #endregion
        #region Private Static Properties/Fields
        private static TimerManager _manager;
        private static GlobalTimerManager _globalManager;
        #endregion
        #region Private Properties/Fields
        private bool isOwnerDestroyed
        {
            get { return this._hasAutoDestroyOwner && this._autoDestroyOwner == null; }
        }
        private readonly Action _onComplete;
        private readonly Action<float> _onUpdate;
        private float _startTime;
        private float _endTime;
        private float _lastUpdateTime;
        private float? _timeElapsedBeforeCancel;
        private float? _timeElapsedBeforePause;
        private readonly MonoBehaviour _autoDestroyOwner;
        private readonly bool _hasAutoDestroyOwner;
        #endregion
        #region 属性区
        public float EndTime { get { return _endTime; } }
        #endregion
        #region Private Constructor (use static Register method to create new timer)
        private Timer(float duration, Action onComplete, Action<float> onUpdate,
            bool isLooped, bool usesRealTime, MonoBehaviour autoDestroyOwner)
        {
            this.duration = duration;
            this._onComplete = onComplete;
            this._onUpdate = onUpdate;
            this.isLooped = isLooped;
            this.usesRealTime = usesRealTime;
            this._autoDestroyOwner = autoDestroyOwner;
            this._hasAutoDestroyOwner = autoDestroyOwner != null;
            this._startTime = this.GetWorldTime();
            this._lastUpdateTime = this._startTime;
            _endTime = _startTime + duration;
        }
        #endregion
        #region  Methods
        public float GetWorldTime()
        {
            return this.usesRealTime ? Time.realtimeSinceStartup : Time.time;
        }
        private float GetFireTime()
        {
            return this._startTime + this.duration;
        }
        private float GetTimeDelta()
        {
            return this.GetWorldTime() - this._lastUpdateTime;
        }
        public void Update()
        {
            isCompletedThisFrame = false;
            if (this.isDone)
            {
                return;
            }
            if (this.isPaused)
            {
                this._startTime += this.GetTimeDelta();
                this._lastUpdateTime = this.GetWorldTime();
                return;
            }
            this._lastUpdateTime = this.GetWorldTime();
            if (this._onUpdate != null)
            {
                this._onUpdate(this.GetTimeElapsed());
            }
            if (this.GetWorldTime() >= this.GetFireTime())
            {
                isCompletedThisFrame = true;
                if (this._onComplete != null)
                {
                    this._onComplete();
                }
                if (this.isLooped)
                {
                    this._startTime = this.GetWorldTime();
                    _endTime = _startTime + duration;
                }
                else
                {
                    this.isCompleted = true;
                }
            }
        }
        #endregion 
    }
}


TimerMgr.cs代码实现


using System.Collections.Generic;
using UnityEngine;
using System;
using Object = UnityEngine.Object;
namespace UnityTimer
{
    public class TimerManager : MonoBehaviour
    {
        //protected List<Timer> _timers = new List<Timer>();
        //初始化默认new 1个空间出来
        protected Timer[] m_szTimers = new Timer[1];
        //已使用的空间
        protected uint m_iUsedSize = 0;
        //protected List<Timer> _timersToAdd = new List<Timer>();
        protected void Resize()
        {
            int iOldCapacity = m_szTimers.Length;
            int iNewCapacity = iOldCapacity * 2;
            Timer[] szTempTimer = new Timer[iNewCapacity];
            //尾部全部设置成null
            for (int i = iOldCapacity; i < iNewCapacity; ++i)
            {
                szTempTimer[i] = null;
            }
            //copy oldData -> newData
            Array.Copy(m_szTimers, szTempTimer, m_szTimers.Length);
            //指向新地址
            m_szTimers = szTempTimer;
            //解除引用
            szTempTimer = null;
        }
        /// <summary>
        /// 小顶堆排序   
        /// </summary>
        public void HeapAdjustSmall(int parent)
        {
            if (parent >= m_szTimers.Length)
            {
                return;
            }
            Timer tmp = m_szTimers[parent];
            //时间复杂度应该在O(LogN)附近
            for (int child = parent * 2 + 1; child < m_iUsedSize; child = child * 2 + 1)
            {
                if (child + 1 < m_iUsedSize && m_szTimers[child].EndTime > m_szTimers[child + 1].EndTime)
                {
                    child++;
                }
                if (tmp.EndTime > m_szTimers[child].EndTime)
                {
                    m_szTimers[parent] = m_szTimers[child];
                    parent = child;
                }
                else
                {
                    break;
                }
            }
            m_szTimers[parent] = tmp;
        }
        public void AddTimer(Timer timer)
        {
            if (null == timer)
            {
                return;
            }
            if (m_iUsedSize >= m_szTimers.Length)
            {
                Resize();
            }
            uint hole = m_iUsedSize;
            ++m_iUsedSize;
            // 由于新结点在最后,因此将其进行上滤,以符合最小堆
            for (uint parent = (hole - 1) / 2; hole > 0; parent = (hole - 1) / 2)
            {
                //把时间最短的计时器交换到树根节点
                if (m_szTimers[parent].EndTime > timer.EndTime)
                {
                    m_szTimers[hole] = m_szTimers[parent];
                    hole = parent;
                }
                else
                {
                    break;
                }
            }
            m_szTimers[hole] = timer;
        }
        public void PopTimer()
        {
            if (0 == m_iUsedSize)
            {
                return;
            }
            if (null != m_szTimers[0])
            {
                m_szTimers[0] = m_szTimers[--m_iUsedSize];
                HeapAdjustSmall(0);
            }
        }
        public void RegisterTimer(Timer timer)
        {
            AddTimer(timer);
        }
        public void CancelAllTimers()
        {
            Timer timer = null;
            for (int i = 0; i < m_szTimers.Length; ++i)
            {
                timer = m_szTimers[i];
                if (null != timer)
                {
                    timer.Cancel();
                    m_szTimers[i] = null;
                }
            }
            m_iUsedSize = 0;
        }
        public void PauseAllTimers()
        {
            Timer timer = null;
            for (int i = 0; i < m_szTimers.Length; ++i)
            {
                timer = m_szTimers[i];
                if (null != timer)
                    timer.Pause();
            }
        }
        public void ResumeAllTimers()
        {
            Timer timer = null;
            for (int i = 0; i < m_szTimers.Length; ++i)
            {
                timer = m_szTimers[i];
                if (null != timer)
                    timer.Resume();
            }
        }
        protected void Update()
        {
            UpdateAllTimers();
        }
        protected void UpdateAllTimers()
        {
            Timer tm = null;
            //for (int i = 0; i < m_szTimers.Length; ++i)
            //{
            //    tm = m_szTimers[i];
            //    if (null != tm)
            //        tm.Update();
            //}
            Timer tmp = null;
            tmp = m_szTimers[0];
            while (m_iUsedSize > 0)
            {
                if (null == tmp)
                    break;
                tmp.Update();
                //循环类型的计时器,如果到了时间,重新排序,而不清理
                if (tmp.isCompletedThisFrame && tmp.isLooped)
                {
                    HeapAdjustSmall(0);
                    tmp = m_szTimers[0];
                    continue;
                }
                if (!tmp.isDone)
                    break;
                PopTimer();
                tmp = m_szTimers[0];
            }
        }
    }
    public class GlobalTimerManager : TimerManager
    {
    }
}


3 测试数据对比


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


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


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


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


引用


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

相关文章
|
5天前
|
设计模式 前端开发 JavaScript
自动化测试框架设计原则与最佳实践####
本文深入探讨了构建高效、可维护的自动化测试框架的核心原则与策略,旨在为软件测试工程师提供一套系统性的方法指南。通过分析常见误区,结合行业案例,阐述了如何根据项目特性定制自动化策略,优化测试流程,提升测试覆盖率与执行效率。 ####
25 6
|
5天前
|
人工智能 前端开发 测试技术
探索软件测试中的自动化框架选择与优化策略####
本文深入剖析了当前主流的自动化测试框架,通过对比分析各自的优势、局限性及适用场景,为读者提供了一套系统性的选择与优化指南。文章首先概述了自动化测试的重要性及其在软件开发生命周期中的位置,接着逐一探讨了Selenium、Appium、Cypress等热门框架的特点,并通过实际案例展示了如何根据项目需求灵活选用与配置框架,以提升测试效率和质量。最后,文章还分享了若干最佳实践和未来趋势预测,旨在帮助测试工程师更好地应对复杂多变的测试环境。 ####
21 4
|
11天前
|
机器学习/深度学习 前端开发 测试技术
探索软件测试中的自动化测试框架选择与优化策略####
本文深入探讨了在当前软件开发生命周期中,自动化测试框架的选择对于提升测试效率、保障产品质量的重要性。通过分析市场上主流的自动化测试工具,如Selenium、Appium、Jest等,结合具体项目需求,提出了一套系统化的选型与优化策略。文章首先概述了自动化测试的基本原理及其在现代软件开发中的角色变迁,随后详细对比了各主流框架的功能特点、适用场景及优缺点,最后基于实际案例,阐述了如何根据项目特性量身定制自动化测试解决方案,并给出了持续集成/持续部署(CI/CD)环境下的最佳实践建议。 --- ####
|
12天前
|
Java 测试技术 持续交付
【入门思路】基于Python+Unittest+Appium+Excel+BeautifulReport的App/移动端UI自动化测试框架搭建思路
本文重点讲解如何搭建App自动化测试框架的思路,而非完整源码。主要内容包括实现目的、框架设计、环境依赖和框架的主要组成部分。适用于初学者,旨在帮助其快速掌握App自动化测试的基本技能。文中详细介绍了从需求分析到技术栈选择,再到具体模块的封装与实现,包括登录、截图、日志、测试报告和邮件服务等。同时提供了运行效果的展示,便于理解和实践。
48 4
【入门思路】基于Python+Unittest+Appium+Excel+BeautifulReport的App/移动端UI自动化测试框架搭建思路
|
11天前
|
测试技术 API Android开发
探索软件测试中的自动化框架选择与实践####
本文深入探讨了软件测试领域内,面对众多自动化测试框架时,如何依据项目特性和团队需求做出明智选择,并分享了实践中的有效策略与技巧。不同于传统摘要的概述方式,本文将直接以一段实践指南的形式,简述在选择自动化测试框架时应考虑的核心要素及推荐路径,旨在为读者提供即时可用的参考。 ####
|
15天前
|
测试技术 Android开发 UED
探索软件测试中的自动化框架选择
【10月更文挑战第29天】 在软件开发的复杂过程中,测试环节扮演着至关重要的角色。本文将深入探讨自动化测试框架的选择,分析不同框架的特点和适用场景,旨在为软件开发团队提供决策支持。通过对比主流自动化测试工具的优势与局限,我们将揭示如何根据项目需求和团队技能来选择最合适的自动化测试解决方案。此外,文章还将讨论自动化测试实施过程中的关键考虑因素,包括成本效益分析、维护难度和扩展性等,确保读者能够全面理解自动化测试框架选择的重要性。
32 1
|
21天前
|
监控 安全 jenkins
探索软件测试的奥秘:自动化测试框架的搭建与实践
【10月更文挑战第24天】在软件开发的海洋里,测试是确保航行安全的灯塔。本文将带领读者揭开软件测试的神秘面纱,深入探讨如何从零开始搭建一个自动化测试框架,并配以代码示例。我们将一起航行在自动化测试的浪潮之上,体验从理论到实践的转变,最终达到提高测试效率和质量的彼岸。
|
24天前
|
Web App开发 敏捷开发 存储
自动化测试框架的设计与实现
【10月更文挑战第20天】在软件开发的快节奏时代,自动化测试成为确保产品质量和提升开发效率的关键工具。本文将介绍如何设计并实现一个高效的自动化测试框架,涵盖从需求分析到框架搭建、脚本编写直至维护优化的全过程。通过实例演示,我们将探索如何利用该框架简化测试流程,提高测试覆盖率和准确性。无论你是测试新手还是资深开发者,这篇文章都将为你提供宝贵的洞见和实用的技巧。
|
13天前
|
机器学习/深度学习 自然语言处理 物联网
探索自动化测试框架的演变与未来趋势
随着软件开发行业的蓬勃发展,软件测试作为保障软件质量的重要环节,其方法和工具也在不断进化。本文将深入探讨自动化测试框架从诞生至今的发展历程,分析当前主流框架的特点和应用场景,并预测未来的发展趋势,为软件开发团队选择合适的自动化测试解决方案提供参考。
|
16天前
|
测试技术 持续交付
探索软件测试中的自动化框架:优势与挑战
【10月更文挑战第28天】 随着软件开发的快速进步,自动化测试已成为确保软件质量的关键步骤。本文将探讨自动化测试框架的优势和面临的挑战,以及如何有效地克服这些挑战。
29 0