4

Libev使用三种数据结构来存储不同的观察者。

堆:用于按时间排序的观察者,例如ev_timerev_periodic

链表:ev_io, ev_signal,ev_child等。

数组:ev_prepareev_checkev_async

毫无疑问,使用堆来存储计时器观察者。但是选择链表和数组的标准是什么?

存储 ev_io 观察者的数据结构似乎有点复杂。它首先是一个以fd索引为索引的数组,数组中的元素是 的链表ev_io watcher。如果使用链表作为元素,则为数组分配空间更方便。是这个原因吗?

或者只是因为插入或删除操作ev_io更频繁,ev_prepare看起来更稳定?

还是有什么其他原因?

4

2 回答 2

5

期望只有少数(通常是一个,并且几乎总是最多两个)io watchers 用于相同的 fd(与信号类似)。将列表链接放入观察者意味着不需要额外的分配,如果每个观察者使用一个数组则需要。如果很多 I/O 观察者在同一个 fd 上处于活动状态,那么这种链表方法会更慢。

使用数组是因为插入和删除非常快(观察者存储数组索引)。使用 4 字节索引还可以减少 64 位机器上的内存需求(每个观察者 12 个字节,而双向链表需要 16 个字节),并且使用指针数组意味着所有指针在内存中彼此靠近,这提高扫描时的缓存效率,这是一些观察者的频繁操作。

缓存效率也是使用 4 堆而不是 2 堆的原因,也是将时间值复制到堆数据结构中的原因,以避免在堆操作时访问观察者内存。

子观察者实际上使用一个固定大小的哈希表,并且再次期望每个哈希桶的子观察者数量很少,因此列表指针成为观察者数据结构的一部分。

于 2013-01-07T15:18:54.767 回答
2

可能原因是在典型情况下需要通过 fd 查找 ev_io。底层库(epoll、select 或其他)将提供具有某些事件的 fd。然后,Libev 将简单地将其用作索引,并仅遍历需要调用的事件观察者的链接列表。所以它可以快速触发事件。

于 2012-12-07T21:17:59.667 回答