Full-chin an tap fè-C I/O réacteurs

Full-chin an tap fè-C I/O réacteurs

Entwodiksyon

I/O réacteurs (yon sèl fil bouk evènman) se yon modèl pou ekri lojisyèl gwo chaj, yo itilize nan anpil solisyon popilè:

Nan atik sa a, nou pral gade nan sa ki genyen nan yon reaktè I/O ak fason li fonksyone, ekri yon aplikasyon nan mwens pase 200 liy nan kòd, epi fè yon pwosesis senp sèvè HTTP plis pase 40 milyon demann / min.

Avètisman

  • Atik la te ekri pou ede konprann fonksyònman I/O réacteurs, Et donc konprann risk ki genyen lè w ap itilize li.
  • Konesans de baz yo oblije konprann atik la. lang C ak kèk eksperyans nan devlopman aplikasyon rezo.
  • Tout kòd ekri nan lang C entèdi dapre (prekosyon: PDF long) nan estanda C11 pou Linux ak disponib sou GitHub.

Poukisa sa nesesè?

Avèk popilarite k ap grandi nan Entènèt la, sèvè entènèt yo te kòmanse bezwen okipe yon gwo kantite koneksyon ansanm, ak Se poutèt sa de apwòch yo te eseye: bloke I / O sou yon gwo kantite fil OS ak ki pa bloke I / O an konbinezon ak yon sistèm notifikasyon evènman, yo rele tou "seleksyon sistèm" (epol/kqueue/IOCP/etc).

Premye apwòch la enplike kreye yon nouvo fil OS pou chak koneksyon fèk ap rantre. Dezavantaj li se pòv évolutivité: sistèm operasyon an ap gen pou aplike anpil tranzisyon kontèks и apèl sistèm yo. Yo se operasyon chè epi yo ka mennen nan yon mank de RAM gratis ak yon kantite enpresyonan koneksyon.

Vèsyon an modifye mete aksan sou kantite fil fiks (pisin fil), kidonk anpeche sistèm nan avòte ekzekisyon, men an menm tan an entwodwi yon nouvo pwoblèm: si yon pisin fil se kounye a bloke pa operasyon lekti long, Lè sa a, lòt sipò ki deja kapab resevwa done yo pa pral kapab. fè sa.

Dezyèm apwòch la itilize sistèm notifikasyon evènman (Seleksyon sistèm) ki ofri pa OS la. Atik sa a diskite sou kalite ki pi komen nan seleksyon sistèm, ki baze sou alèt (evènman, notifikasyon) sou preparasyon pou operasyon I/O, olye ke sou notifikasyon sou fini yo. Yon egzanp senplifye itilizasyon li yo ka reprezante pa dyagram blòk sa a:

Full-chin an tap fè-C I/O réacteurs

Diferans ki genyen ant apwòch sa yo se jan sa a:

  • Bloke operasyon I/O sispann koule itilizatè jiskaskejiskaske eksplwatasyon an byen defragmantasyon fèk ap rantre IP pake to byte stream (Tchp, k ap resevwa done) oswa pa pral gen ase espas ki disponib nan tanpon ekri entèn yo pou voye apre yo atravè Nic (voye done).
  • Seleksyon sistèm sou tan notifye pwogram nan ke OS la deja pake IP defragmante (TCP, resepsyon done) oswa ase espas nan tanpon ekri entèn yo deja disponib (voye done).

Pou rezime li, rezève yon fil OS pou chak I/O se yon fatra nan pouvwa informatique, paske an reyalite, fil yo pa fè travay itil (kidonk tèm nan "entèwonp lojisyèl"). Seleksyon sistèm nan rezoud pwoblèm sa a, ki pèmèt pwogram itilizatè a sèvi ak resous CPU pi plis ekonomikman.

I/O modèl réacteurs

Reaktè I/O aji kòm yon kouch ant seleksyon sistèm lan ak kòd itilizatè a. Prensip operasyon li yo dekri nan dyagram blòk sa a:

