Reattur bare-C I/O bis-sħiħ

Reattur bare-C I/O bis-sħiħ

Introduzzjoni

Reattur I/O (kamin wieħed ċirku tal-avveniment) huwa mudell għall-kitba ta' softwer b'tagħbija għolja, użat f'ħafna soluzzjonijiet popolari:

F'dan l-artikolu, se nħarsu lejn l-iżvantaġġi ta 'reattur I/O u kif jaħdem, niktbu implimentazzjoni f'inqas minn 200 linja ta' kodiċi, u nagħmlu proċess sempliċi ta 'server HTTP fuq 40 miljun talba/min.

Daħla

  • L-artiklu nkiteb biex jgħin biex jifhem il-funzjonament tar-reattur I/O, u għalhekk jifhem ir-riskji meta jużah.
  • L-għarfien tal-baŜi huwa meħtieġ biex tifhem l-artiklu. Lingwa C u xi esperjenza fl-iżvilupp ta 'applikazzjoni tan-netwerk.
  • Il-kodiċi kollu huwa miktub bil-lingwa C strettament skont (attenzjoni: PDF twil) għall-istandard C11 għal Linux u disponibbli fuq GitHub.

Għaliex dan huwa meħtieġ?

Bil-popolarità dejjem tikber tal-Internet, is-servers tal-web bdew jeħtieġu li jimmaniġġjaw numru kbir ta 'konnessjonijiet fl-istess ħin, u għalhekk ġew ippruvati żewġ approċċi: imblukkar I/O fuq numru kbir ta' ħjut tal-OS u I/O li ma jimblokkax flimkien ma ' sistema ta’ notifika ta’ avveniment, imsejħa wkoll “selettur tas-sistema” (epoll/kqueue/IOCP/ eċċ).

L-ewwel approċċ kien jinvolvi l-ħolqien ta 'ħajt OS ġdid għal kull konnessjoni deħlin. L-iżvantaġġ tiegħu huwa l-iskalabbiltà fqira: is-sistema operattiva se jkollha timplimenta ħafna transizzjonijiet tal-kuntest и sejħiet tas-sistema. Huma operazzjonijiet għaljin u jistgħu jwasslu għal nuqqas ta 'RAM ħielsa b'numru impressjonanti ta' konnessjonijiet.

Il-verżjoni modifikata tenfasizza numru fiss ta 'ħjut (thread pool), u b'hekk tipprevjeni lis-sistema milli tħassar l-eżekuzzjoni, iżda fl-istess ħin tintroduċi problema ġdida: jekk thread pool bħalissa tkun imblukkata minn operazzjonijiet ta' qari fit-tul, allura sockets oħra li diġà jistgħu jirċievu data ma jkunux jistgħu tagħmel hekk.

It-tieni approċċ juża sistema ta’ notifika tal-avvenimenti (selettur tas-sistema) ipprovdut mill-OS. Dan l-artikolu jiddiskuti l-aktar tip komuni ta’ selettur tas-sistema, ibbażat fuq twissijiet (avvenimenti, notifiki) dwar it-tħejjija għal operazzjonijiet I/O, aktar milli fuq notifiki dwar it-tlestija tagħhom. Eżempju simplifikat tal-użu tiegħu jista’ jiġi rappreżentat bid-dijagramma blokk li ġejja:

Reattur bare-C I/O bis-sħiħ

Id-differenza bejn dawn l-approċċi hija kif ġej:

  • Imblukkar ta 'operazzjonijiet I/O jissospendi fluss tal-utent sakemmsakemm l-OS huwa kif suppost deframmenti deħlin Pakketti IP għal byte stream (TCP, tirċievi dejta) jew ma jkunx hemm biżżejjed spazju disponibbli fil-buffers tal-kitba interni biex jintbagħtu sussegwenti permezz NIC (jibgħat id-dejta).
  • Selettur tas-sistema maż-żmien jinnotifika lill-programm li l-OS diġà pakketti IP deframmentati (TCP, riċeviment tad-dejta) jew spazju biżżejjed fil-buffers tal-kitba interni diġà disponibbli (li tibgħat id-dejta).

Fil-qosor, ir-riżerva ta’ ħajt tal-OS għal kull I/O hija ħela ta’ qawwa tal-kompjuters, għax fir-realtà, il-ħjut mhux qed jagħmlu xogħol utli (għalhekk it-terminu "interruzzjoni tas-softwer"). Is-selettur tas-sistema jsolvi din il-problema, u jippermetti lill-programm tal-utent juża r-riżorsi tas-CPU b'mod ferm aktar ekonomiku.

Mudell tar-reattur I/O

Ir-reattur I/O jaġixxi bħala saff bejn is-selettur tas-sistema u l-kodiċi tal-utent. Il-prinċipju tat-tħaddim tiegħu huwa deskritt mid-dijagramma blokka li ġejja:

