searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

Golang计时器实现中堆的使用

2023-10-16 02:24:48
9
0

1. 引言

在Go语言中,计时器(Timer)是一种常用的工具,用于在未来的某个时间点触发操作。Go的标准库中提供了time.Timer,它的底层实现采用了四叉堆(Quadheap)而不是常见的二叉堆(Binary Heap)。本文将介绍time.Timer的底层实现,重点关注四叉堆与二叉堆的添加和删除操作的性能比较,并最终得出结论。

2. 计时器的底层实现

time.Timer是Go语言标准库中的一部分,它用于实现计时器功能。底层实现涉及到Go的运行时系统和操作系统的系统调用,用于设置和触发定时器。每个time.Timer都包含一个小顶堆(min-heap),这个堆是四叉堆而不是通常的二叉堆。以下是计时器底层实现的关键点:

  • 四叉堆:Go语言选择使用四叉堆作为计时器的底层数据结构。四叉堆比二叉堆更为复杂,但在某些场景下可以提供更好的性能。

  • Goroutines:每个time.Timer都与一个Goroutine相关联,用于在定时器到期时执行用户指定的操作。这个Goroutine会休眠,直到定时器触发。

  • 系统调用:底层实现依赖操作系统提供的计时器机制,如timer_createtimer_settime(对于Unix系统)。这些系统调用用于设置和触发定时器。

  • 竞争条件处理time.Timer的实现需要处理多个Goroutine之间的竞争条件,以确保定时器的准确性和一致性。通常,互斥锁用于保护共享状态。

  • 资源回收:一旦定时器不再需要,Go的运行时系统会负责回收相关资源,以防止内存泄漏。

3. 二叉堆与四叉堆的添加删除耗时比较

现在让我们比较二叉堆和四叉堆在添加和删除操作上的性能差异。在Go语言的time.Timer中,四叉堆被选择用于其性能优势。

假设:堆为d叉堆,堆中共有n个节点。

备注:时间复杂度的证明见 D-ary_heap

1)添加 Insert()

添加一个计时器。涉及 sift-up(上推)操作,时间复杂度为O(log n / log d)

Python代码

import math
result = math.log(n, d)

时间消耗(需要交换或者比较的次数)

n(数量) 二叉堆 四叉堆 八叉堆
1w 13.3 6.64 4.43
10w 16.61 8.30 5.54
100w 19.93 9.97 6.64
1000w 23.25 11.63 7.75

在添加定时器时,四叉堆的性能优势主要体现在以下几个方面:

  • 多叉堆的插入速度更快:由于每个节点有更多的子节点,四叉堆的插入操作通常比二叉堆更快。这意味着当你需要添加大量定时器时,四叉堆可能更适合。

  • 树的高度更低:四叉堆的高度比二叉堆更低,这意味着查找到合适的位置插入定时器所需的比较操作更少。

2)删除 Delete()

指定时间到达后,清除计时器。涉及 sift-down(下推)操作,时间复杂度为O(d * log n / log d)

Python代码

import math
result = d * math.log(n, d)

时间消耗(需要交换或者比较的次数)

n(数量) 二叉堆 四叉堆 八叉堆
1w 26.58 26.58 35.43
10w 33.33 33.33 44.29
100w 39.86 39.86 53.15
1000w 46.51 46.51 62.01

在删除定时器时,四叉堆的性能也具有优势:

  • 定时器的上浮操作更快:四叉堆的删除操作中,上浮操作(将一个节点上浮到合适的位置)的次数较少,因为树的高度更低。这降低了删除操作的时间复杂度。

  • 对顶元素更容易访问:由于四叉堆中的顶元素存储了最早到期的定时器,因此它可以更容易地被访问,而无需遍历整个树。

4. 结论

在Go语言中,time.Timer的底层实现选择了四叉堆作为数据结构,而不是常见的二叉堆。这个选择是基于性能考虑的,因为四叉堆在添加和删除操作上具有一些优势,尤其在处理大量定时器时。虽然四叉堆的实现可能更复杂,但它可以提供更高效的计时器功能,满足Go语言对性能和可靠性的要求。无论是在高并发的网络服务器还是在需要精确计时的应用中,time.Timer的四叉堆实现都能够可靠地工作。因此,Go语言的time.Timer是一个强大而高效的工具,用于处理时间相关的任务。

0条评论
作者已关闭评论
范****荣
4文章数
0粉丝数
范****荣
4 文章 | 0 粉丝
原创

