Reactor I/O bare-C misy endri-javatra feno

Reactor I/O bare-C misy endri-javatra feno

fampidirana

I/O reactor (tady tokana hetsika loop) dia lamina hanoratana rindrambaiko be entana, ampiasaina amin'ny vahaolana malaza maro:

Ato amin'ity lahatsoratra ity, isika dia hijery ny ins sy ivelan'ny I/O reactor sy ny fomba fiasan'ny, manoratra fampiharana amin'ny latsaky ny 200 andalana code, ary manao dingana tsotra mpizara HTTP mihoatra ny 40 tapitrisa fangatahana/min.

sasin-teny

  • Ny lahatsoratra dia nosoratana mba hanampiana hahatakatra ny fiasan'ny I/O reactor, ary noho izany dia hahatakatra ny loza rehefa mampiasa azy io.
  • Ilaina ny fahalalana ny fototra mba hahatakarana ny lahatsoratra. C fiteny ary ny traikefa sasany amin'ny fampivoarana ny fampiharana tambajotra.
  • Ny kaody rehetra dia voasoratra amin'ny fiteny C araka ny fepetra (fampitandremana: PDF lava) ny C11 standard ho an'ny Linux ary azo alaina amin'ny GitHub.

Nahoana no ilaina izany?

Miaraka amin'ny fitomboan'ny lazan'ny Internet, ny mpizara tranonkala dia nanomboka nitaky fifandraisana marobe miaraka, ary noho izany dia fomba roa no nosedraina: fanakanana I/O amin'ny kofehy OS marobe ary tsy fanakanana I/O miaraka amin'ny rafitra fampandrenesana hetsika, antsoina koa hoe "selector system" (epoll/kqueue/IOCP/ sns).

Ny fomba voalohany dia ny famoronana kofehy OS vaovao ho an'ny fifandraisana miditra tsirairay. Ny fatiantoka dia ny scalability mahantra: ny rafitra fandidiana dia tsy maintsy mampihatra maro fiovan'ny contexte и antson'ny rafitra. Fampandehanana lafo izy ireo ary mety hitarika amin'ny tsy fahampian'ny RAM maimaim-poana miaraka amin'ny fifandraisana marobe.

Manasongadina ny dikan-teny novaina raikitra isan'ny kofehy (dobo filomanosana), amin'izay dia manakana ny rafitra tsy hanajanona ny famonoana, fa miaraka amin'izay koa dia miteraka olana vaovao: raha misy kofehy misy kofehy voasakana amin'izao fotoana izao noho ny famakiana lava be, dia tsy ho afaka ny sockets hafa izay efa afaka mandray data. ataovy izany.

Ny fomba faharoa dia mampiasa rafitra fampahafantarana hetsika (system selector) nomen'ny OS. Ity lahatsoratra ity dia miresaka momba ny karazana mpifidy rafitra mahazatra indrindra, mifototra amin'ny fanairana (hetsika, fampahafantarana) momba ny fahavononana amin'ny asa I/O, fa tsy amin'ny fampahafantarana momba ny fahavitan'izy ireo. Ny ohatra notsorina amin'ny fampiasana azy dia azo aseho amin'ny diagrama sakana manaraka:

Reactor I/O bare-C misy endri-javatra feno

Ny fahasamihafana eo amin'ireo fomba ireo dia toy izao manaraka izao:

  • Fanakanana ny asa I/O mampiato fikorianan'ny mpampiasa MANDRA-mandra-pahatongan'ny OS araka ny tokony ho izy defragments Narahi- fonosana IP to byte stream (TCP, mandray data) na tsy hisy toerana ampy ao amin'ny buffers fanoratana anatiny ho an'ny fandefasana manaraka amin'ny Nic (mandefa data).
  • Mpifidy rafitra rehefa ela mampahafantatra ny programa fa ny OS efa fonosana IP defragmented (TCP, fandraisana data) na habaka ampy amin'ny buffer fanoratana anatiny efa misy (mandefa data).

Raha fintinina dia fandaniam-poana ny herin'ny informatika ny famandrihana kofehy OS ho an'ny I/O tsirairay, satria raha ny tena izy dia tsy manao asa mahasoa ny kofehy (noho izany ny teny hoe "fanelingelenana rindrambaiko"). Mamaha ity olana ity ny mpifidy rafitra, mamela ny programa mpampiasa hampiasa loharanon-karena CPU ara-toekarena kokoa.

Modely reactor I/O

Ny reactor I/O dia miasa toy ny sosona eo anelanelan'ny mpifidy rafitra sy ny kaody mpampiasa. Ny fitsipiky ny fiasan'izy io dia voafaritra amin'ny alàlan'ny diagrama sakana manaraka:

Reactor I/O bare-C misy endri-javatra feno

  • Mamelà ahy hampahatsiahy anao fa ny hetsika dia fampandrenesana fa ny socket sasany dia afaka manao hetsika I/O tsy manakana.
  • Ny mpandrindra hetsika dia asa antsoin'ny reactor I/O rehefa voaray ny hetsika iray, izay manao hetsika I/O tsy manakana.

Zava-dehibe ny manamarika fa ny reactor I / O dia amin'ny famaritana tokana, saingy tsy misy na inona na inona manakana ny foto-kevitra tsy hampiasaina amin'ny tontolo misy kofehy maromaro amin'ny tahan'ny kofehy 1: 1 reactor, ka mamerina ny cores CPU rehetra.

fanatanterahana

Hapetrakay anaty rakitra ny interface public reactor.h, ary fampiharana - in reactor.c. reactor.h dia ahitana ireto fanambarana manaraka ireto:

Asehoy ny fanambarana ao amin'ny 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);

Ny rafitra reactor I/O dia ahitana famaritana rakitra mpifidy epoll и tabilao hash GHashTable, izay sarintany ny socket tsirairay CallbackData (firafitry ny mpitantana hetsika sy ny tohan-kevitry ny mpampiasa azy).

Asehoy ny Reactor sy CallbackData

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

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

Azafady, mariho fa navelanay ny fahaizana mitantana karazana tsy feno araka ny fanondroana. IN reactor.h manambara ny rafitra izahay reactor, ary ao reactor.c mamaritra azy io izahay, ka manakana ny mpampiasa tsy hanova mazava ny sahany. Ity dia iray amin'ireo modely manafina angona, izay mifanentana amin'ny semantika C.

asa reactor_register, reactor_deregister и reactor_reregister manavao ny lisitr'ireo socket mahaliana sy ireo mpiandraikitra hetsika mifandraika amin'izany ao amin'ny lisitry ny rafitra sy ny latabatra hash.

Asehoy ny asa fisoratana anarana

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

Taorian'ny nanakanan'ny reactor I/O ilay hetsika niaraka tamin'ny descriptor fd, miantso ny mpandrindra hetsika mifanaraka amin'izany, izay alehany fd, saron-tava kely niteraka hetsika sy tondro ho an'ny mpampiasa void.

Asehoy ny fiasa 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;
}

Raha fintinina, ny rojom-pifandraisan'ny antso ao amin'ny kaody mpampiasa dia handray izao endrika manaraka izao:

Reactor I/O bare-C misy endri-javatra feno

Mpizara kofehy tokana

Mba hitsapana ny reactor I/O amin'ny enta-mavesatra be dia hanoratra mpizara tranonkala HTTP tsotra izahay izay mamaly ny fangatahana amin'ny sary.

Filazana haingana momba ny protocol HTTP

HTTP - ity no protocole ambaratonga fampiharana, ampiasaina indrindra amin'ny fifandraisan'ny mpizara-mpitety.

Ny HTTP dia azo ampiasaina mora foana Transport fifanarahana TCP, fandefasana sy fandraisana hafatra amin'ny endrika voafaritra famaritana.

Fomba fangatahana

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

  • CRLF dia andian-tsoratra roa: r и n, manasaraka ny andalana voalohany amin'ny fangatahana, lohapejy ary angona.
  • <КОМАНДА> - iray amin'ny CONNECT, DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT, TRACE. Ny navigateur dia handefa baiko amin'ny servery GET, midika hoe "Alefaso amiko ny ao anatin'ilay rakitra."
  • <URI> - fanamiana loharanon-karena identifier. Ohatra, raha URI = /index.html, dia mangataka ny pejy voalohan'ny tranokala ny mpanjifa.
  • <ВЕРСИЯ HTTP> — dikan-tenin'ny protocol HTTP amin'ny endrika HTTP/X.Y. Ny dikan-teny mahazatra indrindra amin'izao fotoana izao dia HTTP/1.1.
  • <ЗАГОЛОВОК N> dia mpivady sanda fototra amin'ny endrika <КЛЮЧ>: <ЗНАЧЕНИЕ>, alefa any amin'ny mpizara ho an'ny fanadihadiana fanampiny.
  • <ДАННЫЕ> - angon-drakitra takian'ny mpizara amin'ny fanatanterahana ny fandidiana. Matetika dia tsotra JSON na endrika hafa.

