Sifooyin buuxa oo qaawan-C I/O reactor

Sifooyin buuxa oo qaawan-C I/O reactor

Horudhac

I/O reactor (hal xadhig ah dhacdo loop) waa qaab qorista software-culus, oo loo isticmaalo xalal badan oo caan ah:

Maqaalkan, waxaan ku eegi doonaa gudaha iyo bannaanka I/O reactor iyo sida uu u shaqeeyo, ku qor hirgelinta wax ka yar 200 oo xariiq oo kood ah, waxaanan samayn doonaa nidaam server HTTP fudud oo ka badan 40 milyan codsi/daqiiqo.

Horudhac

  • Maqaalka waxaa loo qoray si uu u caawiyo fahamka shaqada reactor-ka I/O, oo markaa fahamto khataraha marka la isticmaalayo.
  • Fahamka aasaaska ayaa loo baahan yahay si loo fahmo maqaalka. C luqadda iyo xoogaa waayo-aragnimo ah ee horumarinta codsiga shabakadda.
  • Dhammaan summada waxay ku qoran yihiin luqadda C si adag si waafaqsan (taxaddar: PDF dheer) heerka C11 ee Linux oo laga heli karo GitHub.

Waa maxay sababta loo baahan yahay?

Iyada oo caannimada sii kordheysa ee internetka, server-yada shabakadu waxay bilaabeen inay u baahdaan inay xakameeyaan tiro badan oo iskuxiran isku mar, sidaas darteed laba hab ayaa la isku dayay: xannibaadda I / O tiro badan oo ah dunta OS iyo kuwa aan xannibin I / O oo lagu daray nidaamka ogeysiinta dhacdada, sidoo kale loo yaqaan "System selector" (epol/kuwee/IOCP/iwm).

Habka ugu horreeya ayaa ku lug lahaa abuurista dun OS cusub xiriir kasta oo soo gala. Khasaareheedu waa miisaan liidata: nidaamka qalliinka waa inuu hirgeliyaa qaar badan kala guurka macnaha guud и nidaamka wicitaanada. Waa hawlo qaali ah waxayna u horseedi karaan la'aanta RAM bilaashka ah oo leh tiro badan oo xiriir ah.

Nooca la bedelay ayaa iftiiminaya tirada go'an ee dunta (pool dunta), taas oo ka hortagaysa nidaamka ka soo rididda fulinta, laakiin isla mar ahaantaana soo bandhigid dhibaato cusub: haddii barkadda dunta hadda la xannibay by hawlgallada dheer akhrin, ka dibna saldhigyada kale ee horeba u awoodaan in ay helaan xogta ma awoodi doonaan in ay samee sidaas.

Habka labaad ayaa loo isticmaalaa nidaamka ogeysiinta dhacdada (System selector) oo uu bixiyo OS-gu. Maqaalkani waxa uu ka hadlayaa nooca ugu caansan ee xulashada nidaamka, oo ku salaysan digniinaha (dhacdooyinka, ogeysiisyada) ee ku saabsan u diyaargarowga hawlgallada I/O, halkii laga isticmaali lahaa ogeysiisyada ku saabsan dhammaystirkooda. Tusaalaha la fududeeyay ee isticmaalkeeda waxa lagu matali karaa jaantuska block ee soo socda:

Sifooyin buuxa oo qaawan-C I/O reactor

Farqiga u dhexeeya hababkan waa sida soo socota:

  • Joojinta hawlgallada I/O laalid socodka isticmaalaha ilaailaa OS si sax ah u noqdo defragment soo galaya Xirmooyinka IP ilaa qulqulka byte (TCP, helitaanka xogta) ama ma jiri doonto meel ku filan oo laga heli karo kaydka qoraalka gudaha ee soo dirida xigta NIC (dirista xogta).
  • Xulashada nidaamka waqti ka dib ogeysiinaya barnaamijka in OS ah horay Xirmooyinka IP defragmented (TCP, xogta soo dhaweynta) ama meel ku filan kaydinta qoraalka gudaha horay la heli karo (dirista xogta).

Si loo soo koobo, kaydinta dunta OS ee I/O kasta waa lumin awoodda xisaabinta, sababtoo ah dhab ahaantii, duntu ma qabtaan shaqo faa'iido leh (markaa ereyga "software dhexda"). Xulashada nidaamka ayaa xalliya dhibaatadan, taas oo u oggolaanaysa barnaamijka isticmaalaha inuu isticmaalo ilaha CPU si aad uga dhaqaale badan.

Qaabka reactor I/O

Reactor-ka I/O wuxuu u shaqeeyaa sida lakabka u dhexeeya xulashada nidaamka iyo koodka isticmaalaha. Mabda'a hawlgalkeeda waxaa lagu sifeeyay jaantuska block ee soo socda:

Sifooyin buuxa oo qaawan-C I/O reactor

  • Aan ku xasuusiyo in dhacdo ay tahay ogeysiis ah in godad gaar ah uu awoodo inuu fuliyo hawlgal I/O aan xannibin.
  • Maamule dhacdo waa hawl uu magacaabo I/O reactor marka ay dhacdo dhacdo la helo, ka dibna fuliya hawlgal I/O ah oo aan xannibayn.

Waxaa muhiim ah in la ogaado in reactor-ka I/O uu yahay qeexitaan hal-threaded, laakiin ma jiraan wax ka joojinaya fikradda in lagu isticmaalo jawiga dunta badan leh marka loo eego 1 thread: 1 reactor, taas oo dib u warshadeynaysa dhammaan xirmooyinka CPU.

Реализация

Interface-ka dadweynaha waxaanu gelin doonaa fayl reactor.h, iyo hirgelinta - in reactor.c. reactor.h waxay ka koobnaan doontaa ogeysiisyada soo socda:

Muuji cadeynta 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);

Qaab dhismeedka reactor I/O wuxuu ka kooban yahay sharaxe faylka dooriye epol и miisaska xashiishka GHashTable, Kaas oo khariidad kasta ku dhejinaya CallbackData (qaabka maamulaha dhacdada iyo doodaha isticmaalaha).

Muuji Reactor iyo CallbackData

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

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

Fadlan ogow in aanu awoodnay in aanu wax ka qabano nooc aan dhamaystirnayn sida ku cad tusmada. IN reactor.h waxaan cadeyneynaa qaabka reactor, halka reactor.c waanu qeexaynaa, si aanu uga hortagno isticmaaluhu inuu si cad u bedelo beerihiisa. Tani waa mid ka mid ah qaababka qarinaya xogta, kaas oo si kooban ugu habboon semantics C.

Functions reactor_register, reactor_deregister и reactor_reregister Cusbooneysii liiska godadka xiisaha iyo maamulayaasha dhacdooyinka u dhigma ee nidaamka xulashada iyo miiska xashiishka.

Muuji hawlaha diiwaangelinta

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

Ka dib markii reactor-ka I/O uu dhexda u galo dhacdada sharraxaha fd, waxay wacdaa maamulaha dhacdada u dhigma, kaas oo ay u gudubto fd, xoogaa maaskaro ah dhacdooyinka curiyay iyo tilmaame isticmaale si void.

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

Si loo soo koobo, silsiladda wicitaanada shaqada ee koodka isticmaaluhu wuxuu qaadan doonaa qaabkan soo socda:

Sifooyin buuxa oo qaawan-C I/O reactor

Seerfar xadhig ah oo keli ah

Si loo tijaabiyo reactor-ka I/O ee culayskiisu sarreeyo, waxaanu qori doonaa server-ka HTTP fudud oo ka jawaabaya codsi kasta oo sawir leh.

Tixraaca degdega ah ee borotokoolka HTTP

HTTP - kani waa borotokoolka heerka codsiga, ugu horrayn loo isticmaalo isdhexgalka server-browser.

HTTP si fudud ayaa loo isticmaali karaa gaadiidka borotokoolka TCP, dirida iyo helida fariimaha qaab cayiman qeexid.

Qaabka Codsiga

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

  • CRLF waa isku xigxiga laba xaraf: r и n, kala saaraya safka koowaad ee codsiga, madaxyada iyo xogta.
  • <КОМАНДА> - mid ka mid ah CONNECT, DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT, TRACE. Barrawsarku waxa uu amar u soo diri doona server-kayaga GET, oo macnaheedu yahay "Ii soo dir waxa ku jira faylka."
  • <URI> - aqoonsiga kheyraadka lebbiska. Tusaale ahaan, haddii URI = /index.html, ka dibna macmiilku wuxuu codsadaa bogga ugu weyn ee goobta.
  • <ВЕРСИЯ HTTP> - nooca borotokoolka HTTP ee qaabka HTTP/X.Y. Nooca ugu badan ee la isticmaalo maanta waa HTTP/1.1.
  • <ЗАГОЛОВОК N> waa lamaane-qiimo muhiim ah qaabka <КЛЮЧ>: <ЗНАЧЕНИЕ>, loo soo diray server-ka si loo baaro.
  • <ДАННЫЕ> - xogta uu u baahan yahay server-ku si uu u fuliyo hawlgalka. Badanaa way fududahay JSON ama qaab kasta oo kale.

