Reactair làn-nochdadh lom-C I/O

Reactair làn-nochdadh lom-C I/O

Ro-ràdh

Reactair I/O (snàthainn singilte lùb tachartas) na phàtran airson bathar-bog làn luchd a sgrìobhadh, air a chleachdadh ann am mòran fhuasglaidhean mòr-chòrdte:

San artaigil seo, seallaidh sinn ri taobh a-staigh agus taobh a-muigh reactair I / O agus mar a tha e ag obair, sgrìobhaidh sinn buileachadh ann an nas lugha na loidhnichean còd 200, agus nì sinn pròiseas frithealaiche HTTP sìmplidh thairis air 40 millean iarrtas / mion.

Facal-toisich

  • Chaidh an artaigil a sgrìobhadh gus cuideachadh le bhith a’ tuigsinn gnìomhachd an reactair I/O, agus mar sin a’ tuigsinn nan cunnartan nuair a thathar ga chleachdadh.
  • Tha feum air eòlas air na bunaitean gus an artaigil a thuigsinn. C cànan agus beagan eòlais ann an leasachadh aplacaidean lìonraidh.
  • Tha a h-uile còd sgrìobhte ann an cànan C gu teann a rèir (rabhadh: PDF fada) gu ìre C11 airson Linux agus ri fhaighinn air GitHub.

Carson a tha seo riatanach?

Le fàs mòr-chòrdte air an eadar-lìn, thòisich luchd-frithealaidh lìn a’ feumachdainn àireamh mhòr de cheanglaichean a làimhseachadh aig an aon àm, agus mar sin chaidh dà dhòigh-obrach fheuchainn: a’ bacadh I/O air àireamh mhòr de shnàithleanan OS agus gun a bhith a’ bacadh I/O còmhla ri siostam fios tachartais, ris an canar cuideachd “system selector” (epoll/kqueue/IOCP/etc).

B’ e a’ chiad dòigh-obrach a bhith a’ cruthachadh snàithlean OS ùr airson gach ceangal a bha a’ tighinn a-steach. Is e ana-cothrom a th’ ann droch scalability: feumaidh an siostam obrachaidh mòran a chuir an gnìomh eadar-ghluasadan co-theacsa и gairmean siostam. Tha iad nan obraichean daor agus faodaidh iad leantainn gu dìth RAM an-asgaidh le àireamh iongantach de cheanglaichean.

Tha an dreach atharraichte a’ nochdadh àireamh stèidhichte de snàithleanan (amar snàthainn), mar sin a’ cur casg air an t-siostam bho bhith a’ cur gu bàs, ach aig an aon àm a’ toirt a-steach duilgheadas ùr: ma tha amar snàithlean air a bhacadh an-dràsta le gnìomhachd leughaidh fada, cha bhith e comasach dha socaidean eile a tha comasach air dàta fhaighinn mu thràth. dèan sin.

Tha an dàrna dòigh-obrach a 'cleachdadh siostam fios tachartais (roghnaiche siostam) air a sholarachadh leis an OS. Tha an artaigil seo a’ beachdachadh air an t-seòrsa roghnaiche siostam as cumanta, stèidhichte air rabhaidhean (tachartasan, fiosan) mu cho deònach sa tha obair I/O, seach air fiosan mun chrìochnachadh. Faodar eisimpleir nas sìmplidh de a chleachdadh a riochdachadh leis an diagram bloc a leanas:

Reactair làn-nochdadh lom-C I/O

Tha an eadar-dhealachadh eadar na dòighean-obrach seo mar a leanas:

  • A’ bacadh gnìomhachd I/O cuir stad air sruth luchd-cleachdaidh gusgus am bi an OS ceart defragments a' tighinn a-steach IP pacaidean gu sruth baidht (TCP, a’ faighinn dàta) no cha bhi àite gu leòr ri fhaighinn anns na bufairean sgrìobhaidh a-staigh airson an cur troimhe às deidh sin NIC (a 'cur dàta).
  • Tagraiche siostam Thairis air ùine fios don phrògram gu bheil an OS mar-thà pacaidean IP defragmented (TCP, fàilteachadh dàta) no àite gu leòr ann am bufairean sgrìobhaidh a-staigh mar-thà ri fhaighinn (a’ cur dàta).