Golang计时器实现中堆的使用

2023-10-16 02:24:48
9
0

1. 引言

在Go语言中,计时器(Timer)是一种常用的工具,用于在未来的某个时间点触发操作。Go的标准库中提供了time.Timer,它的底层实现采用了四叉堆(Quadheap)而不是常见的二叉堆(Binary Heap)。本文将介绍time.Timer的底层实现,重点关注四叉堆与二叉堆的添加和删除操作的性能比较,并最终得出结论。

2. 计时器的底层实现

time.Timer是Go语言标准库中的一部分,它用于实现计时器功能。底层实现涉及到Go的运行时系统和操作系统的系统调用,用于设置和触发定时器。每个time.Timer都包含一个小顶堆(min-heap),这个堆是四叉堆而不是通常的二叉堆。以下是计时器底层实现的关键点:

  • 四叉堆:Go语言选择使用四叉堆作为计时器的底层数据结构。四叉堆比二叉堆更为复杂,但在某些场景下可以提供更好的性能。

  • Goroutines:每个time.Timer都与一个Goroutine相关联,用于在定时器到期时执行用户指定的操作。这个Goroutine会休眠,直到定时器触发。

  • 系统调用:底层实现依赖操作系统提供的计时器机制,如timer_createtimer_settime(对于Unix系统)。这些系统调用用于设置和触发定时器。

  • 竞争条件处理time.Timer的实现需要处理多个Goroutine之间的竞争条件,以确保定时器的准确性和一致性。通常,互斥锁用于保护共享状态。

  • 资源回收:一旦定时器不再需要,Go的运行时系统会负责回收相关资源,以防止内存泄漏。

3. 二叉堆与四叉堆的添加删除耗时比较

现在让我们比较二叉堆和四叉堆在添加和删除操作上的性能差异。在Go语言的time.Timer中,四叉堆被选择用于其性能优势。

假设:堆为d叉堆,堆中共有n个节点。

备注:时间复杂度的证明见 D-ary_heap

1)添加 Insert()

添加一个计时器。涉及 sift-up(上推)操作,时间复杂度为O(log n / log d)

Python代码

import math
result = math.log(n, d)

时间消耗(需要交换或者比较的次数)

n(数量) 二叉堆 四叉堆 八叉堆
1w 13.3 6.64 4.43
10w 16.61 8.30 5.54
100w 19.93 9.97 6.64
1000w 23.25 11.63 7.75

在添加定时器时,四叉堆的性能优势主要体现在以下几个方面:

  • 多叉堆的插入速度更快:由于每个节点有更多的子节点,四叉堆的插入操作通常比二叉堆更快。这意味着当你需要添加大量定时器时,四叉堆可能更适合。

  • 树的高度更低:四叉堆的高度比二叉堆更低,这意味着查找到合适的位置插入定时器所需的比较操作更少。

2)删除 Delete()

指定时间到达后,清除计时器。涉及 sift-down(下推)操作,时间复杂度为O(d * log n / log d)

Python代码

import math
result = d * math.log(n, d)

时间消耗(需要交换或者比较的次数)

n(数量) 二叉堆 四叉堆 八叉堆
1w 26.58 26.58 35.43
10w 33.33 33.33 44.29
100w 39.86 39.86 53.15
1000w 46.51 46.51 62.01

在删除定时器时,四叉堆的性能也具有优势:

  • 定时器的上浮操作更快:四叉堆的删除操作中,上浮操作(将一个节点上浮到合适的位置)的次数较少,因为树的高度更低。这降低了删除操作的时间复杂度。

  • 对顶元素更容易访问:由于四叉堆中的顶元素存储了最早到期的定时器,因此它可以更容易地被访问,而无需遍历整个树。

4. 结论

在Go语言中,time.Timer的底层实现选择了四叉堆作为数据结构,而不是常见的二叉堆。这个选择是基于性能考虑的,因为四叉堆在添加和删除操作上具有一些优势,尤其在处理大量定时器时。虽然四叉堆的实现可能更复杂,但它可以提供更高效的计时器功能,满足Go语言对性能和可靠性的要求。无论是在高并发的网络服务器还是在需要精确计时的应用中,time.Timer的四叉堆实现都能够可靠地工作。因此,Go语言的time.Timer是一个强大而高效的工具,用于处理时间相关的任务。

文章来自个人专栏
文章 | 订阅
0条评论
作者已关闭评论
作者已关闭评论
0
0