Bug-os nga gipakita nga hubo-C I/O reactor

Bug-os nga gipakita nga hubo-C I/O reactor

Pasiuna

I/O nga reaktor (usa ka sinulid loop sa panghitabo) maoy usa ka sumbanan sa pagsulat sa high-load software, nga gigamit sa daghang popular nga mga solusyon:

Niini nga artikulo, atong tan-awon ang ins and outs sa usa ka I/O reactor ug kung giunsa kini pagtrabaho, pagsulat sa usa ka pagpatuman sa ubos sa 200 ka linya sa code, ug paghimo sa usa ka yano nga proseso sa HTTP server nga labaw sa 40 ka milyon nga mga hangyo/min.

Pasiuna

  • Gisulat ang artikulo aron matabangan nga masabtan ang paggana sa I/O reactor, ug busa masabtan ang mga peligro kung gamiton kini.
  • Ang kahibalo sa mga sukaranan gikinahanglan aron masabtan ang artikulo. C nga pinulongan ug pipila ka kasinatian sa pagpalambo sa aplikasyon sa network.
  • Ang tanan nga kodigo gisulat sa C nga pinulongan nga estrikto sumala sa (pasidaan: taas nga PDF) sa C11 standard alang sa Linux ug magamit sa GitHub.

Nganong gikinahanglan kini?

Uban sa nagkadako nga pagkapopular sa Internet, ang mga web server nagsugod sa panginahanglan sa pagdumala sa usa ka dako nga gidaghanon sa mga koneksyon nga dungan, ug busa duha ka mga pamaagi ang gisulayan: pag-block sa I/O sa usa ka dako nga gidaghanon sa mga OS thread ug non-blocking I/O inubanan sa usa ka sistema sa pagpahibalo sa panghitabo, gitawag usab nga "system selector" (epoll/kqueue/IOCP/etc).

Ang una nga pamaagi naglakip sa paghimo og bag-ong OS thread alang sa matag umaabot nga koneksyon. Ang disbentaha niini mao ang dili maayo nga scalability: ang operating system kinahanglan nga ipatuman ang daghan mga transisyon sa konteksto и sistema nga tawag. Kini mga mahal nga operasyon ug mahimong mosangpot sa kakulang sa libre nga RAM nga adunay impresibo nga gidaghanon sa mga koneksyon.

Ang giusab nga bersyon nagpasiugda pirmi nga gidaghanon sa mga hilo (thread pool), sa ingon nagpugong sa sistema sa pag-abort sa pagpatuman, apan sa samang higayon nagpaila sa usa ka bag-ong problema: kung ang usa ka thread pool sa pagkakaron gibabagan sa dugay nga pagbasa nga mga operasyon, nan ang ubang mga socket nga nakadawat na og datos dili na makahimo buhata kana.

Ang ikaduha nga pamaagi gigamit sistema sa pagpahibalo sa panghitabo (system selector) nga gihatag sa OS. Kini nga artikulo naghisgot sa labing komon nga matang sa tigpili sa sistema, base sa mga alerto (mga panghitabo, mga pahibalo) mahitungod sa pagkaandam alang sa mga operasyon sa I/O, imbes sa mga pahibalo bahin sa ilang pagkompleto. Ang usa ka gipayano nga pananglitan sa paggamit niini mahimong irepresentar sa mosunod nga block diagram:

Bug-os nga gipakita nga hubo-C I/O reactor

Ang kalainan tali niini nga mga pamaagi mao ang mosunod:

  • Pag-block sa mga operasyon sa I/O suspensohon dagan sa tiggamit hangtodhangtod sa husto ang OS mga defragment umaabot Mga pakete sa IP sa byte stream (TCP, pagdawat sa datos) o walay igo nga luna nga magamit sa internal nga pagsulat buffers alang sa sunod nga pagpadala pinaagi sa NIC (pagpadala sa datos).
  • Tigpili sa sistema sa paglabay sa panahon nagpahibalo sa programa nga ang OS na defragmented IP packets (TCP, data reception) o igo nga luna sa internal write buffers na magamit (pagpadala data).