Gus geàrr-chunntas a dhèanamh, tha a bhith a’ glèidheadh ​​snàithlean OS airson gach I/O na sgudal air cumhachd coimpiutaireachd, oir ann an da-rìribh, chan eil na snàithleanan a’ dèanamh obair fheumail (mar sin an teirm "briseadh bathar-bog"). Bidh roghnaiche an t-siostaim a’ fuasgladh na duilgheadas seo, a’ leigeil leis a’ phrògram luchd-cleachdaidh goireasan CPU a chleachdadh tòrr nas eaconamaiche.

Modail reactor I/O

Bidh an reactar I / O ag obair mar shreath eadar roghnaichear an t-siostaim agus còd an neach-cleachdaidh. Tha prionnsapal a h-obrachaidh air a mhìneachadh leis an diagram bloc a leanas:

Reactair làn-nochdadh lom-C I/O

  • Leig leam do chuimhneachadh gur e fios a th’ ann an tachartas gu bheil socaid sònraichte comasach air gnìomhachd I/O nach eil a’ bacadh a dhèanamh.
  • Is e gnìomh a th’ ann an làimhseachadh tachartais ris an canar an reactar I/O nuair a gheibhear tachartas, a bhios an uairsin a’ coileanadh gnìomhachd I/O nach eil a’ bacadh.

Tha e cudromach toirt fa-near gu bheil an reactair I / O le mìneachadh aon-snàthainn, ach chan eil dad a ’cur stad air a’ bhun-bheachd bho bhith air a chleachdadh ann an àrainneachd ioma-snàithlean aig co-mheas de 1 snàithlean: 1 reactor, mar sin ag ath-chuairteachadh a h-uile cores CPU.

Реализация

Cuiridh sinn an eadar-aghaidh poblach ann am faidhle reactor.h, agus buileachadh - a-steach reactor.c. reactor.h bidh na sanasan a leanas:

Seall dearbhaidhean ann an 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);

Tha structar an reactar I/O air a dhèanamh suas de tuairisgeul faidhle roghnaiche epoll и bùird hash GHashTable, a tha a’ mapadh gach socaid gu CallbackData (structar neach-làimhseachaidh tachartais agus argamaid neach-cleachdaidh air a shon).

Seall Reactor agus CallbackData

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

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

Thoir an aire gu bheil sinn air comas a làimhseachadh seòrsa neo-iomlan a rèir an clàr-amais. ANNS reactor.h bidh sinn a’ cur an cèill an structair reactor, agus a-steach reactor.c bidh sinn ga mhìneachadh, agus mar sin cha leig sinn leis a’ chleachdaiche na raointean aige atharrachadh gu soilleir. Is e seo aon de na pàtranan falach dàta, a tha gu dlùth a’ freagairt ri C semantics.

Feartan reactor_register, reactor_deregister и reactor_reregister ùraich an liosta de socaidean inntinneach agus luchd-làimhseachaidh tachartais co-fhreagarrach ann an roghnaiche an t-siostaim agus clàr hash.

Seall gnìomhan clàraidh

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

Às deidh don reactair I/O an tachartas a ghlacadh leis an tuairisgeul fd, bidh e a’ gairm an neach-làimhseachaidh tachartais co-fhreagarrach, dha bheil e a’ dol fd, beagan masg tachartasan a chaidh a chruthachadh agus comharra neach-cleachdaidh gu void.

Seall gnìomh 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;
}

Gus geàrr-chunntas a dhèanamh, bidh an t-sreath de ghairmean gnìomh ann an còd cleachdaiche mar a leanas:

Reactair làn-nochdadh lom-C I/O

Frithealaiche snàthaichte singilte

Gus an reactair I / O a dhearbhadh fo luchd àrd, sgrìobhaidh sinn frithealaiche lìn HTTP sìmplidh a fhreagras iarrtas sam bith le ìomhaigh.

Iomradh sgiobalta air protocol HTTP

HTTP - is e seo am protocol ìre tagraidh, air a chleachdadh gu sònraichte airson eadar-obrachadh frithealaiche-brabhsair.

