Reaktor I/O Bare-C kanthi fitur lengkap

Reaktor I/O Bare-C kanthi fitur lengkap

Pambuka

reaktor I/O (ulir tunggal loop acara) minangka pola kanggo nulis piranti lunak kanthi beban dhuwur, digunakake ing pirang-pirang solusi populer:

Ing artikel iki, kita bakal ndeleng seluk beluk reaktor I / O lan cara kerjane, nulis implementasine kurang saka 200 baris kode, lan nggawe proses server HTTP sing prasaja liwat 40 yuta panjalukan / min.

Pambuka

  • Artikel kasebut ditulis kanggo mbantu ngerti fungsi reaktor I/O, lan mulane ngerti risiko nalika nggunakake.
  • Kawruh dhasar dibutuhake kanggo mangerteni artikel kasebut. basa C lan sawetara pengalaman ing pangembangan aplikasi jaringan.
  • Kabeh kode ditulis ing basa C kanthi ketat miturut (ati-ati: PDF dawa) kanggo standar C11 kanggo Linux lan kasedhiya ing GitHub.

Apa perlu iki?

Kanthi popularitas Internet sing saya tambah akeh, server web wiwit kudu nangani akeh sambungan bebarengan, lan mulane rong pendekatan dicoba: mblokir I / O ing pirang-pirang benang OS lan I / O non-blocking ing kombinasi karo sistem kabar acara, uga disebut "pemilih sistem" (epoll/kqueue/IOCP/ lsp).

Pendekatan pisanan melu nggawe thread OS anyar kanggo saben sambungan mlebu. Kerugian kasebut yaiku skalabilitas sing kurang apik: sistem operasi kudu ngetrapake akeh transisi konteks и telpon sistem. Padha operasi larang lan bisa mimpin kanggo lack of free RAM karo nomer nyengsemaken sambungan.

Sorotan versi sing diowahi nomer tetep saka thread (blumbang thread), saéngga nyegah sistem supaya ora ngeksekusi, nanging ing wektu sing padha ngenalake masalah anyar: yen blumbang thread saiki diblokir dening operasi maca sing dawa, mula soket liyane sing wis bisa nampa data ora bakal bisa. nglakoni.

Pendekatan kapindho nggunakake sistem notifikasi acara (pemilih sistem) sing diwenehake dening OS. Artikel iki mbahas jinis pamilih sistem sing paling umum, adhedhasar tandha (acara, kabar) babagan kesiapan kanggo operasi I/O, tinimbang ing kabar babagan rampung. Conto panggunaan sing disederhanakake bisa diwakili dening diagram blok ing ngisor iki:

Reaktor I/O Bare-C kanthi fitur lengkap

Bedane antarane pendekatan kasebut yaiku:

  • Bloking operasi I/O nundha aliran pangguna ngantinganti OS bener defragments mlebu paket IP kanggo stream byte (TCP, nampa data) utawa ora bakal ana cukup spasi ing buffer nulis internal kanggo dikirim liwat NIC (ngirim data).
  • Pemilih sistem liwat wektu ngabari program sing OS wis paket IP defragmented (TCP, reception data) utawa papan sing cukup ing buffer nulis internal wis kasedhiya (ngirim data).

Kanggo nyimpulake, ngreksa thread OS kanggo saben I / O minangka sampah daya komputasi, amarga kasunyatane, benang ora nindakake pakaryan sing migunani (mula istilah kasebut "interrupt software"). Pamilih sistem ngatasi masalah iki, ngidini program pangguna nggunakake sumber daya CPU luwih ekonomis.

model reaktor I/O

Reaktor I / O tumindak minangka lapisan antarane pamilih sistem lan kode pangguna. Prinsip operasi kasebut diterangake kanthi diagram blok ing ngisor iki:

Reaktor I/O Bare-C kanthi fitur lengkap

  • Ayo kula ngelingake yen acara minangka kabar yen soket tartamtu bisa nindakake operasi I / O sing ora ngalangi.
  • Handler acara minangka fungsi sing diarani reaktor I / O nalika acara ditampa, sing banjur nindakake operasi I / O sing ora ngalangi.