Sa pagsumada niini, ang pagreserba sa usa ka OS nga thread alang sa matag I/O usa ka pag-usik sa gahum sa pag-compute, tungod kay sa pagkatinuod, ang mga hilo wala nagabuhat sa mapuslanon nga trabaho (kini diin ang termino gikan sa "pagputol sa software"). Gisulbad sa tigpili sa sistema kini nga problema, nga gitugotan ang programa sa gumagamit nga magamit ang mga kapanguhaan sa CPU nga labi ka ekonomikanhon.

I/O reactor nga modelo

Ang I/O reactor naglihok isip layer tali sa system selector ug sa user code. Ang prinsipyo sa operasyon niini gihulagway sa mosunod nga block diagram:

Bug-os nga gipakita nga hubo-C I/O reactor

  • Pahinumdumi ko nimo nga ang usa ka panghitabo usa ka pahibalo nga ang usa ka socket makahimo sa usa ka non-blocking nga operasyon sa I/O.
  • Ang event handler usa ka function nga gitawag sa I/O reactor kung ang usa ka event madawat, nga unya mohimo ug non-blocking I/O operation.

Mahinungdanon nga hinumdoman nga ang I / O reactor pinaagi sa kahulugan nga single-threaded, apan wala’y makapugong sa konsepto nga magamit sa usa ka multi-threaded nga palibot sa ratio nga 1 thread: 1 reactor, sa ingon gi-recycle ang tanan nga mga cores sa CPU.

Pagpatuman

Atong ibutang ang public interface sa usa ka file reactor.h, ug pagpatuman - sa reactor.c. reactor.h maglangkob sa mosunod nga mga pahibalo:

Ipakita ang mga deklarasyon sa reactor.h

typedef struct reactor Reactor;

/*
 * Указатель на функцию, которая будет вызываться I/O реактором при поступлении
 * события от системного селектора.
 */
typedef void (*Callback)(void *arg, int fd, uint32_t events);

/*
 * Возвращает `NULL` в случае ошибки, не-`NULL` указатель на `Reactor` в
 * противном случае.
 */
Reactor *reactor_new(void);

/*
 * Освобождает системный селектор, все зарегистрированные сокеты в данный момент
 * времени и сам I/O реактор.
 *
 * Следующие функции возвращают -1 в случае ошибки, 0 в случае успеха.
 */
int reactor_destroy(Reactor *reactor);

int reactor_register(const Reactor *reactor, int fd, uint32_t interest,
                     Callback callback, void *callback_arg);
int reactor_deregister(const Reactor *reactor, int fd);
int reactor_reregister(const Reactor *reactor, int fd, uint32_t interest,
                       Callback callback, void *callback_arg);

/*
 * Запускает цикл событий с тайм-аутом `timeout`.
 *
 * Эта функция передаст управление вызывающему коду если отведённое время вышло
 * или/и при отсутствии зарегистрированных сокетов.
 */
int reactor_run(const Reactor *reactor, time_t timeout);

Ang I/O reactor structure naglangkob sa deskriptor sa file tigpili epoll и hash nga mga lamesa GHashTable, nga nag-mapa sa matag socket sa CallbackData (istruktura sa usa ka tigdumala sa panghitabo ug usa ka argumento sa tiggamit alang niini).

Ipakita ang Reactor ug CallbackData

struct reactor {
    int epoll_fd;
    GHashTable *table; // (int, CallbackData)
};

typedef struct {
    Callback callback;
    void *arg;
} CallbackData;

Palihug timan-i nga kami nakahimo sa abilidad sa pagdumala dili kompleto nga tipo sumala sa indeks. SA reactor.h gipahayag namon ang istruktura reactorug sa reactor.c gihubit namo kini, sa ingon nagpugong sa tiggamit sa dayag nga pagbag-o sa mga natad niini. Kini usa sa mga pattern pagtago sa datos, nga haom kaayo sa C semantics.

Mga katuyoan reactor_register, reactor_deregister и reactor_reregister i-update ang lista sa mga socket sa interes ug katugbang nga mga tigdumala sa panghitabo sa system selector ug hash table.