Full-chin an tap fè-C I/O réacteurs

  • Kite m fè w sonje ke yon evènman se yon notifikasyon ke yon priz sèten kapab fè yon operasyon I/O ki pa bloke.
  • Yon moun kap okipe evènman an se yon fonksyon ki rele I/O réacteurs lè yo resevwa yon evènman, ki answit fè yon operasyon I/O ki pa bloke.

Li enpòtan pou sonje ke I/O reyaktè a se pa definisyon yon sèl-threaded, men pa gen anyen sispann konsèp nan yo te itilize nan yon anviwònman milti-threaded nan yon rapò nan 1 fil: 1 raktor, kidonk resiklaj tout nwayo CPU.

Aplikasyon

Nou pral mete koòdone piblik la nan yon dosye reactor.h, ak aplikasyon - nan reactor.c. reactor.h pral konpoze de anons sa yo:

Montre deklarasyon nan 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);

Estrikti réacteurs I/O a konsiste de deskriptè dosye seleksyon epol и tab hash GHashTable, ki kat chak priz CallbackData (estrikti yon moun kap okipe evènman ak yon agiman itilizatè pou li).

Montre Reactor ak CallbackData

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

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

Tanpri sonje ke nou te pèmèt kapasite nan okipe kalite enkonplè dapre endèks la. NAN reactor.h nou deklare estrikti a reactor, ak nan reactor.c nou defini li, kidonk anpeche itilizatè a chanje klèman jaden li yo. Sa a se youn nan modèl yo kache done yo, ki byen anfòm nan C semantik.

Fonksyon reactor_register, reactor_deregister и reactor_reregister aktyalize lis sipò enterè yo ak moun kap okipe evènman ki koresponn lan nan seleksyon sistèm lan ak tab hash.

Montre fonksyon enskripsyon yo

#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;
}

Apre I/O reyaktè a te entèsepte evènman an ak deskriptè a fd, li rele moun kap okipe evènman ki koresponn lan, kote li pase fd, ti jan mask evènman ki te pwodwi ak yon konsèy itilizatè a void.

Montre fonksyon 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;
}

Pou rezime, chèn apèl fonksyon nan kòd itilizatè a pral pran fòm sa a:

Full-chin an tap fè-C I/O réacteurs

Single sèvè threaded

Yo nan lòd yo teste I / O reyaktè a anba gwo chaj, nou pral ekri yon senp sèvè entènèt HTTP ki reponn a nenpòt demann ak yon imaj.

Yon referans rapid sou pwotokòl HTTP

HTTP - sa a se pwotokòl la nivo aplikasyon an, prensipalman itilize pou entèraksyon sèvè-navigatè.

HTTP ka fasil pou itilize sou transpò pwotokòl Tchp, voye ak resevwa mesaj nan yon fòma espesifye spesifikasyon.

Fòma demann

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

  • CRLF se yon sekans de karaktè: r и n, separe premye liy demann lan, headers ak done.
  • <КОМАНДА> - youn nan CONNECT, DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT, TRACE. Navigatè a pral voye yon lòd sou sèvè nou an GET, sa vle di "Voye m 'sa ki nan dosye a."
  • <URI> - idantifyan resous inifòm. Pou egzanp, si URI = /index.html, Lè sa a, kliyan an mande paj prensipal la nan sit la.
  • <ВЕРСИЯ HTTP> — vèsyon pwotokòl HTTP a nan fòma a HTTP/X.Y. Vèsyon ki pi souvan itilize jodi a se HTTP/1.1.
  • <ЗАГОЛОВОК N> se yon pè kle-valè nan fòma a <КЛЮЧ>: <ЗНАЧЕНИЕ>, voye bay sèvè a pou plis analiz.
  • <ДАННЫЕ> — done sèvè a mande pou fè operasyon an. Souvan li senp JSON oswa nenpòt lòt fòma.

Fòma repons

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

  • <КОД СТАТУСА> se yon nimewo ki reprezante rezilta operasyon an. Sèvè nou an ap toujou retounen estati 200 (operasyon siksè).
  • <ОПИСАНИЕ СТАТУСА> — reprezantasyon kòd estati a. Pou estati kòd 200 sa a se OK.
  • <ЗАГОЛОВОК N> — header nan menm fòma ak nan demann lan. Nou pral retounen tit yo Content-Length (gwosè dosye) ak Content-Type: text/html (retounen kalite done).
  • <ДАННЫЕ> - done itilizatè a mande yo. Nan ka nou an, sa a se chemen an nan imaj la nan HTML.

