Isici esigcwele se-bare-C I/O reactor

Isici esigcwele se-bare-C I/O reactor

Isingeniso

I/O i-reactor (uchungechunge olulodwa iluphu yomcimbi) iphethini yokubhala isoftware enomthwalo omkhulu, esetshenziswa ezisombululweni eziningi ezidumile:

Kulesi sihloko, sizobheka ukungena nokuphuma kwe-reactor ye-I / O nokuthi isebenza kanjani, bhala ukuqaliswa emigqeni yekhodi engaphansi kwe-200, futhi wenze inqubo yeseva ye-HTTP elula ngaphezu kwezicelo eziyizigidi ezingu-40 / iminithi.

Isibikezelo

  • I-athikili ibhalelwe ukusiza ukuqonda ukusebenza kwe-reactor ye-I/O, futhi ngenxa yalokho ukuqonda ubungozi lapho uyisebenzisa.
  • Ukuqonda okuyisisekelo kuyadingeka ukuze uqonde isihloko. C ulimi kanye nolwazi oluthile ekuthuthukisweni kohlelo lwenethiwekhi.
  • Yonke ikhodi ibhalwe ngolimi C ngokuqinile ngokuvumelana (isixwayiso: i-PDF ende) kuya ku-C11 ejwayelekile ye-Linux futhi iyatholakala ku GitHub.

Kungani uyidinga?

Ngokuthandwa okukhulayo kwe-inthanethi, amaseva ewebhu aqala ukudinga ukuphatha inombolo enkulu yokuxhumana ngesikhathi esisodwa, ngakho-ke kwazanywa izindlela ezimbili: ukuvimba i-I/O enanini elikhulu lezintambo ze-OS kanye ne-I/O engavimbi kuhlanganiswe uhlelo lokwazisa ngomcimbi, olubizwa nangokuthi “isikhethi sohlelo” (epoll/umugqa/I-IOCP/ njll).

Indlela yokuqala yayihilela ukudala intambo ye-OS entsha kuxhumo ngalunye olungenayo. Ububi bayo wukungalinganisi kahle: uhlelo olusebenzayo kuzodingeka lusebenzise eziningi izinguquko zomongo и izingcingo zesistimu. Kuyimisebenzi ebizayo futhi kungaholela ekuntulekeni kwe-RAM yamahhala enenombolo ekhangayo yokuxhumana.

Inguqulo eguquliwe igqamisa inombolo engashintshi yemicu (i-thread pool), ngaleyo ndlela ivimbele uhlelo ekuphahlazekeni, kodwa ngesikhathi esifanayo sethula inkinga entsha: uma i-pool pool okwamanje ivinjelwe ukusebenza kokufunda isikhathi eside, khona-ke amanye amasokhethi asevele ekwazi ukuthola idatha ngeke akwazi ukwenza. ngakho.

Indlela yesibili isetshenziswa uhlelo lwesaziso somcimbi (isikhethi sesistimu) sinikezwe i-OS. Lesi sihloko sidingida uhlobo oluvame kakhulu lwesikhethi sesistimu, olususelwe kuzixwayiso (imicimbi, izaziso) mayelana nokulungela ukusebenza kwe-I/O, kunokuba izaziso mayelana nokuqedwa kwazo. Isibonelo esenziwe lula sokusetshenziswa kwayo singamelwa umdwebo webhulokhi olandelayo:

Isici esigcwele se-bare-C I/O reactor

Umehluko phakathi kwalezi zindlela umi kanje:

  • Ivimba imisebenzi ye-I/O misa okwesikhashana ukugeleza komsebenzisi kuzekuze kube yilapho i-OS isilungile ama-defragments engenayo IP amaphakethe ukusakaza bukhoma (I-TCP, ukwamukela idatha) noma ngeke sibe khona isikhala esanele esitholakala kumabhafa wokubhala wangaphakathi ukuze sithumele ngokulandelayo nge I-NIC (ukuthumela idatha).
  • Isikhethi sesistimu ngokuhamba kwesikhathi yazisa uhlelo ukuthi i-OS vele amaphakethe e-IP ahlukanisiwe (i-TCP, ukwamukela idatha) noma isikhala esanele kumabhafa wokubhala angaphakathi vele etholakalayo (ukuthumela idatha).