Ipakita ang mga gimbuhaton sa pagparehistro

#define REACTOR_CTL(reactor, op, fd, interest)                                 
    if (epoll_ctl(reactor->epoll_fd, op, fd,                                   
                  &(struct epoll_event){.events = interest,                    
                                        .data = {.fd = fd}}) == -1) {          
        perror("epoll_ctl");                                                   
        return -1;                                                             
    }

int reactor_register(const Reactor *reactor, int fd, uint32_t interest,
                     Callback callback, void *callback_arg) {
    REACTOR_CTL(reactor, EPOLL_CTL_ADD, fd, interest)
    g_hash_table_insert(reactor->table, int_in_heap(fd),
                        callback_data_new(callback, callback_arg));
    return 0;
}

int reactor_deregister(const Reactor *reactor, int fd) {
    REACTOR_CTL(reactor, EPOLL_CTL_DEL, fd, 0)
    g_hash_table_remove(reactor->table, &fd);
    return 0;
}

int reactor_reregister(const Reactor *reactor, int fd, uint32_t interest,
                       Callback callback, void *callback_arg) {
    REACTOR_CTL(reactor, EPOLL_CTL_MOD, fd, interest)
    g_hash_table_insert(reactor->table, int_in_heap(fd),
                        callback_data_new(callback, callback_arg));
    return 0;
}

Human ma-intercept sa I/O reactor ang panghitabo gamit ang descriptor fd, kini nagtawag sa katugbang nga tigdumala sa panghitabo, diin kini moagi fd, gamay nga maskara namugna nga mga panghitabo ug usa ka pointer sa user sa void.

Ipakita ang function sa reactor_run().

int reactor_run(const Reactor *reactor, time_t timeout) {
    int result;
    struct epoll_event *events;
    if ((events = calloc(MAX_EVENTS, sizeof(*events))) == NULL)
        abort();

    time_t start = time(NULL);

    while (true) {
        time_t passed = time(NULL) - start;
        int nfds =
            epoll_wait(reactor->epoll_fd, events, MAX_EVENTS, timeout - passed);

        switch (nfds) {
        // Ошибка
        case -1:
            perror("epoll_wait");
            result = -1;
            goto cleanup;
        // Время вышло
        case 0:
            result = 0;
            goto cleanup;
        // Успешная операция
        default:
            // Вызвать обработчиков событий
            for (int i = 0; i < nfds; i++) {
                int fd = events[i].data.fd;

                CallbackData *callback =
                    g_hash_table_lookup(reactor->table, &fd);
                callback->callback(callback->arg, fd, events[i].events);
            }
        }
    }

cleanup:
    free(events);
    return result;
}

Sa pag-summarize, ang kadena sa mga tawag sa function sa user code magkuha sa mosunod nga porma:

Bug-os nga gipakita nga hubo-C I/O reactor

Usa ka thread nga server

Aron masulayan ang I/O reactor ubos sa taas nga load, magsulat kami og yano nga HTTP web server nga motubag sa bisan unsang hangyo gamit ang imahe.

Usa ka dali nga pakisayran sa HTTP protocol

http - kini ang protocol lebel sa aplikasyon, panguna nga gigamit alang sa interaksyon sa server-browser.

Ang HTTP dali nga magamit transportasyon protokol TCP, pagpadala ug pagdawat sa mga mensahe sa usa ka porma nga gitakda espesipikasyon.

Format sa Paghangyo