dosye http_server.c (sèl sèvè threaded) gen ladann fichye common.h, ki gen pwototip fonksyon sa yo:

Montre pwototip fonksyon an komen.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);

Yo dekri tou makro fonksyonèl la SAFE_CALL() epi fonksyon an defini fail(). Makro a konpare valè ekspresyon an ak erè a, epi si kondisyon an se vre, li rele fonksyon an fail():

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

Fonksyon fail() enprime agiman yo pase nan tèminal la (tankou printf()) epi mete fen nan pwogram nan ak kòd la 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);
}

Fonksyon new_server() retounen deskriptè dosye a nan priz "sèvè" ki te kreye pa apèl sistèm yo socket(), bind() и listen() ak kapab aksepte koneksyon fèk ap rantre nan yon mòd ki pa bloke.

Montre new_server() fonksyon

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;
}

  • Remake byen ke priz la okòmansman kreye nan mòd ki pa bloke lè l sèvi avèk drapo a SOCK_NONBLOCKse konsa ke nan fonksyon an on_accept() (li plis) apèl sistèm accept() pa t sispann ekzekisyon fil la.
  • Si reuse_port egal true, Lè sa a, fonksyon sa a pral configured priz la ak opsyon an SO_REUSEPORT pa setsockopt()pou itilize menm pò a nan yon anviwònman milti-threaded (gade seksyon "Multi-threaded sèvè").

Evènman Handler on_accept() rele apre OS la jenere yon evènman EPOLLIN, nan ka sa a sa vle di ke nouvo koneksyon an ka aksepte. on_accept() aksepte yon nouvo koneksyon, chanje li nan mòd ki pa bloke epi anrejistre ak yon moun kap okipe evènman an on_recv() nan yon réacteurs I/O.

Montre on_accept() fonksyon

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);
}

Evènman Handler on_recv() rele apre OS la jenere yon evènman EPOLLIN, nan ka sa a sa vle di ke koneksyon an anrejistre on_accept(), pare pou resevwa done.

on_recv() li done ki soti nan koneksyon an jiskaske demann HTTP an konplètman resevwa, Lè sa a, li anrejistre yon moun kap okipe on_send() pou voye yon repons HTTP. Si kliyan an kraze koneksyon an, priz la derejistre epi fèmen lè l sèvi avèk close().

Montre fonksyon 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);
    }
}

Evènman Handler on_send() rele apre OS la jenere yon evènman EPOLLOUT, sa vle di ke koneksyon an anrejistre on_recv(), pare pou voye done. Fonksyon sa a voye yon repons HTTP ki gen HTML ak yon imaj bay kliyan an epi li chanje moun kap okipe evènman an on_recv().

Montre on_send() fonksyon

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);
}

Epi finalman, nan dosye a http_server.c, nan fonksyon main() nou kreye yon I/O réacteurs itilize reactor_new(), kreye yon priz sèvè epi anrejistre li, kòmanse raktor a lè l sèvi avèk reactor_run() pou egzakteman yon minit, ak Lè sa a, nou lage resous ak sòti pwogram nan.

Montre 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);
}

Ann tcheke ke tout bagay ap travay jan yo espere. Konpile (chmod a+x compile.sh && ./compile.sh nan rasin pwojè a) epi lanse sèvè a ekri pwòp tèt ou, louvri http://127.0.0.1:18470 nan navigatè a epi wè sa nou te espere:

Full-chin an tap fè-C I/O réacteurs

Mezi pèfòmans

Montre espesifikasyon machin mwen an

$ 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

Ann mezire pèfòmans yon sèvè yon sèl-threaded. Ann louvri de tèminal: nan youn nou pral kouri ./http_server, nan yon lòt - travay. Apre yon minit, estatistik sa yo pral parèt nan dezyèm tèminal la:

$ 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