Faodar HTTP a chleachdadh gu furasta thairis air còmhdhail protocol TCP, a 'cur agus a' faighinn teachdaireachdan ann an cruth a chaidh a shònrachadh sònrachadh.

Foirm Iarrtais

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

  • CRLF tha sreath de dhà charactar ann: r и n, a 'sgaradh a' chiad loidhne den iarrtas, cinn-cinn agus dàta.
  • <КОМАНДА> - aon de CONNECT, DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT, TRACE. Cuiridh am brabhsair àithne chun an fhrithealaiche againn GET, meaning "Cuir thugam susbaint an fhaidhle."
  • <URI> - aithnichear goireas èideadh. Mar eisimpleir, ma tha URI = /index.html, an uairsin bidh an neach-dèiligidh ag iarraidh prìomh dhuilleag na làraich.
  • <ВЕРСИЯ HTTP> - dreach den phròtacal HTTP san cruth HTTP/X.Y. Is e an dreach as cumanta an-diugh HTTP/1.1.
  • <ЗАГОЛОВОК N> 's e paidhir prìomh-luach anns a' chruth <КЛЮЧ>: <ЗНАЧЕНИЕ>, air a chuir chun t-seirbheisiche airson tuilleadh sgrùdaidh.
  • <ДАННЫЕ> - dàta a dh’ fheumas an t-seirbheisiche gus an obair a choileanadh. Gu tric tha e sìmplidh JSON no cruth sam bith eile.

Cruth freagairt

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

  • <КОД СТАТУСА> Is e àireamh a tha a’ riochdachadh toradh na h-obrach. Bidh an frithealaiche againn an-còmhnaidh a’ tilleadh inbhe 200 (obrachadh soirbheachail).
  • <ОПИСАНИЕ СТАТУСА> - riochdachadh sreang den chòd inbhe. Airson còd inbhe 200 tha seo OK.
  • <ЗАГОЛОВОК N> - bann-cinn den aon chruth ris an iarrtas. Tillidh sinn na tiotalan Content-Length (meud faidhle) agus Content-Type: text/html (seòrsa dàta tilleadh).
  • <ДАННЫЕ> - dàta a dh’ iarr an neach-cleachdaidh. Anns a 'chùis againn, is e seo an t-slighe chun an ìomhaigh a-steach HTML.

faidhl http_server.c (frithealaiche snàthaichte singilte) a’ toirt a-steach faidhle common.h, anns a bheil na prototypes gnìomh a leanas:

Seall prototypes gnìomh mar as trice.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);

Tha am macro gnìomh air a mhìneachadh cuideachd SAFE_CALL() agus tha an gnìomh air a mhìneachadh fail(). Bidh am macro a 'dèanamh coimeas eadar luach an abairt leis a' mhearachd, agus ma tha an suidheachadh fìor, canar an gnìomh ris fail():

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

gnìomh fail() a’ clò-bhualadh na h-argamaidean a chaidh seachad chun cheann-uidhe (mar printf()) agus a’ cur crìoch air a’ phrògram leis a’ chòd 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);
}

gnìomh new_server() a’ tilleadh tuairisgeul faidhle an t-socaid “frithealaiche” a chaidh a chruthachadh le fiosan siostam socket(), bind() и listen() agus comasach air gabhail ri ceanglaichean a tha a’ tighinn a-steach ann am modh gun bhacadh.

Seall gnìomh 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;
}

  • Thoir an aire gu bheil an socaid air a chruthachadh an toiseach ann am modh neo-bacadh a’ cleachdadh a’ bhratach SOCK_NONBLOCKmar sin anns a' ghnìomh on_accept() (leugh tuilleadh) gairm siostam accept() cha do chuir sin stad air coileanadh an t-snàthainn.
  • ma reuse_port co-ionann true, an uairsin rèitichidh an gnìomh seo an t-socaid leis an roghainn SO_REUSEPORT troimhe setsockopt()gus an aon phort a chleachdadh ann an àrainneachd ioma-snàthainn (faic an earrann “Frithealaiche ioma-snàithleach”).