<КОМАНДА> <URI> <ВЕРСИЯ HTTP>CRLF
<ЗАГОЛОВОК 1>CRLF
<ЗАГОЛОВОК 2>CRLF
<ЗАГОЛОВОК N>CRLF CRLF
<ДАННЫЕ>

  • CRLF usa ka han-ay sa duha ka karakter: r и n, nga nagbulag sa unang linya sa hangyo, mga ulohan ug datos.
  • <КОМАНДА> - usa sa CONNECT, DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT, TRACE. Ang browser magpadala usa ka mando sa among server GET, nagpasabut nga "Ipadala kanako ang mga sulod sa file."
  • <URI> - uniporme nga resource identifier. Pananglitan, kung URI = /index.html, unya gihangyo sa kliyente ang panguna nga panid sa site.
  • <ВЕРСИЯ HTTP> — bersyon sa HTTP protocol sa format HTTP/X.Y. Ang labing kasagarang gigamit nga bersyon karon mao ang HTTP/1.1.
  • <ЗАГОЛОВОК N> kay usa ka key-value pares sa format <КЛЮЧ>: <ЗНАЧЕНИЕ>, gipadala sa server para sa dugang nga pagtuki.
  • <ДАННЫЕ> - datos nga gikinahanglan sa server aron mahimo ang operasyon. Kasagaran kini yano JSON o bisan unsang lain nga pormat.

Format sa Tubag

<ВЕРСИЯ HTTP> <КОД СТАТУСА> <ОПИСАНИЕ СТАТУСА>CRLF
<ЗАГОЛОВОК 1>CRLF
<ЗАГОЛОВОК 2>CRLF
<ЗАГОЛОВОК N>CRLF CRLF
<ДАННЫЕ>

  • <КОД СТАТУСА> mao ang numero nga nagrepresentar sa resulta sa operasyon. Ang among server kanunay nga mobalik sa status 200 (malampuson nga operasyon).
  • <ОПИСАНИЕ СТАТУСА> — string nga representasyon sa status code. Para sa status code 200 kini OK.
  • <ЗАГОЛОВОК N> — header sa parehas nga format sama sa gihangyo. Atong ibalik ang mga titulo Content-Length (gidak-on sa file) ug Content-Type: text/html (ibalik ang tipo sa datos).
  • <ДАННЫЕ> - data nga gihangyo sa user. Sa among kaso, kini ang agianan sa imahe sa HTML.

file http_server.c (single threaded server) naglakip sa file common.h, nga naglangkob sa mosunod nga function prototypes:

Ipakita ang mga prototype sa function nga parehas.h

/*
 * Обработчик событий, который вызовется после того, как сокет будет
 * готов принять новое соединение.
 */
static void on_accept(void *arg, int fd, uint32_t events);

/*
 * Обработчик событий, который вызовется после того, как сокет будет
 * готов отправить HTTP ответ.
 */
static void on_send(void *arg, int fd, uint32_t events);

/*
 * Обработчик событий, который вызовется после того, как сокет будет
 * готов принять часть HTTP запроса.
 */
static void on_recv(void *arg, int fd, uint32_t events);

/*
 * Переводит входящее соединение в неблокирующий режим.
 */
static void set_nonblocking(int fd);

/*
 * Печатает переданные аргументы в stderr и выходит из процесса с
 * кодом `EXIT_FAILURE`.
 */
static noreturn void fail(const char *format, ...);

/*
 * Возвращает файловый дескриптор сокета, способного принимать новые
 * TCP соединения.
 */
static int new_server(bool reuse_port);

Gihulagway usab ang functional macro SAFE_CALL() ug ang function gihubit fail(). Gitandi sa macro ang kantidad sa ekspresyon sa sayup, ug kung tinuod ang kondisyon, tawgon ang function fail():