Wigati dimangerteni yen reaktor I / O kanthi definisi single-threaded, nanging ora ana sing bisa nyegah konsep kasebut digunakake ing lingkungan multi-threaded kanthi rasio 1 thread: 1 reaktor, saéngga daur ulang kabeh inti CPU.

Реализация

Kita bakal nyelehake antarmuka umum ing file reactor.h, lan implementasine - ing reactor.c. reactor.h bakal kalebu pengumuman ing ngisor iki:

Tampilake deklarasi ing reaktor.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);

Struktur reaktor I/O kasusun saka deskriptor file pamilih epoll и tabel hash GHashTable, kang map saben soket kanggo CallbackData (struktur handler acara lan argumentasi pangguna).

Tampilake Reaktor lan CallbackData

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

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

Elinga yen kita wis ngaktifake kemampuan kanggo nangani jinis ora pepak miturut indeks. ING reactor.h kita ngumumake struktur kasebut reactor, lan ing reactor.c kita nemtokake, saéngga nyegah pangguna supaya ora ngganti lapangan kanthi jelas. Iki minangka salah sawijining pola ndhelikake data, sing pas karo semantik C.

Fungsi reactor_register, reactor_deregister и reactor_reregister nganyari dhaptar soket kapentingan lan pawang acara sing cocog ing pamilih sistem lan tabel hash.

Tampilake fungsi registrasi

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

Sawise reaktor I / O nyegat acara kasebut kanthi deskriptor fd, nelpon pawang acara sing cocog, sing dilewati fd, topeng bit acara kui lan pitunjuk pangguna kanggo void.

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

Kanggo ngringkes, rantai panggilan fungsi ing kode pangguna bakal njupuk formulir ing ngisor iki:

Reaktor I/O Bare-C kanthi fitur lengkap

Server utas tunggal

Kanggo nguji reaktor I / O ing beban dhuwur, kita bakal nulis server web HTTP sing prasaja sing nanggapi panjaluk apa wae kanthi gambar.

Referensi cepet kanggo protokol HTTP

HTTP - iki protokol tingkat aplikasi, utamané digunakake kanggo interaksi server-browser.

HTTP bisa gampang digunakake liwat Tumpaan protokol TCP, ngirim lan nampa pesen ing format sing ditemtokake spesifikasi.

Format Panjaluk

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

  • CRLF yaiku urutan saka rong karakter: r и n, misahake baris pisanan panjalukan, header lan data.
  • <КОМАНДА> - salah siji saka CONNECT, DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT, TRACE. Browser bakal ngirim printah menyang server kita GET, tegesé "Kirimi aku isi berkas."
  • <URI> - pengenal sumber daya seragam. Contone, yen URI = /index.html, banjur klien njaluk kaca utama situs kasebut.
  • <ВЕРСИЯ HTTP> — versi protokol HTTP ing format HTTP/X.Y. Versi sing paling umum digunakake saiki yaiku HTTP/1.1.
  • <ЗАГОЛОВОК N> minangka pasangan kunci-nilai ing format <КЛЮЧ>: <ЗНАЧЕНИЕ>, dikirim menyang server kanggo analisis luwih.
  • <ДАННЫЕ> - data sing dibutuhake dening server kanggo nindakake operasi. Asring iku prasaja JSON utawa format liyane.

Format Tanggapan

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

  • <КОД СТАТУСА> minangka nomer sing nuduhake asil operasi. Server kita mesthi bakal ngasilake status 200 (operasi sing sukses).
  • <ОПИСАНИЕ СТАТУСА> - perwakilan string saka kode status. Kanggo kode status 200 iki OK.
  • <ЗАГОЛОВОК N> - header saka format sing padha ing request. Kita bakal bali judhul Content-Length (ukuran file) lan Content-Type: text/html (jinis data bali).
  • <ДАННЫЕ> - data sing dijaluk pangguna. Ing kasus kita, iki minangka dalan menyang gambar ing HTML.