Reattur bare-C I/O bis-sħiħ

  • Ħa nfakkarkom li avveniment huwa notifika li ċertu socket huwa kapaċi jwettaq operazzjoni I/O li ma jimblokkax.
  • Maniġer tal-avvenimenti huwa funzjoni msejħa mir-reattur tal-I/O meta jiġi riċevut avveniment, li mbagħad iwettaq operazzjoni tal-I/O li ma jimblokkax.

Huwa importanti li wieħed jinnota li r-reattur I/O huwa b'definizzjoni b'ħajt wieħed, iżda m'hemm xejn li jwaqqaf il-kunċett milli jintuża f'ambjent b'ħafna kamini bi proporzjon ta 'ħajt 1: reattur 1, u b'hekk jiġu riċiklati l-qlub kollha tas-CPU.

Реализация

Se npoġġu l-interface pubblika f'fajl reactor.h, u l-implimentazzjoni - in reactor.c. reactor.h se jikkonsisti fl-avviżi li ġejjin:

Uri dikjarazzjonijiet fir-reattur.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);

L-istruttura tar-reattur I/O tikkonsisti minn deskrittur tal-fajl selettur epoll и imwejjed tal-hash GHashTable, li mapep kull socket għal CallbackData (struttura ta' handler tal-avvenimenti u argument tal-utent għalih).

Uri Reattur u CallbackData

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

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

Jekk jogħġbok innota li ppermettejna l-abbiltà li timmaniġġa tip mhux komplut skond l-indiċi. IN reactor.h aħna niddikjaraw l-istruttura reactor, u ġewwa reactor.c aħna niddefinixxuha, u b'hekk nipprevjenu lill-utent milli jbiddel espliċitament l-oqsma tiegħu. Din hija waħda mill-mudelli ħabi tad-data, li fil-qosor tidħol fis-semantika C.

Funzjonijiet reactor_register, reactor_deregister и reactor_reregister taġġorna l-lista tas-sokits ta 'interess u l-immaniġġjar tal-avvenimenti korrispondenti fis-selettur tas-sistema u t-tabella tal-hash.

Uri funzjonijiet ta 'reġistrazzjoni

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

Wara li r-reattur I/O ikun interċetta l-avveniment bid-deskrittur fd, isejjaħ lill-immaniġġjar tal-avvenimenti korrispondenti, li jgħaddi għalih fd, maskra bit avvenimenti ġġenerati u pointer tal-utent għal void.

Uri l-funzjoni 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;
}

Fil-qosor, il-katina ta 'sejħiet ta' funzjoni fil-kodiċi tal-utent se tieħu l-forma li ġejja:

Reattur bare-C I/O bis-sħiħ

Server b'kamin wieħed

Sabiex nittestjaw ir-reattur I/O taħt tagħbija għolja, aħna se niktbu server web HTTP sempliċi li jirrispondi għal kwalunkwe talba b'immaġni.

Referenza rapida għall-protokoll HTTP

HTTP - dan huwa l-protokoll livell ta' applikazzjoni, primarjament użat għall-interazzjoni server-browser.

HTTP jista 'jintuża faċilment fuq trasport protokoll TCP, tibgħat u tirċievi messaġġi f'format speċifikat speċifikazzjoni.

Format tat-Talba

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

  • CRLF hija sekwenza ta' żewġ karattri: r и n, li tissepara l-ewwel linja tat-talba, l-intestaturi u d-dejta.
  • <КОМАНДА> - wieħed minn CONNECT, DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT, TRACE. Il-browser se jibgħat kmand lis-server tagħna GET, li jfisser "Ibgħatli l-kontenut tal-fajl."
  • <URI> - identifikatur tar-riżorsi uniformi. Per eżempju, jekk URI = /index.html, imbagħad il-klijent jitlob il-paġna ewlenija tas-sit.
  • <ВЕРСИЯ HTTP> — verżjoni tal-protokoll HTTP fil-format HTTP/X.Y. L-aktar verżjoni użata llum hija HTTP/1.1.
  • <ЗАГОЛОВОК N> huwa par ta' valur-ċavetta fil-format <КЛЮЧ>: <ЗНАЧЕНИЕ>, mibgħuta lis-server għal aktar analiżi.
  • <ДАННЫЕ> — data meħtieġa mis-server biex iwettaq l-operazzjoni. Ħafna drabi huwa sempliċi JSON jew kwalunkwe format ieħor.

