如何降低由 epoll_wait 调用的 finish_task_switch() 的 CPU 用法?
How to lower CPU usage of finish_task_switch(), called by epoll_wait?
我编写了一个简单的 epoll 驱动的服务器来测试 network/io 性能。服务器仅接收请求并立即发送响应。 它比 redis-server 'get' 慢,38k/s vs 40k/s。两者都使用 redis-benchmark 作为负载运行器,并且都使用 cpu up (>99%).
bench redis-server: redis-benchmark -n 1000000 -c 20 -t get -p 6379
bench myserver : redis-benchmark -n 1000000 -c 20 -t get -p 6399
我已经使用 linux perf 对它们进行了分析,在我的服务器中消除了 epoll_ctl(就像 redis-server 所做的那样)。现在的问题是函数 finish_task_switch() 花费太多 cpu 时间,大约 10%-15%(对于 redis-server 和 redis-benchmark 是 3%, 在同一台机器上).
调用流程(自上而下阅读)为
-> epoll_wait(25%)
-> entry_SYSCALL_64_after_hwframe(23.56%)
-> do_syscall_64(23.23%)
-> sys_epoll_wait(22.36%)
-> ep_poll(21.88%)
-> schedule_hrtimeout_range(12.98%)
-> schedule_hrtimeout_range_clock(12.74%)
-> schedule(11.30%)
-> _schedule (11.30%)
-> finish_task_switch(10.82%)
我试过使用原始 epoll api 编写服务器,并在 redis/src/ae.c 中使用 redis 的 api,没有任何改变。
我检查了 redis-server 和 redis-benchmark 如何使用 epoll,没有发现任何技巧。
redis CFLAGS 用于 myserver,与 redis-benchmark 相同。
CPU 用法与 level/edge-triggered、块或非块客户端 fd 无关,无论是否设置 epoll_wait 的超时。
#include <sys/epoll.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h> // exit
#include <string.h> // memset
#include "anet.h"
#define MAX_EVENTS 32
typedef struct {
int fd;
char querybuf[256];
} client;
client *clients;
char err[256];
#define RESPONSE_REDIS "8\r\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\r\n"
static int do_use_fd(client *c)
{
int n = read(c->fd, c->querybuf, sizeof(c->querybuf));
if (n == 0) { printf("Client Closed\n"); return n; }
n = write(c->fd, RESPONSE_REDIS, sizeof(RESPONSE_REDIS)-1);
return n;
}
int main()
{
struct epoll_event ev, events[MAX_EVENTS];
int listen_sock, conn_sock, nfds, epollfd;
epollfd = epoll_create(MAX_EVENTS);
listen_sock = anetTcpServer(err, 6399, NULL, MAX_EVENTS);
ev.events = EPOLLIN;
ev.data.fd = listen_sock;
epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev);
clients = (client *)malloc(sizeof(client) * MAX_EVENTS);
memset(clients, 0, sizeof(client) * MAX_EVENTS);
for (;;) {
int n;
struct sockaddr addr;
socklen_t addrlen = sizeof(addr);
nfds = epoll_wait(epollfd, events, MAX_EVENTS, 100);
for (n = 0; n < nfds; ++n) {
if (events[n].data.fd == listen_sock) {
conn_sock = accept(listen_sock,
(struct sockaddr *) &addr, &addrlen);
anetNonBlock(err, conn_sock);
ev.events = EPOLLIN;
//ev.events = EPOLLIN | EPOLLET;
ev.data.fd = conn_sock;
epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock,&ev);
clients[conn_sock].fd = conn_sock;
} else {
client *c = &clients[events[n].data.fd];
int ret = do_use_fd(c);
if (ret == 0) {
epoll_ctl(epollfd, EPOLL_CTL_DEL, c->fd, &ev);
}
}
}
}
}
服务器的监听fd被阻塞。将其设置为非阻塞会将 finish_task_switch 的使用率降低到 <2%。
我编写了一个简单的 epoll 驱动的服务器来测试 network/io 性能。服务器仅接收请求并立即发送响应。 它比 redis-server 'get' 慢,38k/s vs 40k/s。两者都使用 redis-benchmark 作为负载运行器,并且都使用 cpu up (>99%).
bench redis-server: redis-benchmark -n 1000000 -c 20 -t get -p 6379
bench myserver : redis-benchmark -n 1000000 -c 20 -t get -p 6399
我已经使用 linux perf 对它们进行了分析,在我的服务器中消除了 epoll_ctl(就像 redis-server 所做的那样)。现在的问题是函数 finish_task_switch() 花费太多 cpu 时间,大约 10%-15%(对于 redis-server 和 redis-benchmark 是 3%, 在同一台机器上).
调用流程(自上而下阅读)为
-> epoll_wait(25%)
-> entry_SYSCALL_64_after_hwframe(23.56%)
-> do_syscall_64(23.23%)
-> sys_epoll_wait(22.36%)
-> ep_poll(21.88%)
-> schedule_hrtimeout_range(12.98%)
-> schedule_hrtimeout_range_clock(12.74%)
-> schedule(11.30%)
-> _schedule (11.30%)
-> finish_task_switch(10.82%)
我试过使用原始 epoll api 编写服务器,并在 redis/src/ae.c 中使用 redis 的 api,没有任何改变。
我检查了 redis-server 和 redis-benchmark 如何使用 epoll,没有发现任何技巧。
redis CFLAGS 用于 myserver,与 redis-benchmark 相同。
CPU 用法与 level/edge-triggered、块或非块客户端 fd 无关,无论是否设置 epoll_wait 的超时。
#include <sys/epoll.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h> // exit
#include <string.h> // memset
#include "anet.h"
#define MAX_EVENTS 32
typedef struct {
int fd;
char querybuf[256];
} client;
client *clients;
char err[256];
#define RESPONSE_REDIS "8\r\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\r\n"
static int do_use_fd(client *c)
{
int n = read(c->fd, c->querybuf, sizeof(c->querybuf));
if (n == 0) { printf("Client Closed\n"); return n; }
n = write(c->fd, RESPONSE_REDIS, sizeof(RESPONSE_REDIS)-1);
return n;
}
int main()
{
struct epoll_event ev, events[MAX_EVENTS];
int listen_sock, conn_sock, nfds, epollfd;
epollfd = epoll_create(MAX_EVENTS);
listen_sock = anetTcpServer(err, 6399, NULL, MAX_EVENTS);
ev.events = EPOLLIN;
ev.data.fd = listen_sock;
epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev);
clients = (client *)malloc(sizeof(client) * MAX_EVENTS);
memset(clients, 0, sizeof(client) * MAX_EVENTS);
for (;;) {
int n;
struct sockaddr addr;
socklen_t addrlen = sizeof(addr);
nfds = epoll_wait(epollfd, events, MAX_EVENTS, 100);
for (n = 0; n < nfds; ++n) {
if (events[n].data.fd == listen_sock) {
conn_sock = accept(listen_sock,
(struct sockaddr *) &addr, &addrlen);
anetNonBlock(err, conn_sock);
ev.events = EPOLLIN;
//ev.events = EPOLLIN | EPOLLET;
ev.data.fd = conn_sock;
epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock,&ev);
clients[conn_sock].fd = conn_sock;
} else {
client *c = &clients[events[n].data.fd];
int ret = do_use_fd(c);
if (ret == 0) {
epoll_ctl(epollfd, EPOLL_CTL_DEL, c->fd, &ev);
}
}
}
}
}
服务器的监听fd被阻塞。将其设置为非阻塞会将 finish_task_switch 的使用率降低到 <2%。