#define SAFE_CALL(call, error)                                                 
    do {                                                                       
        if ((call) == error) {                                                   
            fail("%s", #call);                                                 
        }                                                                      
    } while (false)

function fail() nag-imprinta sa gipasa nga mga argumento sa terminal (sama sa printf()) ug tapuson ang programa gamit ang code EXIT_FAILURE:

static noreturn void fail(const char *format, ...) {
    va_list args;
    va_start(args, format);
    vfprintf(stderr, format, args);
    va_end(args);
    fprintf(stderr, ": %sn", strerror(errno));
    exit(EXIT_FAILURE);
}

function new_server() ibalik ang file descriptor sa "server" socket nga gihimo sa mga tawag sa sistema socket(), bind() и listen() ug makahimo sa pagdawat sa umaabot nga mga koneksyon sa usa ka non-blocking mode.

Ipakita ang new_server() function

static int new_server(bool reuse_port) {
    int fd;
    SAFE_CALL((fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP)),
              -1);

    if (reuse_port) {
        SAFE_CALL(
            setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &(int){1}, sizeof(int)),
            -1);
    }

    struct sockaddr_in addr = {.sin_family = AF_INET,
                               .sin_port = htons(SERVER_PORT),
                               .sin_addr = {.s_addr = inet_addr(SERVER_IPV4)},
                               .sin_zero = {0}};

    SAFE_CALL(bind(fd, (struct sockaddr *)&addr, sizeof(addr)), -1);
    SAFE_CALL(listen(fd, SERVER_BACKLOG), -1);
    return fd;
}

  • Timan-i nga ang socket sa sinugdan gihimo sa non-blocking mode gamit ang bandila SOCK_NONBLOCKaron sa function on_accept() (basaha ang dugang) tawag sa sistema accept() wala mihunong sa pagpatuman sa thread.
  • kon reuse_port parehas sa true, unya kini nga function mag-configure sa socket nga adunay kapilian SO_REUSEPORT pinaagi sa setsockopt()sa paggamit sa samang pantalan sa usa ka multi-threaded nga palibot (tan-awa ang seksyon nga "Multi-threaded server").

Handler sa Panghitabo on_accept() gitawag human ang OS makamugna og panghitabo EPOLLIN, sa kini nga kaso nagpasabut nga ang bag-ong koneksyon mahimong madawat. on_accept() modawat ug bag-ong koneksyon, mobalhin niini ngadto sa non-blocking mode ug magparehistro sa usa ka event handler on_recv() sa usa ka I/O reactor.

Ipakita ang on_accept() function

static void on_accept(void *arg, int fd, uint32_t events) {
    int incoming_conn;
    SAFE_CALL((incoming_conn = accept(fd, NULL, NULL)), -1);
    set_nonblocking(incoming_conn);
    SAFE_CALL(reactor_register(reactor, incoming_conn, EPOLLIN, on_recv,
                               request_buffer_new()),
              -1);
}

Handler sa Panghitabo on_recv() gitawag human ang OS makamugna og panghitabo EPOLLIN, sa kini nga kaso nagpasabut nga ang koneksyon narehistro on_accept(), andam sa pagdawat sa datos.

on_recv() nagbasa sa datos gikan sa koneksyon hangtud nga ang HTTP nga hangyo hingpit nga madawat, unya kini nagparehistro sa usa ka handler on_send() aron magpadala usa ka tubag sa HTTP. Kung ang kliyente maguba ang koneksyon, ang socket matangtang sa rehistro ug sirado ang paggamit close().

Ipakita ang function on_recv()

static void on_recv(void *arg, int fd, uint32_t events) {
    RequestBuffer *buffer = arg;

    // Принимаем входные данные до тех пор, что recv возвратит 0 или ошибку
    ssize_t nread;
    while ((nread = recv(fd, buffer->data + buffer->size,
                         REQUEST_BUFFER_CAPACITY - buffer->size, 0)) > 0)
        buffer->size += nread;

    // Клиент оборвал соединение
    if (nread == 0) {
        SAFE_CALL(reactor_deregister(reactor, fd), -1);
        SAFE_CALL(close(fd), -1);
        request_buffer_destroy(buffer);
        return;
    }

    // read вернул ошибку, отличную от ошибки, при которой вызов заблокирует
    // поток
    if (errno != EAGAIN && errno != EWOULDBLOCK) {
        request_buffer_destroy(buffer);
        fail("read");
    }

    // Получен полный HTTP запрос от клиента. Теперь регистрируем обработчика
    // событий для отправки данных
    if (request_buffer_is_complete(buffer)) {
        request_buffer_clear(buffer);
        SAFE_CALL(reactor_reregister(reactor, fd, EPOLLOUT, on_send, buffer),
                  -1);
    }
}