Ukukufingqa, ukugcina intambo ye-OS ye-I/O ngayinye kuwukumosha amandla ekhompiyutha, ngoba empeleni, imicu ayenzi umsebenzi owusizo (ngakho-ke igama elithi "ukuphazamiseka kwesoftware"). Isikhethi sesistimu sixazulula le nkinga, sivumela uhlelo lomsebenzisi ukuthi lusebenzise izinsiza ze-CPU ngokonga kakhulu.

Imodeli ye-reactor ye-I/O

I-reactor ye-I/O isebenza njengesendlalelo phakathi kwesikhethi sesistimu nekhodi yomsebenzisi. Umgomo wokusebenza kwawo uchazwa yi-block diagram elandelayo:

Isici esigcwele se-bare-C I/O reactor

  • Ake ngikukhumbuze ukuthi umcimbi uyisaziso sokuthi isokhethi elithile liyakwazi ukwenza umsebenzi we-I/O ongavimbi.
  • Isiphathi somcimbi wumsebenzi obizwa i-reactor ye-I/O lapho umcimbi wamukelwe, ebese senza umsebenzi we-I/O ongavimbi.

Kubalulekile ukuqaphela ukuthi i-reactor ye-I/O ngencazelo iwuchungechunge olulodwa, kodwa akukho lutho oluvimba umqondo ukuthi usetshenziswe endaweni enemicu eminingi ngesilinganiso somucu ongu-1: ireactor engu-1, ngaleyo ndlela kugaywe kabusha wonke ama-CPU cores.

Ukuqaliswa

Sizobeka isixhumi esibonakalayo kufayela reactor.h, kanye nokuqaliswa - ku reactor.c. reactor.h izoqukatha izimemezelo ezilandelayo:

Bonisa izimemezelo ku-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);

I/O isakhiwo se-reactor siqukethe isichazi sefayela isikhethi epoll и amatafula e-hashi GHashTable, okubonisa isokhethi ngalinye CallbackData (isakhiwo somphathi womcimbi kanye nokuphikisana komsebenzisi kwaso).

Bonisa i-Reactor kanye ne-CallbackData

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

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

Sicela uqaphele ukuthi senze ikhono lokuphatha uhlobo olungaphelele ngokwenkomba. IN reactor.h simemezela isakhiwo reactor, futhi ngaphakathi reactor.c siyakuchaza, ngaleyo ndlela sivimbela umsebenzisi ekuguquleni izinkambu zakhe ngokusobala. Leli elinye lamaphethini ukufihla idatha, elingena kafushane ku-C semantics.

Imisebenzi reactor_register, reactor_deregister и reactor_reregister buyekeza uhlu lwamasokhethi wentshisekelo kanye nezibambi zomcimbi ohambelanayo kusikhethi sesistimu nethebula le-hashi.

Bonisa imisebenzi yokubhalisa

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

Ngemuva kokuthi i-reactor ye-I/O ibambe umcimbi ngesichazi fd, ibiza umphathi womcimbi ohambisanayo, lapho idlula khona fd, imaski encane izenzakalo ezikhiqiziwe kanye nesikhombi somsebenzisi void.

Bonisa umsebenzi we-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;
}

Ukufingqa, uchungechunge lwamakholi ekhodi yomsebenzisi luzothatha ifomu elilandelayo:

Isici esigcwele se-bare-C I/O reactor

Iseva yochungechunge olulodwa

Ukuze sihlole i-reactor ye-I/O ngaphansi komthwalo omkhulu, sizobhala iseva yewebhu ye-HTTP elula ephendula noma yisiphi isicelo ngesithombe.