berkas http_server.c (single threaded server) kalebu file common.h, sing ngemot prototipe fungsi ing ngisor iki:

Tampilake prototipe fungsi ing umum.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);

Makro fungsional uga diterangake SAFE_CALL() lan fungsi ditetepake fail(). Makro mbandhingake nilai ekspresi karo kesalahan, lan yen kondisi kasebut bener, nelpon fungsi kasebut fail():

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

fungsi fail() nyithak argumen sing dilewati menyang terminal (kayata printf()) lan mungkasi program kanthi kode 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);
}

fungsi new_server() ngasilake deskriptor file saka soket "server" sing digawe dening panggilan sistem socket(), bind() и listen() lan bisa nampa sambungan mlebu ing mode non-blocking.

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

  • Elinga yen soket wiwitane digawe ing mode non-blocking nggunakake gendera SOCK_NONBLOCKsupaya ing fungsi on_accept() (waca liyane) telpon sistem accept() ora mungkasi eksekusi thread.
  • yen reuse_port iku padha true, banjur fungsi iki bakal ngatur soket karo pilihan SO_REUSEPORT ngliwati setsockopt()kanggo nggunakake port padha ing lingkungan multi-Utas (ndeleng bagean "Server Multi-Utas").

Penanganan Acara on_accept() disebut sawise OS ngasilake acara EPOLLIN, ing kasus iki tegese sambungan anyar bisa ditampa. on_accept() nampa sambungan anyar, ngalih menyang mode non-blocking lan ndhaftar karo handler acara on_recv() ing reaktor I/O.

Tampilake on_accept() fungsi

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

Penanganan Acara on_recv() disebut sawise OS ngasilake acara EPOLLIN, ing kasus iki tegese sambungan kadhaptar on_accept(), siap nampa data.

on_recv() maca data saka sambungan nganti request HTTP rampung ditampa, banjur ndhaptar handler on_send() kanggo ngirim respon HTTP. Yen klien break sambungan, soket deregistered lan ditutup nggunakake close().

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

Penanganan Acara on_send() disebut sawise OS ngasilake acara EPOLLOUT, tegese sambungan kadhaptar on_recv(), siap ngirim data. Fungsi iki ngirim respon HTTP ngemot HTML karo gambar kanggo klien lan banjur ngganti handler acara bali menyang on_recv().

Tampilake on_send () fungsi

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

Lan pungkasanipun, ing file http_server.c, ing fungsi main() kita nggawe I / O reaktor nggunakake reactor_new(), nggawe soket server lan ndhaftar, miwiti reaktor nggunakake reactor_run() kanggo persis siji menit, lan banjur kita nerbitaké sumber daya lan metu saka program.

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

Ayo priksa manawa kabeh bisa digunakake kaya sing dikarepake. Kompilasi (chmod a+x compile.sh && ./compile.sh ing ROOT project) lan miwiti server nulis dhewe, mbukak http://127.0.0.1:18470 ing browser lan ndeleng apa sing dikarepake:

Reaktor I/O Bare-C kanthi fitur lengkap

Pangukuran kinerja

Tampilake spesifikasi mobilku

$ 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

Ayo ngukur kinerja server siji-threaded. Ayo mbukak rong terminal: siji bakal mbukak ./http_server, ing beda- wkwk. Sawise sawetara menit, statistik ing ngisor iki bakal ditampilake ing terminal kapindho:

$ 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

Server single-threaded kita bisa ngolah luwih saka 11 yuta panjalukan saben menit saka 100 sambungan. Ora ana asil sing ala, nanging apa bisa ditingkatake?

Server multithreaded

Kaya sing kasebut ing ndhuwur, reaktor I / O bisa digawe ing benang sing kapisah, saengga nggunakake kabeh inti CPU. Ayo dileksanakake pendekatan iki:

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

Saiki saben thread duweke dhewe reaktor:

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