Format ta' Rispons

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

  • <КОД СТАТУСА> huwa numru li jirrappreżenta r-riżultat tal-operazzjoni. Is-server tagħna dejjem se jirritorna l-istatus 200 (operazzjoni b'suċċess).
  • <ОПИСАНИЕ СТАТУСА> — rappreżentazzjoni ta' string tal-kodiċi tal-istatus. Għall-kodiċi tal-istatus 200 dan huwa OK.
  • <ЗАГОЛОВОК N> — header tal-istess format bħal fit-talba. Se nirritornaw it-titli Content-Length (daqs tal-fajl) u Content-Type: text/html (it-tip tad-data tar-ritorn).
  • <ДАННЫЕ> — data mitluba mill-utent. Fil-każ tagħna, din hija t-triq għall-immaġni fil HTML.

fajl http_server.c (server b'kamin wieħed) jinkludi fajl common.h, li fih il-prototipi tal-funzjoni li ġejjin:

Uri prototipi tal-funzjoni b'mod komuni.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);

Il-makro funzjonali hija deskritta wkoll SAFE_CALL() u l-funzjoni hija definita fail(). Il-makro tqabbel il-valur tal-espressjoni mal-iżball, u jekk il-kundizzjoni hija vera, isejjaħ il-funzjoni fail():

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

Funzjoni fail() jistampa l-argumenti mgħoddija lit-terminal (bħal printf()) u jtemm il-programm bil-kodiċi 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);
}

Funzjoni new_server() jirritorna d-deskrittur tal-fajl tas-socket "server" maħluqa mis-sejħiet tas-sistema socket(), bind() и listen() u kapaċi li jaċċettaw konnessjonijiet deħlin f'mod li ma jimblokkax.

Uri l-funzjoni new_server().

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

  • Innota li s-sokit inizjalment jinħoloq f'mod li ma jimblokka bl-użu tal-bandiera SOCK_NONBLOCKsabiex fil-funzjoni on_accept() (aqra aktar) sejħa tas-sistema accept() ma waqfitx l-eżekuzzjoni tal-ħajt.
  • Jekk reuse_port huwa daqs true, allura din il-funzjoni se tikkonfigura s-sokit bl-għażla SO_REUSEPORT permezz setsockopt()biex tuża l-istess port f'ambjent multi-threaded (ara t-taqsima "Multi-threaded server").

Maniġer tal-Avvenimenti on_accept() imsejħa wara li l-OS jiġġenera avveniment EPOLLIN, f'dan il-każ ifisser li l-konnessjoni l-ġdida tista' tiġi aċċettata. on_accept() jaċċetta konnessjoni ġdida, jaqilbu għall-mod li ma jimblokkax u jirreġistra ma' handler tal-avvenimenti on_recv() f'reattur I/O.

Uri on_accept() funzjoni

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

Maniġer tal-Avvenimenti on_recv() imsejħa wara li l-OS jiġġenera avveniment EPOLLIN, f'dan il-każ ifisser li l-konnessjoni rreġistrata on_accept(), lest biex jirċievi data.

on_recv() jaqra d-dejta mill-konnessjoni sakemm it-talba HTTP tiġi riċevuta kompletament, imbagħad tirreġistra handler on_send() biex tibgħat tweġiba HTTP. Jekk il-klijent jikser il-konnessjoni, is-sokit jiġi dereġistrat u jingħalaq bl-użu close().

Uri l-funzjoni 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);
    }
}

Maniġer tal-Avvenimenti on_send() imsejħa wara li l-OS jiġġenera avveniment EPOLLOUT, li jfisser li l-konnessjoni rreġistrata on_recv(), lest biex jibgħat data. Din il-funzjoni tibgħat rispons HTTP li jkun fih HTML b'immaġni lill-klijent u mbagħad jibdel il-handler tal-avvenimenti lura għal on_recv().

Uri on_send() funzjoni

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

U finalment, fil-fajl http_server.c, fil-funzjoni main() noħolqu reattur I/O bl-użu reactor_new(), Oħloq socket server u rreġistrah, ibda r-reattur bl-użu reactor_run() għal eżattament minuta, u mbagħad nirrilaxxaw ir-riżorsi u noħorġu mill-programm.

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

Ejja niċċekkjaw li kollox qed jaħdem kif mistenni. Kumpilazzjoni (chmod a+x compile.sh && ./compile.sh fl-għerq tal-proġett) u tniedi s-server miktub minnu nnifsu, miftuħ http://127.0.0.1:18470 fil-browser u ara dak li stennejna:

Reattur bare-C I/O bis-sħiħ

Kejl tal-prestazzjoni

Uri l-ispeċifikazzjonijiet tal-karozza tiegħi

$ 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

Ejja nkejlu l-prestazzjoni ta 'server b'ħajt wieħed. Ejja niftħu żewġ terminals: f'wieħed niġru ./http_server, b'mod differenti - xogħol. Wara minuta, l-istatistika li ġejja tintwera fit-tieni 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

