meinheld为何比gevent高效?
2016-01-18 01:01
309 查看
XYM博客对应文章
meinheld:greenlet(协程) + picoev(高性能网络库)
gevent:greenlet(协程) + libevent(高性能网络库)
greenlet 提供了在协程中直接切换控制权的方式,比生成器(yield)更加灵活、简洁。
协程:又称微线程,纤程。
协程的这种“挂起”和“唤醒”机制实质上是将一个过程切分成了若干个子过程,给了我们一种以扁平的方式来使用事件回调模型。优点:共享进程的上下文,一个进程可以创建百万,千万的coroutine。
程序通过Libevent框架注册相应的事件和回调函数;当这些事件发生时,Libevent会调用这些回调函数处理相应的事件(I/O读写、定时和信号)。整个过程都是异步高效的。想看具体源码实现的请移步这里。这里我们只看主要处理部分event_base_loop。
简要说明event_base_loop实现。
事件:首先loop中要处理的事件有3种,一种是计时事件(timeout触发),一种是普通I/O事件(select, poll,epoll),还有一种信号事件(signal),其中信号事件最终也是被转换成普通I/O事件被监听。
流程:
1. 先通过Timer最小堆(以时间为排序的键)找出至少要等待的时间。(代码中的timeout_next()函数)。
2. 通过select发送这些事件fd到内核并设置时间为1中所求的等待时间。然后把select返回的就绪事件放到就绪列表。(对应 evsel->dispatch(base, evbase, tv_p))。
3. 然后把现在超时的计时事件放到就绪列表。(对应gettime(base, &base->tv_cache))。
4. 最后调用处理函数处理就绪列表中的事件(timeout_process(base))。
1. picoev几乎所有顺序结构都是用数组实现的,索引访问速度比libevent的链表快很多。
2. picoev采用了环形队列+vector+bitmap来实现定时事件的检测。
下面看看picoev的picoev_loop_once。
这里主要讲一下loop的结构,因为这是高效的原因。
对于timeout环形队列,每经过resolution时间就往后移动一块,当前队头永远指向刚刚到达时间的事件块,如图当前处理的是2,那么说明队列头在2,那么再经过resolution时间就会到3,根据时间不断后移,循环利用。
在处理每一块timeout里面注册的事件时,遍历所有不为0的vector,得出对应的fd。图中已经写的很清楚的,其实原理和16进制一样简单。插入一个事件的效率为O(1),遍历所有在timeout块的注册事件效率为O(n)[注:这里n为timeout里面注册事件的个数],对比lievent的最小堆O(logn)插入,每次处理一个后调整堆的复杂度O(logn)处理n个就为O(nlogn),确实是高效很多。
还有一个高效的地方在于,picoev是检测到有一个事件就马上处理(无阻塞),不像libevent挂起等待最小等待时间到达(阻塞),然后才对所有就绪事件队列里面的事件进行处理,不过这也导致了picoev不能设定事件处理的优先级。
缺点(对比libevent):作者在最后说picoev并没有libevent成熟,也没有很多功能,现在只支持select,epoll,kqueue,我们也可以看到没有信号事件的处理,优先级设定这些功能的支持。不过他简单快速,而且支持多线程。
前言
两者都是高性能的WSGI兼容的web服务器。既然是同种东西,必然有对比,网上有挺多benchmark,我也做过对应的benchmark,不过没有整理,这里推荐一下网上的一篇benchmark,能够看出meinheld的性能确实好得令人意外。那么为什么meinheld会比gevent性能高这么多呢?我们从底层实现来看看,他究竟做了一些什么。meinheld和gevent
两者实现很相似。meinheld:greenlet(协程) + picoev(高性能网络库)
gevent:greenlet(协程) + libevent(高性能网络库)
greenlet
python中的yield和第三方库greenlet,都是用来实现协程的利器。greenlet 提供了在协程中直接切换控制权的方式,比生成器(yield)更加灵活、简洁。
协程:又称微线程,纤程。
协程的这种“挂起”和“唤醒”机制实质上是将一个过程切分成了若干个子过程,给了我们一种以扁平的方式来使用事件回调模型。优点:共享进程的上下文,一个进程可以创建百万,千万的coroutine。
libevent
libevent是一个事件驱动的网络库,主要设计模式是Reactor(反应器)。程序通过Libevent框架注册相应的事件和回调函数;当这些事件发生时,Libevent会调用这些回调函数处理相应的事件(I/O读写、定时和信号)。整个过程都是异步高效的。想看具体源码实现的请移步这里。这里我们只看主要处理部分event_base_loop。
简要说明event_base_loop实现。
事件:首先loop中要处理的事件有3种,一种是计时事件(timeout触发),一种是普通I/O事件(select, poll,epoll),还有一种信号事件(signal),其中信号事件最终也是被转换成普通I/O事件被监听。
流程:
1. 先通过Timer最小堆(以时间为排序的键)找出至少要等待的时间。(代码中的timeout_next()函数)。
2. 通过select发送这些事件fd到内核并设置时间为1中所求的等待时间。然后把select返回的就绪事件放到就绪列表。(对应 evsel->dispatch(base, evbase, tv_p))。
3. 然后把现在超时的计时事件放到就绪列表。(对应gettime(base, &base->tv_cache))。
4. 最后调用处理函数处理就绪列表中的事件(timeout_process(base))。
int event_base_loop(struct event_base *base, int flags) { const struct eventop *evsel = base->evsel; void *evbase = base->evbase; struct timeval tv; struct timeval *tv_p; int res, done; // 清空时间缓存 base->tv_cache.tv_sec = 0; // evsignal_base是全局变量,在处理signal时,用于指名signal所属的event_base实例 if (base->sig.ev_signal_added) evsignal_base = base; done = 0; while (!done) { // 事件主循环 // 查看是否需要跳出循环,程序可以调用event_loopexit_cb()设置event_gotterm标记 // 调用event_base_loopbreak()设置event_break标记 if (base->event_gotterm) { base->event_gotterm = 0; break; } if (base->event_break) { base->event_break = 0; break; } // 校正系统时间,如果系统使用的是非MONOTONIC时间,用户可能会向后调整了系统时间 // 在timeout_correct函数里,比较last wait time和当前时间,如果当前时间< last wait time // 表明时间有问题,这是需要更新timer_heap中所有定时事件的超时时间。 timeout_correct(base, &tv); // 根据timer heap中事件的最小超时时间,计算系统I/O demultiplexer的最大等待时间 tv_p = &tv; if (!base->event_count_active && !(flags & EVLOOP_NONBLOCK)) { timeout_next(base, &tv_p); } else { // 依然有未处理的就绪时间,就让I/O demultiplexer立即返回,不必等待 // 下面会提到,在libevent中,低优先级的就绪事件可能不能立即被处理 evutil_timerclear(&tv); } // 如果当前没有注册事件,就退出 if (!event_haveevents(base)) { event_debug(("%s: no events registered.", __func__)); return (1); } // 更新last wait time,并清空time cache gettime(base, &base->event_tv); base->tv_cache.tv_sec = 0; // 调用系统I/O demultiplexer等待就绪I/O events,可能是epoll_wait,或者select等; // 在evsel->dispatch()中,会把就绪signal event、I/O event插入到激活链表中 res = evsel->dispatch(base, evbase, tv_p); if (res == -1) return (-1); // 将time cache赋值为当前系统时间 gettime(base, &base->tv_cache); // 检查heap中的timer events,将就绪的timer event从heap上删除,并插入到激活链表中 timeout_process(base); // 调用event_process_active()处理激活链表中的就绪event,调用其回调函数执行事件处理 // 该函数会寻找最高优先级(priority值越小优先级越高)的激活事件链表, // 然后处理链表中的所有就绪事件; // 因此低优先级的就绪事件可能得不到及时处理; if (base->event_count_active) { event_process_active(base); if (!base->event_count_active && (flags & EVLOOP_ONCE)) done = 1; } else if (flags & EVLOOP_NONBLOCK) done = 1; } // 循环结束,清空时间缓存 base->tv_cache.tv_sec = 0; event_debug(("%s: asked to terminate loop.", __func__)); return (0); }
picoev
picoev在项目下有把picoev和libevent这些库做对比,作者也提了一下为什么picoev的速度会这么快。主要有两个原因。1. picoev几乎所有顺序结构都是用数组实现的,索引访问速度比libevent的链表快很多。
2. picoev采用了环形队列+vector+bitmap来实现定时事件的检测。
下面看看picoev的picoev_loop_once。
int picoev_loop_once(picoev_loop* loop, int max_wait) { loop->now = time(NULL); //获取当前时间 // 最大等待事件不超过计时器的处理时间 if (max_wait > loop->timeout.resolution) { max_wait = loop->timeout.resolution; } // 使用select去检测事件是否完成,如果完成就调对应的回调函数处理 if (picoev_poll_once_internal(loop, max_wait) != 0) { return -1; } if (max_wait != 0) { //有指定时间则刷新当前时间 loop->now = time(NULL); } // 处理到时间的计时事件 picoev_handle_timeout_internal(loop); return 0; }
这里主要讲一下loop的结构,因为这是高效的原因。
对于timeout环形队列,每经过resolution时间就往后移动一块,当前队头永远指向刚刚到达时间的事件块,如图当前处理的是2,那么说明队列头在2,那么再经过resolution时间就会到3,根据时间不断后移,循环利用。
在处理每一块timeout里面注册的事件时,遍历所有不为0的vector,得出对应的fd。图中已经写的很清楚的,其实原理和16进制一样简单。插入一个事件的效率为O(1),遍历所有在timeout块的注册事件效率为O(n)[注:这里n为timeout里面注册事件的个数],对比lievent的最小堆O(logn)插入,每次处理一个后调整堆的复杂度O(logn)处理n个就为O(nlogn),确实是高效很多。
还有一个高效的地方在于,picoev是检测到有一个事件就马上处理(无阻塞),不像libevent挂起等待最小等待时间到达(阻塞),然后才对所有就绪事件队列里面的事件进行处理,不过这也导致了picoev不能设定事件处理的优先级。
缺点(对比libevent):作者在最后说picoev并没有libevent成熟,也没有很多功能,现在只支持select,epoll,kqueue,我们也可以看到没有信号事件的处理,优先级设定这些功能的支持。不过他简单快速,而且支持多线程。
最后
使用python做web开发的同学,可以尝试一下nginx + meinheld + gunicorn + flask。相关文章推荐
- libevent库的使用--定时器的使用实例
- Linux下编译安装php libevent扩展实例
- libevent 一个简单的event示例
- libevent 初始化与event_init
- libevent event_set函数与event_add函数解析 [SYM]
- libevent2 bufferevent应用示例
- 复习ZMQ的 REQ-ROUTER
- 【网游服务器】Linux下libevent安装与示例
- 后续博客的方向
- 静态编译libevent
- python + flask + uwsgi + gevent + nginx 环境搭建(非阻塞)
- libevent源码分析之 tail queue
- django+nginx+supervisor+gunicorn+gevent 网站部署
- libevent安装与libevent定时器
- Archlinux使用最新版goagent的几点注意(无法上传问题)
- cocos2dx加libevent库
- libevent中evbuffer简单使用
- libevent 中bufferevent_setcb 之writecb 使用
- 在VS2010环境编译libevent-2.0.22-stable
- memcached--------基于centos6源码安装 推荐