Qaabka Jawaabta

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

  • <КОД СТАТУСА> waa lambar ka dhigan natiijada qalliinka. Seerfarkayagu wuxuu had iyo jeer soo celin doonaa heerka 200 (hawlgal guul leh).
  • <ОПИСАНИЕ СТАТУСА> - string matalaad ee code xaaladda. Xaaladda code 200 kani waa OK.
  • <ЗАГОЛОВОК N> - madaxa qaab la mid ah sida codsiga. Waxaan soo celin doonaa cinwaanada Content-Length (xajmiga faylka) iyo Content-Type: text/html (nooca xogta soo celinta).
  • <ДАННЫЕ> - xogta uu codsaday isticmaaluhu. Xaaladeena, tani waa jidka sawirka gudaha HTML.

file http_server.c (Serfar-xarun leh) waxa ku jira faylka common.h, kaas oo ka kooban prototypes shaqada ee soo socota:

Tus noocyada shaqada ee caadiga ah.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);

Macro-shaqeedka ayaa sidoo kale lagu tilmaamay SAFE_CALL() shaqadana waa la qeexay fail(). Makrosku wuxuu isbarbar dhigayaa qiimaha odhaahda iyo khaladka, haddii xaaladdu run tahay, wac shaqada fail():

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

function fail() u daabacaa doodaha la gudbiyay terminaalka (sida printf()) oo ku joojiya barnaamijka koodka 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);
}

function new_server() soo celiyaa sharraxa faylka "server" godka ay abuureen wicitaanada nidaamka socket(), bind() и listen() oo awood u leh inuu aqbalo isku-xiryada soo galaya qaab aan xannibin.

Muuji shaqada cusub_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;
}

  • Ogsoonow in godka markii hore lagu sameeyay qaab aan xannibin iyadoo la adeegsanayo calanka SOCK_NONBLOCKsi in shaqada on_accept() (wax dheeraad ah ka sii akhri) wicitaanka nidaamka accept() ma joojin fulinta dunta.
  • haddii reuse_port waa loo siman yahay true, ka dibna shaqadani waxay ku habeyn doontaa godka ikhtiyaarka SO_REUSEPORT iyada oo loo marayo setsockopt()Si aad u isticmaasho deked isku mid ah jawiga dunta badan leh (eeg qaybta "Serfar-threaded badan").

Maamulaha Dhacdada on_accept() loo yaqaan ka dib markii OS soo saaro dhacdo EPOLLIN, xaaladdan oo kale macnaheedu waa in xidhiidhka cusub la aqbali karo. on_accept() aqbala xidhiidh cusub, waxa uu u beddelaa hab aan xannibin oo waxa uu iska diwaangeliyaa maamule dhacdo on_recv() I/O reactor.

Muuji on_aqbal () shaqada

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

Maamulaha Dhacdada on_recv() loo yaqaan ka dib markii OS soo saaro dhacdo EPOLLIN, xaaladdan oo kale waxay la macno tahay in xiriirku diiwaangashan yahay on_accept(), diyaar u ah helitaanka xogta.

on_recv() wuxuu akhriyaa xogta xiriirka ilaa codsiga HTTP si buuxda loo helo, ka dibna waxay diiwaangelisaa maamule on_send() in loo diro jawaab HTTP ah. Haddii macmiilku jebiyo xidhiidhka, godka waa laga saaray diiwaanka oo la xidhay iyadoo la isticmaalayo close().

Ku muuji shaqada_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);
    }
}

Maamulaha Dhacdada on_send() loo yaqaan ka dib markii OS soo saaro dhacdo EPOLLOUT, taasoo la micno ah in xiriirku diiwaan gashan yahay on_recv(), diyaar u ah inuu diro xogta. Shaqadani waxay u dirtaa jawaab celin HTTP ka kooban HTML oo sawir leh macmiilka ka dibna u beddela maamulaha dhacdada on_recv().

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

Ugu dambeyntiina, faylka http_server.c, shaqada main() Waxaan abuurnaa I/O reactor anagoo adeegsanayna reactor_new()Samee godad server ah oo diwaangeli, ku bilow reactor adiga oo isticmaalaya reactor_run() hal daqiiqo oo sax ah, ka dibna waanu sii dayn khayraadka oo ka baxnaa barnaamijka.

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

Aynu eegno in wax walba ay u shaqeeyaan sidii la filayay. Ururintachmod a+x compile.sh && ./compile.sh ee xididka mashruuca) oo bilow server-ka is-qoray, furan http://127.0.0.1:18470 browserka oo arag waxa aanu filaynay:

Sifooyin buuxa oo qaawan-C I/O reactor

Cabbirka waxqabadka

Tus tilmaamaha gaarigayga

$ 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

Aynu cabbirno waxqabadka server-ka-xadhkaha leh. Aynu furno laba terminal: mid waanu ku ordi doonaa ./http_server, si ka duwan - wrk. Hal daqiiqo ka dib, tirakoobyada soo socda ayaa lagu soo bandhigi doonaa terminalka labaad:

$ 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

Seerfarkeena hal-xadhka leh ayaa awooday in uu farsameeyo in ka badan 11 milyan oo codsi daqiiqaddii oo ka yimid 100 xidhiidh. Natiijo xun maaha, laakiin ma la hagaajin karaa?