Ireferensi esheshayo kuphrothokholi ye-HTTP

HTTP - lena iphrothokholi izinga lesicelo, esetshenziselwa ikakhulukazi ukusebenzisana kwesiphequluli seseva.

I-HTTP ingasetshenziswa kalula ngaphezulu ezokuthutha umthetho olandelwayo I-TCP, ukuthumela nokwamukela imiyalezo ngefomethi eshiwo ukucaciswa.

Ifomethi yesicelo

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

  • CRLF kuwukulandelana kwezinhlamvu ezimbili: r и n, ehlukanisa umugqa wokuqala wesicelo, izihloko nedatha.
  • <КОМАНДА> - okukodwa kwe CONNECT, DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT, TRACE. Isiphequluli sizothumela umyalo kuseva yethu GET, okusho ukuthi "Ngithumele okuqukethwe kwefayela."
  • <URI> - isihlonzi sensiza efanayo. Isibonelo, uma i-URI = /index.html, bese iklayenti licela ikhasi eliyinhloko lesayithi.
  • <ВЕРСИЯ HTTP> — inguqulo yephrothokholi ye-HTTP ngefomethi HTTP/X.Y. Inguqulo esetshenziswa kakhulu namuhla HTTP/1.1.
  • <ЗАГОЛОВОК N> ingumbhangqwana yenani elingukhiye ngefomethi <КЛЮЧ>: <ЗНАЧЕНИЕ>, ithunyelwe kuseva ukuze ihlaziywe okwengeziwe.
  • <ДАННЫЕ> — idatha edingwa yiseva ukwenza umsebenzi. Ngokuvamile kulula I-JSON nanoma iyiphi enye ifomethi.

Ifomethi Yempendulo

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

  • <КОД СТАТУСА> inombolo emele umphumela womsebenzi. Iseva yethu izohlala ibuyisela isimo esingu-200 (ukusebenza ngempumelelo).
  • <ОПИСАНИЕ СТАТУСА> — ukumelwa kochungechunge lwekhodi yesimo. Ngekhodi yesimo 200 lokhu OK.
  • <ЗАГОЛОВОК N> — unhlokweni wefomethi efanayo naleyo esesicelweni. Sizobuyisela izihloko Content-Length (usayizi wefayela) kanye Content-Type: text/html (buyisela uhlobo lwedatha).
  • <ДАННЫЕ> - idatha ecelwe umsebenzisi. Esimweni sethu, lena yindlela eya esithombeni i-HTML.

Файл http_server.c (iseva enentambo eyodwa) ihlanganisa ifayela common.h, equkethe ama-prototypes omsebenzi alandelayo:

Bonisa ama-prototypes okusebenza okufanayo.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);

I-macro esebenzayo nayo iyachazwa SAFE_CALL() futhi umsebenzi uchazwa fail(). I-macro iqhathanisa inani lenkulumo nephutha, futhi uma isimo siyiqiniso, ibiza umsebenzi fail():

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

Umsebenzi fail() iphrinta ama-agumenti adlulisiwe kutheminali (njenge printf()) futhi inqamula uhlelo ngekhodi 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);
}

Umsebenzi new_server() ibuyisela incazelo yefayela yesokhethi "yeseva" edalwe amakholi wesistimu socket(), bind() и listen() futhi ekwazi ukwamukela ukuxhumana okungenayo ngemodi engavimbi.

Bonisa umsebenzi omusha_weseva ().

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

  • Qaphela ukuthi isokhethi ekuqaleni idalwe kumodi engavimbeli kusetshenziswa ifulegi SOCK_NONBLOCKukuze emsebenzini on_accept() (funda kabanzi) ikholi yesistimu accept() ayizange imise ukukhishwa kochungechunge.
  • Uma reuse_port ilingana ne true, bese lo msebenzi uzomisa isokhethi ngenketho SO_REUSEPORT ngokusebenzisa setsockopt()ukusebenzisa imbobo efanayo endaweni enemicu eminingi (bona isigaba “Iseva enemicu eminingi”).

