Я пишу сервер приложений, который использует сокет TCP в Linux. Когда нет трафика (данные не отправляются клиентом, нет клиента connect() или же close()), процесс засыпает epoll_wait() при ожидании событий попадают в дескрипторы файлов сокетов.
Что можно делать, пока процесс спит?
Поэтому моя инициатива состоит в том, чтобы сократить время сна и заставить процесс читать из памяти снова и снова, пока не наступят события.
Причина этого заключается в том, чтобы держать критически важные (для производительности) данные в кэше в горячем состоянии.
- Стоит ли это делать?
- Или лучше я оставлю процесс в спящем режиме, пока не наступят события?
Мое понимание
Пока процесс спит epoll_wait() слишком долго ядро будет планировать запуск других процессов.
Если мое приложение запланировано слишком долго, его данные в памяти будут вытеснены из кеша, поскольку кеш совместно используется несколькими процессами, тогда данные других процессов будут размещаться в кеше.
epoll_wait документация
- https://man7.org/linux/man-pages/man2/epoll_wait.2.html
- Также можно прочитать из
man 2 epoll_wait.
Соответствующая часть Кодекса (event_loop и exec_epoll_wait)
struct srv_tcp_state {
int epoll_fd;
int tcp_fd;
int tun_fd;
bool stop;
struct_pad(0, 3);
struct cl_slot_stk client_stack;
struct srv_cfg *cfg;
struct client_slot *clients;
uint16_t *epoll_map;
/*
* We only support maximum of CIDR /16 number of clients.
* So this will be `uint16_t [256][256]`
*/
uint16_t (*ip_map)[256];
/* Counters */
uint32_t read_tun_c;
uint32_t write_tun_c;
struct bc_arr bc_arr_ct;
utsrv_pkt_t send_buf;
struct iface_cfg siff;
bool need_iface_down;
bool aff_ok;
struct_pad(1, 4);
cpu_set_t aff;
};
static int exec_epoll_wait(int epoll_fd, struct epoll_event *events,
int maxevents, struct srv_tcp_state *state)
{
int err;
int retval;
int timeout = 50; /* in milliseconds */
retval = epoll_wait(epoll_fd, events, maxevents, timeout);
if (unlikely(retval == 0)) {
/*
* epoll_wait() reaches timeout
*
* TODO: Do something meaningful here.
*/
/*
* Force the process to read critical data so
* it is always hot (at least in L2 or L3?)
*/
memcmp_explicit(state, state, sizeof(*state));
return 0;
}
if (unlikely(retval < 0)) {
err = errno;
if (err == EINTR) {
retval = 0;
prl_notice(0, "Interrupted!");
return 0;
}
pr_err("epoll_wait(): " PRERF, PREAR(err));
return -err;
}
return retval;
}
static int event_loop(struct srv_tcp_state *state)
{
int retval = 0;
int maxevents = 64;
int epoll_fd = state->epoll_fd;
struct epoll_event events[64];
/* Shut the valgrind up! */
memset(events, 0, sizeof(events));
while (likely(!state->stop)) {
retval = exec_epoll_wait(epoll_fd, events, maxevents, state);
if (unlikely(retval == 0))
continue;
if (unlikely(retval < 0))
goto out;
retval = handle_events(state, events, retval);
if (unlikely(retval < 0))
goto out;
}
out:
return retval;
}
memcmp_explicit
Этот код находится в другом файле, это не позволяет компилятору встроить, оптимизировать или удалить memcmp звонок (что происходит, когда epoll_wait достигает своего тайм-аута).
int memcmp_explicit(const void *s1, const void *s2, size_t n)
{
return memcmp(s1, s2, n);
}
likely и unlikely макросы
#define likely(EXPR) __builtin_expect(!!(EXPR), 1)
#define unlikely(EXPR) __builtin_expect(!!(EXPR), 0)
О __builtin_expect
1 ответ
Поддержание кеша в горячем состоянии
Поэтому моя инициатива состоит в том, чтобы сократить время сна и заставить процесс читать из памяти снова и снова, пока не наступят события.
Причина этого заключается в том, чтобы держать критически важные (для производительности) данные в кэше в горячем состоянии.
Если на том же процессоре ничего не работает, нет причин для охлаждения кеша. Вещи в кэше не истекают по таймауту, они просто остаются там до тех пор, пока не будут выселены, когда это необходимо.
Если есть другие запущенные процессы, ваша стратегия может помочь вашему серверу сокетов, но с этим есть несколько проблем:
- Это эгоистично; вы можете запретить другим процессам хранить свои данные в кэше, что снизит их производительность.
- Это может вообще не сработать, поскольку, как только другой процесс получит временной интервал, его шаблоны доступа к памяти могут снова вытеснить ваши данные из кеша.
- Если не бездействовать, ваш процессор может не получить шанс перейти в состояние низкого энергопотребления, что приведет к тому, что ваш кеш будет буквально горячим.
Я рекомендую просто использовать бесконечный тайм-аут для epoll_wait().
О memcmp_explicit()
Имейте в виду, что компиляторы становятся все лучше и лучше при оптимизации. Даже если поставить memcmp_explicit() в другой единице перевода, чем та, в которой он вызывается, компилятор может оптимизировать его, если оптимизация времени ссылки включен.