Sèvè yon sèl fil nou an te kapab trete plis pase 11 milyon demann pou chak minit ki soti nan 100 koneksyon. Se pa yon move rezilta, men èske li ka amelyore?

Sèvè Multithreaded

Kòm mansyone pi wo a, yo ka kreye réacteurs I/O nan fil separe, kidonk itilize tout nwayo CPU. Ann mete apwòch sa a an pratik:

Montre 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);
    }
}

Koulye a, chak fil posede pwòp tèt li raktor:

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

Tanpri sonje ke agiman an fonksyon new_server() defansè yo true. Sa vle di ke nou bay opsyon a nan priz sèvè a SO_REUSEPORTpou itilize li nan yon anviwònman milti-threaded. Ou ka li plis detay isit la.

Dezyèm kouri

Koulye a, kite a mezire pèfòmans nan yon sèvè milti-threaded:

$ 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

Kantite demann yo trete nan 1 minit ogmante pa ~3.28 fwa! Men, nou te sèlman ~ XNUMX milyon kout nan nimewo wonn lan, kidonk ann eseye ranje sa.

Premyèman, ann gade nan estatistik yo ki te pwodwi pèf:

$ 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

Sèvi ak CPU afinite, konpilasyon ak -march=native, PGO, yon ogmantasyon nan kantite frape kachèt, ogmante MAX_EVENTS epi sèvi ak EPOLLET pa t bay yon ogmantasyon siyifikatif nan pèfòmans. Men, sa k ap pase si ou ogmante kantite koneksyon similtane?

Estatistik pou 352 koneksyon similtane:

$ 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

Yo te jwenn rezilta a vle, epi avèk li yon graf enteresan ki montre depandans kantite demann trete nan 1 minit sou kantite koneksyon:

Full-chin an tap fè-C I/O réacteurs

Nou wè ke apre yon koup la san koneksyon, kantite demann trete pou tou de sèvè gout sevè (nan vèsyon an milti-threaded sa a se pi plis aparan). Èske sa a gen rapò ak aplikasyon Linux TCP/IP pil? Ezite ekri sipozisyon ou sou konpòtman sa a nan graf la ak optimize pou opsyon milti-threaded ak yon sèl-threaded nan kòmantè yo.

Kòm te note nan kòmantè yo, tès pèfòmans sa a pa montre konpòtman I/O réacteurs anba chay reyèl, paske prèske toujou sèvè a reyaji ak baz done a, pwodiksyon mòso bwa, sèvi ak kriptografi ak tl elatriye, kòm yon rezilta chaj la vin pa inifòm (dinamik). Tès ansanm ak eleman twazyèm pati yo pral fèt nan atik la sou I/O proactor la.

Dezavantaj nan I/O réacteurs

Ou bezwen konprann ke I/O reyaktè a pa san dezavantaj li yo, sètadi:

  • Sèvi ak yon réacteurs I/O nan yon anviwonman milti-threaded yon ti jan pi difisil, paske w ap gen manyèlman jere koule yo.
  • Pratike montre ke nan pifò ka chay la pa inifòm, sa ki ka mennen nan yon sèl fil antre pandan yon lòt okipe ak travay.
  • Si yon sèl moun kap okipe evènman bloke yon fil, Lè sa a, seleksyon sistèm nan tèt li pral bloke tou, sa ki ka mennen nan pinèz difisil pou jwenn.

Rezoud pwoblèm sa yo I/O proactor, ki souvan gen yon pwogramè ki respire distribye chay la nan yon pisin nan fil, epi tou li gen yon API ki pi pratik. Nou pral pale sou li pita, nan lòt atik mwen an.

Konklizyon

Sa a se kote vwayaj nou an soti nan teyori tou dwat nan tiyo echapman an profiler rive nan yon fen.

Ou pa ta dwe rete sou sa a, paske gen anpil lòt apwòch egalman enteresan nan ekri lojisyèl rezo ak diferan nivo konvenyans ak vitès. Enteresan, nan opinyon mwen, yo bay lyen anba a.

Nou wè byento!

Pwojè enteresan

Ki lòt bagay pou li?

Sous: www.habr.com

Add nouvo kòmantè