Handler sa Panghitabo on_send() gitawag human ang OS makamugna og panghitabo EPOLLOUT, nagpasabot nga ang koneksyon narehistro on_recv(), andam sa pagpadala sa datos. Ang kini nga function nagpadala usa ka tubag sa HTTP nga adunay HTML nga adunay usa ka imahe sa kliyente ug dayon giusab ang tigdumala sa panghitabo balik on_recv().

Ipakita ang on_send() function

static void on_send(void *arg, int fd, uint32_t events) {
    const char *content = "<img "
                          "src="https://habrastorage.org/webt/oh/wl/23/"
                          "ohwl23va3b-dioerobq_mbx4xaw.jpeg">";
    char response[1024];
    sprintf(response,
            "HTTP/1.1 200 OK" CRLF "Content-Length: %zd" CRLF "Content-Type: "
            "text/html" DOUBLE_CRLF "%s",
            strlen(content), content);

    SAFE_CALL(send(fd, response, strlen(response), 0), -1);
    SAFE_CALL(reactor_reregister(reactor, fd, EPOLLIN, on_recv, arg), -1);
}

Ug sa katapusan, sa file http_server.c, sa pag-obra main() naghimo kita ug I/O reactor gamit reactor_new(), paghimo ug server socket ug irehistro kini, sugdi ang reactor gamit reactor_run() sa eksaktong usa ka minuto, ug dayon buhian namo ang mga kapanguhaan ug mogawas sa programa.

Ipakita ang http_server.c

#include "reactor.h"

static Reactor *reactor;

#include "common.h"

int main(void) {
    SAFE_CALL((reactor = reactor_new()), NULL);
    SAFE_CALL(
        reactor_register(reactor, new_server(false), EPOLLIN, on_accept, NULL),
        -1);
    SAFE_CALL(reactor_run(reactor, SERVER_TIMEOUT_MILLIS), -1);
    SAFE_CALL(reactor_destroy(reactor), -1);
}

Atong susihon nga ang tanan nagtrabaho sama sa gipaabut. Pag-compile (chmod a+x compile.sh && ./compile.sh sa gamut sa proyekto) ug ilunsad ang self-written server, bukas http://127.0.0.1:18470 sa browser ug tan-awa kung unsa ang among gipaabut:

Bug-os nga gipakita nga hubo-C I/O reactor

Pagsukod sa performance

Ipakita ang mga detalye sa akong awto

$ screenfetch
 MMMMMMMMMMMMMMMMMMMMMMMMMmds+.        OS: Mint 19.1 tessa
 MMm----::-://////////////oymNMd+`     Kernel: x86_64 Linux 4.15.0-20-generic
 MMd      /++                -sNMd:    Uptime: 2h 34m
 MMNso/`  dMM    `.::-. .-::.` .hMN:   Packages: 2217
 ddddMMh  dMM   :hNMNMNhNMNMNh: `NMm   Shell: bash 4.4.20
     NMm  dMM  .NMN/-+MMM+-/NMN` dMM   Resolution: 1920x1080
     NMm  dMM  -MMm  `MMM   dMM. dMM   DE: Cinnamon 4.0.10
     NMm  dMM  -MMm  `MMM   dMM. dMM   WM: Muffin
     NMm  dMM  .mmd  `mmm   yMM. dMM   WM Theme: Mint-Y-Dark (Mint-Y)
     NMm  dMM`  ..`   ...   ydm. dMM   GTK Theme: Mint-Y [GTK2/3]
     hMM- +MMd/-------...-:sdds  dMM   Icon Theme: Mint-Y
     -NMm- :hNMNNNmdddddddddy/`  dMM   Font: Noto Sans 9
      -dMNs-``-::::-------.``    dMM   CPU: Intel Core i7-6700 @ 8x 4GHz [52.0°C]
       `/dMNmy+/:-------------:/yMMM   GPU: NV136
          ./ydNMMMMMMMMMMMMMMMMMMMMM   RAM: 2544MiB / 7926MiB
             .MMMMMMMMMMMMMMMMMMM