Isibambi somcimbi on_accept() ebizwa ngemuva kokuthi i-OS ikhiqize umcimbi EPOLLIN, kulokhu okusho ukuthi ukuxhumana okusha kungamukelwa. on_accept() yamukela uxhumo olusha, ilishintshele kumodi engavimbi futhi irejista nesibambi somcimbi on_recv() ku-reactor ye-I/O.

Bonisa on_accept() umsebenzi

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

Isibambi somcimbi on_recv() ebizwa ngemuva kokuthi i-OS ikhiqize umcimbi EPOLLIN, kulokhu okusho ukuthi uxhumano lubhalisiwe on_accept(), ilungele ukwamukela idatha.

on_recv() ifunda idatha ekuxhumaneni kuze kube yilapho isicelo se-HTTP samukelwe ngokuphelele, bese ibhalisa isibambi on_send() ukuthumela impendulo ye-HTTP. Uma iklayenti linqamula uxhumano, isokhethi iyasuswa futhi ivalwe kusetshenziswa close().

Bonisa umsebenzi ku_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);
    }
}

Isibambi somcimbi on_send() ebizwa ngemuva kokuthi i-OS ikhiqize umcimbi EPOLLOUT, okusho ukuthi uxhumano lubhalisiwe on_recv(), ilungele ukuthumela idatha. Lo msebenzi uthumela impendulo ye-HTTP equkethe i-HTML enesithombe kuklayenti bese ushintsha isibambi somcimbi sibuyele kuso on_recv().

Bonisa on_send() umsebenzi

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

Futhi ekugcineni, kufayela http_server.c, emsebenzini main() sakha i-reactor ye-I/O sisebenzisa reactor_new(), dala isokhethi leseva bese uyibhalisa, qala i-reactor usebenzisa reactor_run() umzuzu owodwa ncamashi, bese sikhipha izinsiza bese siphuma ohlelweni.

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

Ake sihlole ukuthi yonke into isebenza njengoba bekulindelekile. Ukuhlanganisa (chmod a+x compile.sh && ./compile.sh empandeni yephrojekthi) bese wethula iseva ezibhalele yona, vula http://127.0.0.1:18470 esipheqululini futhi sibone ukuthi yini ebesiyilindele:

Isici esigcwele se-bare-C I/O reactor

Isilinganiso sokusebenza

Bonisa imininingwane yemoto yami

$ 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

Ake silinganise ukusebenza kweseva enomucu owodwa. Masivule amatheminali amabili: kwesinye sizogijima ./http_server, ngenye indlela - impikiswano. Ngemva komzuzu, izibalo ezilandelayo zizoboniswa kutheminali yesibili:

$ 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

Iseva yethu enomucu owodwa ikwazile ukucubungula izicelo ezingaphezu kwesigidi esingu-11 ngeminithi ezivela ekuxhumekeni okungu-100. Akuwona umphumela omubi, kodwa ingabe ungathuthukiswa?

Iseva enemicu eminingi

Njengoba kushiwo ngenhla, i-reactor ye-I/O ingadalwa ngemicu ehlukene, ngaleyo ndlela kusetshenziswa wonke ama-CPU cores. Masisebenzise le ndlela yokusebenza:

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

Manje yonke intambo okungokwakhe i-reactor:

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

Sicela uqaphele ukuthi i-agumenti yomsebenzi new_server() abameli true. Lokhu kusho ukuthi sinikeza inketho esokhethi yeseva SO_REUSEPORTukuyisebenzisa endaweni enemicu eminingi. Ungafunda imininingwane eyengeziwe lapha.

Ukugijima kwesibili

Manje ake silinganise ukusebenza kweseva enezintambo eziningi:

$ 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