Elinga yen argumen fungsi new_server() panyengkuyung true. Iki tegese kita nemtokake pilihan kanggo soket server SO_REUSEPORTkanggo nggunakake ing lingkungan multi-Utas. Sampeyan bisa maca rincian liyane kene.

Lari kapindho

Saiki ayo ngukur kinerja server multi-threaded:

$ wrk -c100 -d1m -t8 http://127.0.0.1:18470 -H "Host: 127.0.0.1:18470" -H "Accept-Language: en-US,en;q=0.5" -H "Connection: keep-alive"
Running 1m test @ http://127.0.0.1:18470
  8 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.14ms    2.53ms  40.73ms   89.98%
    Req/Sec    79.98k    18.07k  154.64k    78.65%
  38208400 requests in 1.00m, 5.23GB read
Requests/sec: 635876.41
Transfer/sec:     89.14MB

Jumlah panjalukan sing diproses sajrone 1 menit tambah ~3.28 kaping! Nanging kita mung ~ XNUMX yuta kurang saka nomer babak, mula ayo nyoba ndandani.

Pisanan ayo ndeleng statistik sing digawe parfum:

$ 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

Nggunakake CPU Affinity, kompilasi karo -march=native, OGP, Tambah ing nomer hit cache, Mundakake MAX_EVENTS lan nggunakake EPOLLET ora menehi Tambah pinunjul ing kinerja. Nanging apa sing kedadeyan yen sampeyan nambah jumlah sambungan bebarengan?

Statistik kanggo 352 sambungan simultan:

$ 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

Asil sing dikarepake dipikolehi, lan kanthi grafik sing menarik nuduhake katergantungan jumlah panjaluk sing diproses sajrone 1 menit babagan jumlah sambungan:

Reaktor I/O Bare-C kanthi fitur lengkap

Kita weruh yen sawise sawetara atus sambungan, jumlah panjalukan sing diproses kanggo loro server kasebut mudhun banget (ing versi multi-threaded iki luwih katon). Apa iki ana hubungane karo implementasi tumpukan TCP / IP Linux? Bebas bae kanggo nulis asumsi babagan prilaku grafik iki lan optimasi kanggo opsi multi-threaded lan single-threaded ing komentar.

Carane nyatet ing komentar, tes kinerja iki ora nuduhake prilaku reaktor I / O ing beban nyata, amarga meh tansah server sesambungan karo database, output log, nggunakake kriptografi karo TLS lan liya-liyane, akibate bebane dadi ora seragam (dinamis). Tes bebarengan karo komponen pihak katelu bakal ditindakake ing artikel babagan proaktor I/O.

Kekurangan reaktor I/O

Sampeyan kudu ngerti yen reaktor I / O ora tanpa kekurangan, yaiku:

  • Nggunakake reaktor I / O ing lingkungan multi-Utas Luwih angel, amarga sampeyan kudu ngatur aliran kanthi manual.
  • Praktek nuduhake manawa beban kasebut ora seragam, sing bisa nyebabake siji thread logging nalika liyane sibuk kerja.
  • Yen salah sawijining pawang acara mblokir thread, pamilih sistem dhewe uga bakal mblokir, sing bisa nyebabake kewan omo sing angel ditemokake.

Ngatasi masalah kasebut proaktor I/O, kang asring duwe panjadwal sing roto-roto distributes mbukak menyang blumbang Utas, lan uga wis API luwih trep. Kita bakal ngomong babagan iki mengko, ing artikel liyane.

kesimpulan

Iki ngendi perjalanan kita saka teori langsung menyang knalpot profiler wis rampung.

Sampeyan ora kudu mikir babagan iki, amarga ana akeh pendekatan liyane sing padha menarik kanggo nulis piranti lunak jaringan kanthi tingkat penak lan kacepetan sing beda. Menarik, miturut pendapatku, pranala diwenehi ing ngisor iki.

Ndeleng sampeyan rauh!

proyek menarik

Apa maneh sing kudu dakwaca?

Source: www.habr.com

Add a comment