Fomba famaliana

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

  • <КОД СТАТУСА> dia isa maneho ny vokatry ny fandidiana. Ny mpizara anay dia hamerina ny sata 200 (fahombiazana mahomby).
  • <ОПИСАНИЕ СТАТУСА> - fanehoana tady ny kaody sata. Ho an'ny code status 200 dia ity OK.
  • <ЗАГОЛОВОК N> - lohatenin'ny endrika mitovy amin'ny fangatahana. Haverinay ny lohateny Content-Length (haben'ny rakitra) ary Content-Type: text/html (karazana data miverina).
  • <ДАННЫЕ> - angona angatahin'ny mpampiasa. Amin'ity tranga ity, ity no lalana mankany amin'ny sary HTML.

rakitra http_server.c (mpizara tokana misy kofehy) dia misy rakitra common.h, izay misy ireto prototypes ireto:

Asehoy ny prototypes fiasa iraisana.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);

Famaritana ihany koa ny macro functional SAFE_CALL() ary voafaritra ny asa fail(). Ny macro dia mampitaha ny sandan'ny fitenenana amin'ny lesoka, ary raha marina ny fepetra dia miantso ny fiasa fail():

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

asa fail() manonta ny tohan-kevitra nandalo tamin'ny terminal (toy ny printf()) ary mamarana ny programa amin'ny 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);
}

asa new_server() mamerina ny fichier descriptor ny socket "server" noforonin'ny system calls socket(), bind() и listen() ary afaka manaiky ny fifandraisana miditra amin'ny fomba tsy manakana.

Asehoy ny fiasa 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;
}

  • Mariho fa ny socket dia noforonina tamin'ny fomba tsy fanakanana tamin'ny fampiasana ny saina SOCK_NONBLOCKka amin'ny asa on_accept() (vakio bebe kokoa) antso an-tariby accept() tsy nanakana ny famonoana kofehy.
  • raha reuse_port mitovy amin'ny true, dia ity fiasa ity dia hanitsy ny socket miaraka amin'ny safidy SO_REUSEPORT ny alalan ' setsockopt()hampiasa seranan-tsambo mitovy amin'ny tontolo misy kofehy maromaro (jereo ny fizarana "Server multi-threaded").

Event Handler on_accept() antsoina rehefa mamorona hetsika ny OS EPOLLIN, amin'ity tranga ity dia midika fa azo ekena ny fifandraisana vaovao. on_accept() manaiky fifandraisana vaovao, mamadika izany amin'ny fomba tsy manakana ary misoratra anarana amin'ny mpitantana hetsika on_recv() amin'ny reactor I/O.

Asehoy ny asa on_accept().

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

Event Handler on_recv() antsoina rehefa mamorona hetsika ny OS EPOLLIN, amin'ity tranga ity dia midika fa nisoratra anarana ny fifandraisana on_accept(), vonona handray angona.

on_recv() mamaky angon-drakitra avy amin'ny fifandraisana mandra-pahazoana tanteraka ny fangatahana HTTP, avy eo dia manoratra mpandrindra on_send() handefa valiny HTTP. Raha tapaka ny fifandraisana ny mpanjifa dia nesorina ny socket ary mihidy amin'ny fampiasana azy close().

Asehoy ny fiasa 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);
    }
}

Event Handler on_send() antsoina rehefa mamorona hetsika ny OS EPOLLOUT, midika izany fa nisoratra anarana ny fifandraisana on_recv(), vonona handefa angona. Ity fampiasa ity dia mandefa valinteny HTTP misy HTML miaraka amin'ny sary ho an'ny mpanjifa ary avy eo dia manova ny mpitantana hetsika on_recv().

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

Ary farany, amin'ny rakitra http_server.c, amin'ny asany main() mamorona reactor I/O mampiasa reactor_new(), mamorona socket server ary misoratra anarana izany, atombohy ny reactor mampiasa reactor_run() mandritra ny iray minitra katroka, ary avy eo dia mamoaka loharano izahay ary mivoaka ny programa.

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