Is-server single-threaded tagħna kien kapaċi jipproċessa aktar minn 11-il miljun talba kull minuta li joriġinaw minn 100 konnessjoni. Mhux riżultat ħażin, imma jista 'jitjieb?

Server multithreaded

Kif imsemmi hawn fuq, ir-reattur I/O jista 'jinħoloq f'ħjut separati, u b'hekk jutilizzaw il-qlub kollha tas-CPU. Ejja npoġġu dan l-approċċ fil-prattika:

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

Issa kull ħajta huwa proprjetarju tiegħu reattur:

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

Jekk jogħġbok innota li l-argument tal-funzjoni new_server() jippreferi true. Dan ifisser li aħna jassenjaw l-għażla lis-socket tas-server SO_REUSEPORTbiex tużah f'ambjent b'ħafna kamini. Tista' taqra aktar dettalji hawn.

It-tieni ġirja

Issa ejja nkejlu l-prestazzjoni ta 'server multi-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

In-numru ta' talbiet ipproċessati f'minuta żdied b'~1 darbiet! Imma konna biss ~ 3.28 miljuni inqas min-numru tond, allura ejja nippruvaw nirranġaw dan.

L-ewwel ejja nħarsu lejn l-istatistika ġġenerata perfetta:

$ 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

Bl-użu ta 'CPU Affinity, kumpilazzjoni ma -march=native, PGO, żieda fin-numru ta 'hits cache, żid MAX_EVENTS u l-użu EPOLLET ma tawx żieda sinifikanti fil-prestazzjoni. Imma x'jiġri jekk iżżid in-numru ta 'konnessjonijiet simultanji?

Statistika għal 352 konnessjoni simultanja:

$ 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

Inkiseb ir-riżultat mixtieq, u magħha grafika interessanti li turi d-dipendenza tan-numru ta 'talbiet ipproċessati f'minuta 1 fuq in-numru ta' konnessjonijiet:

Reattur bare-C I/O bis-sħiħ

Naraw li wara ftit mijiet ta 'konnessjonijiet, in-numru ta' talbiet ipproċessati għaż-żewġ servers jonqos drastikament (fil-verżjoni multi-threaded dan huwa aktar notevoli). Dan huwa relatat mal-implimentazzjoni tal-munzell Linux TCP/IP? Ħossok liberu li tikteb is-suppożizzjonijiet tiegħek dwar din l-imġieba tal-graff u l-ottimizzazzjonijiet għal għażliet b'ħafna kamini u b'kamin wieħed fil-kummenti.

Kif innota fil-kummenti, dan it-test tal-prestazzjoni ma jurix l-imġieba tar-reattur I/O taħt tagħbijiet reali, għax kważi dejjem is-server jinteraġixxi mad-database, joħroġ zkuk, juża kriptografija b' TLS eċċ., li b'riżultat tiegħu t-tagħbija ssir mhux uniformi (dinamika). It-testijiet flimkien ma 'komponenti ta' partijiet terzi se jitwettqu fl-artiklu dwar il-proactor I/O.

Żvantaġġi tar-reattur I/O

Trid tifhem li r-reattur I/O mhuwiex mingħajr l-iżvantaġġi tiegħu, jiġifieri:

  • L-użu ta 'reattur I/O f'ambjent b'ħafna kamini huwa kemmxejn aktar diffiċli, għaliex ser ikollok timmaniġġja manwalment il-flussi.
  • Il-prattika turi li fil-biċċa l-kbira tal-każijiet it-tagħbija mhix uniformi, li tista 'twassal għal qtugħ ta' ħajta filwaqt li ieħor ikun okkupat bix-xogħol.
  • Jekk wieħed jimmaniġġja l-avvenimenti jimblokka ħajta, allura s-selettur tas-sistema nnifsu jimblokka wkoll, li jista 'jwassal għal bugs diffiċli biex issibhom.

Issolvi dawn il-problemi Proattur I/O, li ħafna drabi għandu Scheduler li jqassam it-tagħbija b'mod ugwali għal ġabra ta 'ħjut, u għandu wkoll API aktar konvenjenti. Nitkellmu dwarha aktar tard, fl-artiklu l-ieħor tiegħi.

Konklużjoni

Dan huwa fejn il-vjaġġ tagħna mit-teorija dritt għall-exhaust tal-profiler wasal fi tmiemu.

M'għandekx toqgħod fuq dan, għaliex hemm ħafna approċċi oħra ugwalment interessanti għall-kitba ta 'softwer tan-netwerk b'livelli differenti ta' konvenjenza u veloċità. Interessanti, fl-opinjoni tiegħi, links huma mogħtija hawn taħt.

Ara inti darb'oħra!

Proġetti interessanti

X'iktar għandi naqra?

Sors: www.habr.com

Żid kumment