Atong sukdon ang performance sa usa ka single-threaded server. Atong ablihan ang duha ka mga terminal: sa usa kita modagan ./http_server, sa lahi nga - wrk. Human sa usa ka minuto, ang mosunod nga estadistika ipakita sa ikaduhang terminal:

$ wrk -c100 -d1m -t8 http://127.0.0.1:18470 -H "Host: 127.0.0.1:18470" -H "Accept-Language: en-US,en;q=0.5" -H "Connection: keep-alive"
Running 1m test @ http://127.0.0.1:18470
  8 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   493.52us   76.70us  17.31ms   89.57%
    Req/Sec    24.37k     1.81k   29.34k    68.13%
  11657769 requests in 1.00m, 1.60GB read
Requests/sec: 193974.70
Transfer/sec:     27.19MB

Ang among single-threaded server nakahimo sa pagproseso sa kapin sa 11 ka milyon nga hangyo kada minuto gikan sa 100 ka koneksyon. Dili usa ka daotan nga resulta, apan mahimo ba kini nga mapauswag?

Multithreaded nga server

Sama sa nahisgutan sa ibabaw, ang I/O reactor mahimong mabuhat sa lainlain nga mga hilo, sa ingon magamit ang tanan nga mga core sa CPU. Atong ibutang kini nga pamaagi sa praktis:

Ipakita ang http_server_multithreaded.c

#include "reactor.h"

static Reactor *reactor;
#pragma omp threadprivate(reactor)

#include "common.h"

int main(void) {
#pragma omp parallel
    {
        SAFE_CALL((reactor = reactor_new()), NULL);
        SAFE_CALL(reactor_register(reactor, new_server(true), EPOLLIN,
                                   on_accept, NULL),
                  -1);
        SAFE_CALL(reactor_run(reactor, SERVER_TIMEOUT_MILLIS), -1);
        SAFE_CALL(reactor_destroy(reactor), -1);
    }
}

Karon ang matag hilo nanag-iya sa iyang kaugalingon reaktor:

static Reactor *reactor;
#pragma omp threadprivate(reactor)

Palihug timan-i nga ang function argument new_server() pabor true. Nagpasabot kini nga gi-assign namo ang opsyon sa socket sa server SO_REUSEPORTsa paggamit niini sa usa ka multi-threaded palibot. Makabasa ka ug dugang detalye dinhi.

Ikaduhang dagan

Karon atong sukdon ang performance sa usa ka multi-threaded server:

$ wrk -c100 -d1m -t8 http://127.0.0.1:18470 -H "Host: 127.0.0.1:18470" -H "Accept-Language: en-US,en;q=0.5" -H "Connection: keep-alive"
Running 1m test @ http://127.0.0.1:18470
  8 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.14ms    2.53ms  40.73ms   89.98%
    Req/Sec    79.98k    18.07k  154.64k    78.65%
  38208400 requests in 1.00m, 5.23GB read
Requests/sec: 635876.41
Transfer/sec:     89.14MB

Ang gidaghanon sa mga hangyo nga giproseso sulod sa 1 ka minuto misaka ug ~3.28 ka beses! Apan kulang ra kami sa ~XNUMX milyon sa round number, busa sulayan naton nga ayohon kana.

Una atong tan-awon ang mga estadistika nga nahimo perf:

$ sudo perf stat -B -e task-clock,context-switches,cpu-migrations,page-faults,cycles,instructions,branches,branch-misses,cache-misses ./http_server_multithreaded

 Performance counter stats for './http_server_multithreaded':

     242446,314933      task-clock (msec)         #    4,000 CPUs utilized          
         1 813 074      context-switches          #    0,007 M/sec                  
             4 689      cpu-migrations            #    0,019 K/sec                  
               254      page-faults               #    0,001 K/sec                  
   895 324 830 170      cycles                    #    3,693 GHz                    
   621 378 066 808      instructions              #    0,69  insn per cycle         
   119 926 709 370      branches                  #  494,653 M/sec                  
     3 227 095 669      branch-misses             #    2,69% of all branches        
           808 664      cache-misses                                                

      60,604330670 seconds time elapsed

