作为一个概念验证,我实现了一个多线程 stat() 调用。目前这有点像一个“hack”,但当我查看性能数据时,它看起来很有前景。
avg-cpu: %user %nice %system %iowait %steal %idle 5.00 0.00 26.60 68.40 0.00 0.00 Device: rrqm/s wrqm/s r/s w/s rsec/s wsec/s rMB/s wMB/s avgrq-sz avgqu-sz await svctm %util sda 0.00 0.60 66.90 1.60 13019.20 22.40 6.36 0.01 190.39 6.10 88.20 14.49 99.28 sdb 0.00 0.60 66.60 1.60 13061.60 22.40 6.38 0.01 191.85 14.09 208.82 14.67 100.04
在 blog.lighttpd.net/articles/2007/01/27/accelerating-small-file-transfers 中,我们尝试了同样的方法,但没有使用异步 stat() 并且使用了 fcgi-stat-accel。通过多线程 stat(),我将代码移入了 lighttpd 自身,这减少了外部通信,并使所有内容都在 lighttpd 自身内部进行管理。
name Throughput util% iowait% ----------------- ------------ ----- ------------ no stat-accel 12.07MByte/s 81% stat-accel (tcp) 13.64MByte/s 99% 45.00% stat-accel (unix) 13.86MByte/s 99% 53.25% threaded-stat 14.32MByte/s 99% 68.40%
(越大越好)
实现
在 stat_cache.c 中,我启动了一个单独的线程来处理 stat() 调用,确切地说,是4个线程。
stat_cache_get_entry() 检查其缓存,以确定该文件是否已知。如果未知,它将文件名推入 stat_cache_queue 并返回 HANDLER_WAIT_FOR_EVENT。stat_cache_queue 的另一端是4个 stat() 线程之一,它运行 stat() 并将连接推回 joblist_queue。在主循环中,就在 poll() 调用启动的地方,现在是此队列的处理程序,它会激活此队列中的所有连接。
通过这种方式,我们使 stat() 调用本身异步化,并可以保持代码的其余部分不变。到目前为止,我们只将 inode 放入 fs-buffers 中,就像其他示例一样,我们没有在线程中处理完整的 stat-cache 更新。
gpointer *stat_cache_thread(gpointer *_srv) { server *srv = (server *)_srv; stat_job *sj = NULL; /* take the stat-job-queue */ GAsyncQueue * inq = g_async_queue_ref(srv->stat_queue); GAsyncQueue * outq = g_async_queue_ref(srv->joblist_queue); /* get the jobs from the queue */ while ((sj = g_async_queue_pop(inq))) { /* let's see what we have to stat */ struct stat st; /* don't care about the return code for now */ stat(sj->name->ptr, &st); stat_job_free(sj); g_async_queue_push(outq, sj->con); } return NULL; }