#redis #benchmarking #scheduler #cpu-usage #epoll
#redis #сравнительный анализ #планировщик #загрузка процессора #epoll
Вопрос:
Я написал простой сервер, управляемый epoll, для оценки производительности сети / ввода-вывода. Сервер просто получает запрос и немедленно отправляет ответ. Это медленнее, чем redis-server ‘get’, 38k / s против 40k / s . Оба используют redis-benchmark в качестве загрузчика, и оба использовали процессор (> 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 на myserver (как это делает redis-server). Теперь проблема заключается в том, что функция finish_task_switch() занимает слишком много процессорного времени, около 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%)
-> расписание (11,30%)
-> _schedule(11,30%)
-> finish_task_switch(10,82%)
Я пробовал писать сервер с использованием необработанного epoll api и использовать api redis в redis / src / ae.c, ничего не изменилось.
Я изучил, как redis-server и redis-benchmark используют epoll, никаких трюков не найдено.
CFLAGS redis используется для myserver так же, как и redis-benchmark.
Загрузка ЦП не имеет ничего общего с уровнем / граничным, блочным или неблочным клиентским 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 "$128rnxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxrn"
static int do_use_fd(client *c)
{
int n = read(c->fd, c->querybuf, sizeof(c->querybuf));
if (n == 0) { printf("Client Closedn"); 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, amp;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 *) amp;addr, amp;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,amp;ev);
clients[conn_sock].fd = conn_sock;
} else {
client *c = amp;clients[events[n].data.fd];
int ret = do_use_fd(c);
if (ret == 0) {
epoll_ctl(epollfd, EPOLL_CTL_DEL, c->fd, amp;ev);
}
}
}
}
}
Ответ №1:
fd прослушивания сервера заблокирован. если установить его неблокируемым, использование finish_task_switch снизится до <2%.
Комментарии:
1. Но снижает ли это общее время процессора или просто заставляет другие вещи занимать больше времени процессора, что приводит к снижению относительного времени процессора. например, то же время занимает, но меньше общего, если ваша программа тратит время процессора на системный вызов, который нужно повторить вместо блокировки?