Paggamit sa CPU Affinity, compilation uban sa -march=native, PGO, pagdugang sa gidaghanon sa mga hit cache, pagdugang MAX_EVENTS ug gamit EPOLLET wala maghatag ug dakong pagsaka sa performance. Apan unsa ang mahitabo kung imong dugangan ang gidaghanon sa dungan nga mga koneksyon?

Estadistika para sa 352 ka dungan nga koneksyon:

$ wrk -c352 -d1m -t8 http://127.0.0.1:18470 -H "Host: 127.0.0.1:18470" -H "Accept-Language: en-US,en;q=0.5" -H "Connection: keep-alive"
Running 1m test @ http://127.0.0.1:18470
  8 threads and 352 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     2.12ms    3.79ms  68.23ms   87.49%
    Req/Sec    83.78k    12.69k  169.81k    83.59%
  40006142 requests in 1.00m, 5.48GB read
Requests/sec: 665789.26
Transfer/sec:     93.34MB

Nakuha ang gitinguha nga resulta, ug uban niini ang usa ka makapaikag nga graph nga nagpakita sa pagsalig sa gidaghanon sa giproseso nga mga hangyo sa 1 minuto sa gidaghanon sa mga koneksyon:

Bug-os nga gipakita nga hubo-C I/O reactor

Nakita namon nga pagkahuman sa usa ka gatos nga mga koneksyon, ang gidaghanon sa giproseso nga mga hangyo alang sa duha nga mga server mikunhod pag-ayo (sa multi-threaded nga bersyon kini mas mamatikdan). May kalabotan ba kini sa pagpatuman sa Linux TCP/IP stack? Mobati nga gawasnon sa pagsulat sa imong mga pangagpas mahitungod niini nga kinaiya sa graph ug mga pag-optimize alang sa multi-threaded ug single-threaded nga mga kapilian sa mga komento.

Sa unsang paagi nga namatikdan sa mga komentaryo, kini nga performance test wala magpakita sa kinaiya sa I/O reactor ubos sa tinuod nga mga load, tungod kay halos kanunay ang server nakig-interact sa database, output logs, naggamit sa cryptography nga adunay TLS ug uban pa, ingon usa ka sangputanan diin ang karga mahimong dili managsama (dinamikong). Ang mga pagsulay kauban ang mga sangkap sa ikatulo nga partido himuon sa artikulo bahin sa I/O proactor.

Mga disbentaha sa I/O reactor

Kinahanglan nimong masabtan nga ang I/O reactor kay walay mga kakulian, nga mao:

  • Ang paggamit sa usa ka I/O reactor sa usa ka multi-threaded nga palibot medyo mas lisud, tungod kay kinahanglan nimo nga mano-mano ang pagdumala sa mga dagan.
  • Gipakita sa praktis nga sa kadaghanan nga mga kaso ang load dili uniporme, nga mahimong mosangpot sa usa ka thread logging samtang ang lain busy sa trabaho.
  • Kung ang usa ka event handler mag-block sa usa ka thread, ang system selector mismo mo-block usab, nga mahimong mosangpot sa lisud nga pagpangita sa mga bug.

Pagsulbad niini nga mga problema I/O proactor, nga sa kasagaran adunay usa ka scheduler nga parehas nga nag-apod-apod sa load sa usa ka pool sa mga hilo, ug usab adunay usa ka mas kombenyente nga API. Atong hisgotan kini sa ulahi, sa akong laing artikulo.

konklusyon

Dinhi natapos ang among panaw gikan sa teorya diretso sa tambutso sa profiler.

Dili nimo kini kinahanglan nga hunahunaon, tungod kay adunay daghang uban pang parehas nga makapaikag nga mga pamaagi sa pagsulat sa software sa network nga adunay lainlaing lebel sa kasayon ​​​​ug katulin. Makapainteres, sa akong opinyon, ang mga link gihatag sa ubos.

Magkita ta pag-usab!

Makapaikag nga mga proyekto

Unsa pa ang basahon?

Source: www.habr.com

Idugang sa usa ka comment