Seerfar badan oo fidsan

Sida kor ku xusan, reactor-ka I/O waxaa lagu abuuri karaa dunta kala duwan, iyada oo laga faa'iideysanayo dhammaan xudunta CPU. Aan dhaqan galno habkan:

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

Hadda dun kasta isagaa iska leh reactor:

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

Fadlan la soco in doodda shaqadu tahay new_server() u dooda true. Tani waxay ka dhigan tahay in aan ku meeleyno ikhtiyaarka godka serverka SO_REUSEPORTsi loogu isticmaalo jawi isku xiran oo badan. Waxaad akhrin kartaa faahfaahin dheeraad ah halkan.

Orodkii labaad

Haddaba aan cabbirno waxqabadka server-ka dunta badan leh:

$ 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

Tirada codsiyada lagu shaqeeyay 1 daqiiqo ayaa kordhay ~3.28 jeer! Laakiin waxaan ahayn kaliya ~ XNUMX milyan oo ka gaaban tirada wareega, markaa aan isku dayno inaan hagaajino taas.

Marka hore aan eegno tirakoobka la sameeyay kaamil ah:

$ 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

Isticmaalka Affinity CPU, oo lagu soo ururiyey -march=native, PGO, korodhka tirada hits kaydin, kordhiyo MAX_EVENTS iyo isticmaal EPOLLET ma aysan siinin koror la taaban karo ee waxqabadka. Laakiin maxaa dhacaya haddii aad kordhiso tirada isku xirka isku mar?

Tirakoobka 352 isku xidhka isku mar ah:

$ 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

Natiijadii la rabay ayaa la helay, oo ay la socoto garaaf xiiso leh oo muujinaya ku-tiirsanaanta tirada codsiyada la habeeyey 1 daqiiqo ee tirada isku xirka:

Sifooyin buuxa oo qaawan-C I/O reactor

Waxaan aragnaa in ka dib dhowr boqol oo xiriir ah, tirada codsiyada la farsameeyay ee labada server ay si aad ah hoos ugu dhacayaan (nooca multi-threaded kani waa mid la dareemi karo). Tani miyay la xiriirtaa Linux TCP/IP fulinta xirmada? Dareen xor inaad qorto fikradahaaga ku saabsan hab-dhaqankan garaafyada iyo hagaajinta isku-xidhka badan iyo xulashooyinka hal-stringed ee faallooyinka.

Sidee xusay Faallooyinka, imtixaanka waxqabadkani ma muujinayo habdhaqanka I/O reactor ee culeyska dhabta ah, sababtoo ah had iyo jeer serverku wuxuu la falgalaa kaydka xogta, soo saarida qoraallada, wuxuu isticmaalaa cryptography TLS iwm, taas oo ay sabab u tahay in culaysku noqdo mid aan labbis ahayn (firfircoon). Tijaabooyin ay weheliyaan qaybaha qolo saddexaad ayaa lagu samayn doonaa maqaalka ku saabsan proactor-ka I/O.

Khasaaraha I/O reactor

Waxaad u baahan tahay inaad fahanto in reactor-ka I/O aanu la'hayn cilladihiisa, kuwaas oo ah:

  • Isticmaalka reactor-ka I/O ee jawiga dunta badan leh ayaa xoogaa ka dhib badan, sababtoo ah waa inaad gacanta ku maareysaa qulqulka.
  • Dhaqanku wuxuu muujinayaa in kiisaska intooda badan culeysku aanu labis ahayn, taasoo keeni karta in hal dun la gooyo halka mid kalena uu ku mashquulsan yahay shaqo.
  • Haddii hal maamule dhacdo uu xannibo dunta, markaa nidaamka xulashada laftiisa ayaa sidoo kale xannibi doona, taas oo u horseedi karta dhiqlaha ay adagtahay in la helo.

Wuxuu xalliyaa dhibaatooyinkan I/O proactor, kaas oo inta badan leh jadwal si siman u qaybiya culeyska balliga dunta, sidoo kale wuxuu leeyahay API ku habboon. Dib ayaan uga hadli doonaa, maqaalkeyga kale.

gunaanad

Tani waa halka safarkayagii ka yimid aragtida tooska ah ee qiiqa profiler uu ku dhammaaday.

Waa inaadan ku mashquulin tan, sababtoo ah waxaa jira habab kale oo badan oo isku mid ah oo xiiso leh oo lagu qoro software shabakadeed oo leh heerar kala duwan oo ku habboon iyo xawaare. Xiiso leh, ra'yigeyga, xiriiriyeyaasha ayaa lagu bixiyaa hoos.

До novыh встреч!

Mashaariic xiiso leh

Maxaa kale oo la akhriyaa?

Source: www.habr.com

Add a comment