Làimhseachadh Tachartas on_accept() air a ghairm às deidh don OS tachartas a ghineadh EPOLLIN, anns a 'chùis seo a' ciallachadh gum faodar gabhail ris a 'cheangal ùr. on_accept() a’ gabhail ri ceangal ùr, ga atharrachadh gu modh neo-bacadh agus a’ clàradh le neach-làimhseachaidh tachartais on_recv() ann an reactar I/O.

Seall gnìomh 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);
}

Làimhseachadh Tachartas on_recv() air a ghairm às deidh don OS tachartas a ghineadh EPOLLIN, anns a 'chùis seo a' ciallachadh gu bheil an ceangal clàraichte on_accept(), deiseil airson dàta fhaighinn.

on_recv() a 'leughadh dàta bhon cheangal gus an tèid an t-iarrtas HTTP fhaighinn gu tur, an uairsin bidh e a' clàradh inneal-làimhseachaidh on_send() gus freagairt HTTP a chuir. Ma bhriseas an neach-dèiligidh an ceangal, thèid an t-socaid a dhì-chlàradh agus a dhùnadh le bhith a’ cleachdadh close().

Seall gnìomh 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);
    }
}

Làimhseachadh Tachartas on_send() air a ghairm às deidh don OS tachartas a ghineadh EPOLLOUT, a 'ciallachadh gu bheil an ceangal clàraichte on_recv(), deiseil airson dàta a chuir. Bidh an gnìomh seo a’ cur freagairt HTTP anns a bheil HTML le ìomhaigh chun neach-dèiligidh agus an uairsin ag atharrachadh làimhseachadh an tachartais air ais gu on_recv().

Seall gnìomh on_send ().

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

Agus mu dheireadh, anns an fhaidhle http_server.c, ann an gnìomh main() bidh sinn a’ cruthachadh reactar I/O a’ cleachdadh reactor_new(), cruthaich socaid frithealaiche agus clàraich e, tòisich an reactair a’ cleachdadh reactor_run() airson dìreach aon mhionaid, agus an uairsin bidh sinn a’ leigeil a-mach goireasan agus a ’fàgail a’ phrògram.

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

Feuch an dèan sinn cinnteach gu bheil a h-uile càil ag obair mar a bhiodh dùil. A' cur ri chèile (chmod a+x compile.sh && ./compile.sh ann am freumh a’ phròiseict) agus cuir air bhog am frithealaiche fèin-sgrìobhte, fosgail http://127.0.0.1:18470 sa bhrobhsair agus faic na bha sinn an dùil:

Reactair làn-nochdadh lom-C I/O

Tomhas coileanaidh

Seall mion-chomharrachadh mo chàr

$ 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

Feuch an tomhais sinn coileanadh frithealaiche aon-snàthainn. Fosglamaid dà cheann-uidhe: ann an aon ruithidh sinn ./http_server, ann an caochladh - obair. Às deidh mionaid, thèid na staitistig a leanas a thaisbeanadh san dàrna ceann-uidhe:

$ 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

B’ urrainn don t-seirbheisiche aon-snàthainn againn còrr air 11 millean iarrtas gach mionaid a phròiseasadh a thàinig bho 100 ceangal. Chan e droch thoradh a th’ ann, ach an gabh a leasachadh?

Frithealaiche ioma-snàthainn

Mar a chaidh ainmeachadh gu h-àrd, faodar an reactair I / O a chruthachadh ann an snàithleanan fa leth, agus mar sin a’ cleachdadh a h-uile cores CPU. Leig leinn an dòigh-obrach seo a chur an gnìomh:

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

A-nis a h-uile sreath tha aige fhèin reactar:

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

Thoir an aire gu bheil an argamaid gnìomh new_server() tagraichean true. Tha seo a’ ciallachadh gun sònraich sinn an roghainn gu socaid an fhrithealaiche SO_REUSEPORTa chleachdadh ann an àrainneachd ioma-snàthainn. Faodaidh tu barrachd mion-fhiosrachaidh a leughadh an seo.

An dàrna ruith

A-nis tomhais sinn coileanadh frithealaiche ioma-snàithlean:

$ 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

Chaidh an àireamh de dh’iarrtasan a chaidh a ghiullachd ann an 1 mhionaid suas ~3.28 uair! Ach cha robh sinn ach ~XNUMX mhillean gann air an àireamh chruinn, mar sin feuchaidh sinn ri sin a chàradh.

An toiseach leig dhuinn sùil a thoirt air na staitistig a chaidh a chruthachadh 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

A 'cleachdadh CPU Affinity, cruinneachadh le -march=native, PGO, àrdachadh anns an àireamh de bhuillean tasgadan, àrdachadh MAX_EVENTS agus cleachdadh EPOLLET cha tug e àrdachadh mòr ann an coileanadh. Ach dè a thachras ma mheudaicheas tu an àireamh de cheanglaichean aig an aon àm?

Staitistig airson 352 ceanglaichean aig an aon àm:

$ 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

Chaidh an toradh a bhathas ag iarraidh fhaighinn, agus leis graf inntinneach a 'sealltainn an eisimeileachd air an àireamh de dh'iarrtasan pròiseasaichte ann an 1 mionaid air an àireamh de cheanglaichean:

Reactair làn-nochdadh lom-C I/O

Chì sinn, às deidh dà cheud ceangal, gu bheil an àireamh de dh ’iarrtasan giullachd airson an dà fhrithealaiche a’ tuiteam gu mòr (anns an dreach ioma-snàthainn tha seo nas follaisiche). A bheil seo co-cheangailte ri buileachadh stac Linux TCP/IP? Faodaidh tu do bharailean a sgrìobhadh mun ghiùlan seo den ghraf agus optimizations airson roghainnean ioma-snàthainn agus aon-snàthainn anns na beachdan.

Ciamar thugadh fainear anns na beachdan, chan eil an deuchainn coileanaidh seo a’ sealltainn giùlan an reactair I / O fo luchdan fìor, oir cha mhòr an-còmhnaidh bidh an frithealaiche ag eadar-obrachadh leis an stòr-dàta, logaichean toraidh, a’ cleachdadh cryptography le TLS msaa, agus mar thoradh air an sin bidh an luchd a’ fàs neo-èideadh (fiùghantach). Thèid deuchainnean còmhla ri co-phàirtean treas-phàrtaidh a dhèanamh san artaigil mun proactor I / O.

Eas-bhuannachdan an reactar I/O

Feumaidh tu tuigsinn nach eil an reactar I / O às aonais na h-eas-bhuannachdan aige, is e sin:

  • Tha e nas duilghe a bhith a’ cleachdadh reactair I/O ann an àrainneachd ioma-snàthainn, oir feumaidh tu na sruthan a riaghladh le làimh.
  • Tha cleachdadh a 'sealltainn gu bheil an luchd neo-èideadh sa mhòr-chuid, agus faodaidh seo leantainn gu aon snàithlean a' logadh fhad 'sa tha fear eile trang le obair.
  • Ma chuireas aon neach-làimhseachaidh tachartas casg air snàithlean, cuiridh neach-roghnaiche an t-siostaim fhèin bacadh air cuideachd, agus faodaidh seo leantainn gu mialan a tha doirbh a lorg.

Fuasgail na duilgheadasan sin I/O neach-dearbhaidh, aig a bheil clàr-ama gu tric a bhios a’ sgaoileadh an luchd gu cruinneachadh snàithnean gu cothromach, agus aig a bheil API nas goireasaiche cuideachd. Bruidhnidh sinn mu dheidhinn nas fhaide air adhart, anns an artaigil eile agam.

co-dhùnadh

Seo far a bheil ar turas bho theòiridh dìreach a-steach don inneal-togail profiler air a thighinn gu crìch.

Cha bu chòir dhut fuireach air an seo, oir tha mòran dhòighean-obrach eile ann a tha a cheart cho inntinneach airson bathar-bog lìonra a sgrìobhadh le diofar ìrean de ghoireasachd agus astar. Inntinneach, nam bheachd-sa, tha ceanglaichean air an toirt seachad gu h-ìosal.

Gus an ath thuras!

Pròiseactan inntinneach

Dè eile a bu chòir dhomh a leughadh?

Source: www.habr.com

Cuir beachd ann