Andeha hojerentsika fa mandeha araka ny nantenaina ny zava-drehetra. Manangona (chmod a+x compile.sh && ./compile.sh ao amin'ny fototry ny tetikasa) ary atombohy ny mpizara sora-tena, sokafy http://127.0.0.1:18470 ao amin'ny navigateur ary jereo izay nantenainay:

Reactor I/O bare-C misy endri-javatra feno

Fandrefesana fahombiazana

Asehoy ny mombamomba ny fiarako

$ 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

Andeha horefesina ny fahombiazan'ny mpizara misy kofehy tokana. Andao hanokatra terminal roa: amin'ny iray dia hihazakazaka isika ./http_server, amin'ny fiteny hafa - wrk. Rehefa afaka iray minitra dia hiseho ao amin'ny terminal faharoa ireto antontan'isa manaraka ireto:

$ 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

Nahavita nikarakara fangatahana mihoatra ny 11 tapitrisa isa-minitra avy amin'ny fifandraisana 100 ny mpizara misy kofehy tokana. Tsy vokatra ratsy, fa azo hatsaraina?

Mpizara maromaro

Araka ny voalaza etsy ambony, ny reactor I / O dia azo noforonina amin'ny kofehy misaraka, ka mampiasa ny CPU rehetra. Andao hampihatra ity fomba fiasa ity:

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

Ankehitriny ny kofehy tsirairay manana ny azy reactor:

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

Mariho fa ny argument function new_server() mpiaro true. Midika izany fa manendry ny safidy amin'ny socket server izahay SO_REUSEPORThampiasa azy io amin'ny tontolo misy kofehy maromaro. Afaka mamaky antsipiriany bebe kokoa ianao eto.

Hazakazaka faharoa

Andeha horefesina ny fahombiazan'ny mpizara maromaro misy kofehy:

$ 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

Nitombo ~1 heny ny isan'ny fangatahana voakarakara tao anatin'ny 3.28 minitra! Saingy ~XNUMX tapitrisa monja no tsy ampy tamin'ny isa boribory, ka andeha isika hanamboatra izany.

Andeha aloha hojerentsika ny antontan'isa vokarina tonga lafatra:

$ 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

Mampiasa CPU Affinity, fanangonana miaraka amin'ny -march=native, PGO, fitomboan'ny isan'ny hitifitra cache, mitombo MAX_EVENTS ary mampiasa EPOLLET tsy nanome fitomboana lehibe amin'ny fampisehoana. Inona anefa no mitranga raha mampitombo ny isan'ny fifandraisana simultaneous?

Statistika momba ny fifandraisana 352 simultaneous:

$ 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

Ny vokatra tadiavina dia azo, ary miaraka amin'izany ny grafika mahaliana mampiseho ny fiankinan'ny isan'ny fangatahana voakarakara ao anatin'ny 1 minitra amin'ny isan'ny fifandraisana:

Reactor I/O bare-C misy endri-javatra feno

Hitantsika fa taorian'ny fifandraisana an-jatony roa dia nihena be ny fangatahana nokarakaraina ho an'ny mpizara roa (amin'ny dikan-teny maromaro misy kofehy io dia miharihary kokoa). Mifandray amin'ny fampiharana stack Linux TCP/IP ve izany? Aza misalasala manoratra ny eritreritrao momba ity fitondran-tenan'ny grafika ity sy ny fanatsarana ny safidy maromaro misy kofehy sy kofehy tokana ao amin'ny fanehoan-kevitra.

Ahoana no nanamarika Ao amin'ny fanehoan-kevitra, ity fitsapana fampisehoana ity dia tsy mampiseho ny fihetsiky ny reactor I/O eo ambanin'ny enta-mavesatra, satria saika matetika ny mpizara dia mifandray amin'ny angon-drakitra, mamoaka logs, mampiasa kriptografika amin'ny TLS sns., ka lasa tsy mitovy ny entana (dynamique). Ny fitsapana miaraka amin'ireo singa avy amin'ny antoko fahatelo dia hatao ao amin'ny lahatsoratra momba ny proactor I/O.

Ny tsy fahampian'ny reactor I/O

Tokony ho takatrao fa ny reactor I/O dia tsy misy tsy fahampiana, izany hoe:

  • Ny fampiasana reactor I/O amin'ny tontolo misy kofehy maromaro dia somary sarotra kokoa, satria tsy maintsy mitantana amin'ny tananao ny fikoriana ianao.
  • Ny fanazaran-tena dia mampiseho fa amin'ny ankamaroan'ny toe-javatra dia tsy mitovy ny entana, izay mety hitarika amin'ny fametahana kofehy iray raha ny iray kosa sahirana amin'ny asa.
  • Raha manakana kofehy iray ny mpitantana hetsika iray, dia ny mpifidy rafitra ihany koa no hisakana, izay mety hitarika amin'ny bibikely sarotra hita.

Mamaha ireo olana ireo I/O proactor, izay matetika manana fandaharam-potoana izay mizara mitovy ny entana amin'ny dobo kofehy, ary manana API mora kokoa. Hiresaka momba izany isika any aoriana, ao amin’ny lahatsoratro hafa.

famaranana

Eto no nifarana ny diantsika avy amin'ny teoria mankany amin'ny setroka profiler.

Tsy tokony hieritreritra an'izany ianao, satria misy fomba maro hafa mahaliana amin'ny fanoratana rindrambaiko amin'ny tambajotra miaraka amin'ny haavo sy hafainganam-pandeha samihafa. Mahaliana, raha ny hevitro, dia omena eto ambany ny rohy.

Mandra-pihaona!

Tetikasa mahaliana

Inona koa no tokony hovakiko?

Source: www.habr.com

Add a comment