Inani lezicelo ezicutshungulwe eminithini elingu-1 likhuphuke izikhathi ezingu-~3.28! Kodwa besishoda ngezigidi ezingu-XNUMX kuphela kunombolo eyindilinga, ngakho-ke ake sizame ukukulungisa lokho.

Okokuqala ake sibheke izibalo ezikhiqizwayo i-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

Ukusebenzisa i-CPU Affinity, ukuhlanganisa ne -march=native, I-PGO, ukwanda kwenani lamahithi inqolobane, ukwanda MAX_EVENTS nokusebenzisa EPOLLET ayizange inikeze ukwanda okuphawulekayo ekusebenzeni. Kodwa kwenzekani uma wandisa inombolo yokuxhumana ngasikhathi sinye?

Izibalo ze-352 yokuxhumana ngesikhathi esisodwa:

$ 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

Umphumela obukade utholakele utholakele, futhi kanye nayo igrafu ethokozisayo ekhombisa ukuncika kwenani lezicelo ezicutshunguliwe ngeminithi elingu-1 enanini loxhumo:

Isici esigcwele se-bare-C I/O reactor

Siyabona ukuthi ngemva kokuxhumana okungamakhulu ambalwa, inani lezicelo ezicutshunguliwe zazo zombili iziphakeli lehla kakhulu (enguqulweni enezintambo eziningi lokhu kubonakala kakhulu). Ingabe lokhu kuhlobene nokuqaliswa kwesitaki se-Linux TCP/IP? Zizwe ukhululekile ukubhala ukucabangela kwakho mayelana nalokhu kuziphatha kwegrafu nokulungiselelwa kwezinketho ezinentambo eminingi nezinochungechunge olulodwa kumazwana.

Indlela kuphawuliwe kumazwana, lokhu kuhlolwa kokusebenza akubonisi ukuziphatha kwe-reactor ye-I/O ngaphansi kwemithwalo yangempela, ngoba cishe njalo iseva isebenzisana nedathabhesi, ikhipha izingodo, isebenzisa i-cryptography nge. TLS njll., ngenxa yalokho umthwalo uba ongafani (oguqukayo). Ukuhlolwa kanye nezingxenye zenkampani yangaphandle kuzokwenziwa esihlokweni esimayelana ne-I/O proactor.

Ububi be-reactor ye-I/O

Udinga ukuqonda ukuthi i-reactor ye-I/O ayinazo izithiyo zayo, okuyilezi:

  • Ukusebenzisa i-reactor ye-I/O endaweni enezintambo eziningi kunzima kakhulu, ngoba kuzodingeka ukuthi ulawule ngokuzenzela ukugeleza.
  • Ukuzijwayeza kubonisa ukuthi ezimweni eziningi umthwalo awufani, okungaholela ekugawulweni kwentambo eyodwa kuyilapho omunye ematasa nomsebenzi.
  • Uma isibambi somcimbi esisodwa sivimba uchungechunge, isikhethi sesistimu ngokwaso sizophinde sivimbe, okungaholela ekutholeni iziphazamisi okunzima ukuzithola.

Ixazulula lezi zinkinga Umlingisi we-I/O, evame ukuba nesihleli esisabalalisa umthwalo ngokulinganayo eqoqweni lezintambo, futhi sibe ne-API elula kakhulu. Sizokhuluma ngakho kamuva, kwesinye isihloko sami.

isiphetho

Yilapho uhambo lwethu olusuka kuthiyori luqonde ngqo ku-profiler exhaust lufike esiphethweni.

Akufanele uhlale kulokhu, ngoba ziningi ezinye izindlela ezithakazelisayo ngokulinganayo zokubhala isofthiwe yenethiwekhi enamazinga ahlukene okulula nesivinini. Kuyathakazelisa, ngombono wami, izixhumanisi zinikezwa ngezansi.

Ngikubone futhi!

Amaphrojekthi anentshisekelo

Yini enye okufanele ngiyifunde?

Source: www.habr.com

Engeza amazwana