Использование незанятого сервера сокетов для значимых действий (тайм-аут после сна на epoll_wait)

Я пишу сервер приложений, который использует сокет TCP в Linux. Когда нет трафика (данные не отправляются клиентом, нет клиента connect() или же close()), процесс засыпает epoll_wait() при ожидании событий попадают в дескрипторы файлов сокетов.

Что можно делать, пока процесс спит?

Поэтому моя инициатива состоит в том, чтобы сократить время сна и заставить процесс читать из памяти снова и снова, пока не наступят события.

Причина этого заключается в том, чтобы держать критически важные (для производительности) данные в кэше в горячем состоянии.

  • Стоит ли это делать?
  • Или лучше я оставлю процесс в спящем режиме, пока не наступят события?

Мое понимание

Пока процесс спит epoll_wait() слишком долго ядро ​​будет планировать запуск других процессов.

Если мое приложение запланировано слишком долго, его данные в памяти будут вытеснены из кеша, поскольку кеш совместно используется несколькими процессами, тогда данные других процессов будут размещаться в кеше.

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 ответ
1

Поддержание кеша в горячем состоянии

Поэтому моя инициатива состоит в том, чтобы сократить время сна и заставить процесс читать из памяти снова и снова, пока не наступят события.

Причина этого заключается в том, чтобы держать критически важные (для производительности) данные в кэше в горячем состоянии.

Если на том же процессоре ничего не работает, нет причин для охлаждения кеша. Вещи в кэше не истекают по таймауту, они просто остаются там до тех пор, пока не будут выселены, когда это необходимо.

Если есть другие запущенные процессы, ваша стратегия может помочь вашему серверу сокетов, но с этим есть несколько проблем:

  • Это эгоистично; вы можете запретить другим процессам хранить свои данные в кэше, что снизит их производительность.
  • Это может вообще не сработать, поскольку, как только другой процесс получит временной интервал, его шаблоны доступа к памяти могут снова вытеснить ваши данные из кеша.
  • Если не бездействовать, ваш процессор может не получить шанс перейти в состояние низкого энергопотребления, что приведет к тому, что ваш кеш будет буквально горячим.

Я рекомендую просто использовать бесконечный тайм-аут для epoll_wait().

О memcmp_explicit()

Имейте в виду, что компиляторы становятся все лучше и лучше при оптимизации. Даже если поставить memcmp_explicit() в другой единице перевода, чем та, в которой он вызывается, компилятор может оптимизировать его, если оптимизация времени ссылки включен.

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *