
Cyflwyniad
(edau sengl ) yn batrwm ar gyfer ysgrifennu meddalwedd llwyth uchel, a ddefnyddir mewn llawer o atebion poblogaidd:
- ...
Yn yr erthygl hon, byddwn yn edrych ar i mewn a thu allan adweithydd I / O a sut mae'n gweithio, yn ysgrifennu gweithrediad mewn llai na 200 llinell o god, ac yn gwneud proses gweinydd HTTP syml dros 40 miliwn o geisiadau / mun.
Rhagair
- Ysgrifennwyd yr erthygl i helpu i ddeall gweithrediad yr adweithydd I/O, ac felly i ddeall y risgiau wrth ei ddefnyddio.
- Mae angen gwybodaeth o'r pethau sylfaenol i ddeall yr erthygl. a pheth profiad o ddatblygu cymwysiadau rhwydwaith.
- Mae'r holl god wedi'i ysgrifennu yn iaith C yn unol â (rhybudd: PDF hir) gyfer Linux ac mae ar gael ar .
Pam mae angen hyn?
Gyda phoblogrwydd cynyddol y Rhyngrwyd, dechreuodd gweinyddwyr gwe fod angen trin nifer fawr o gysylltiadau ar yr un pryd, ac felly rhoddwyd cynnig ar ddau ddull: blocio I/O ar nifer fawr o edafedd OS a pheidio â rhwystro I/O ar y cyd â system hysbysu digwyddiad, a elwir hefyd yn "ddewisydd system" (///etc).
Roedd y dull cyntaf yn cynnwys creu edefyn OS newydd ar gyfer pob cysylltiad sy'n dod i mewn. Ei anfantais yw scalability gwael: bydd yn rhaid i'r system weithredu weithredu llawer и . Maent yn weithrediadau drud a gallant arwain at ddiffyg RAM am ddim gyda nifer drawiadol o gysylltiadau.
Mae'r fersiwn addasedig yn amlygu (pwll edau), a thrwy hynny atal y system rhag erthylu gweithredu, ond ar yr un pryd yn cyflwyno problem newydd: os yw pwll edau yn cael ei rwystro ar hyn o bryd gan weithrediadau darllen hir, yna ni fydd socedi eraill sydd eisoes yn gallu derbyn data yn gallu gwneud hynny.
Mae'r ail ddull yn defnyddio (detholwr system) a ddarperir gan yr OS. Mae'r erthygl hon yn trafod y math mwyaf cyffredin o ddewiswr system, yn seiliedig ar rybuddion (digwyddiadau, hysbysiadau) ynghylch parodrwydd ar gyfer gweithrediadau I/O, yn hytrach nag ar . Gellir cynrychioli enghraifft symlach o'i ddefnydd gan y diagram bloc canlynol:

Mae'r gwahaniaeth rhwng y dulliau hyn fel a ganlyn:
- Rhwystro gweithrediadau I/O atal llif defnyddiwr nesnes bod yr OS yn iawn yn dod i mewn i ffrwd beit (, derbyn data) neu ni fydd digon o le ar gael yn y byfferau ysgrifennu mewnol ar gyfer anfon ymlaen wedyn (anfon data).
- Dewisydd system dros amser yn hysbysu'r rhaglen bod yr OS eisoes pecynnau IP wedi'u darnio (TCP, derbyn data) neu ddigon o le mewn byfferau ysgrifennu mewnol eisoes ar gael (anfon data).
I grynhoi, mae cadw edefyn OS ar gyfer pob I/O yn wastraff o bŵer cyfrifiadurol, oherwydd mewn gwirionedd, nid yw'r edafedd yn gwneud gwaith defnyddiol (a dyna pam y mae'r term ). Mae'r dewisydd system yn datrys y broblem hon, gan ganiatáu i'r rhaglen ddefnyddwyr ddefnyddio adnoddau CPU yn llawer mwy darbodus.
Model adweithydd I/O
Mae'r adweithydd I/O yn gweithredu fel haen rhwng y dewisydd system a'r cod defnyddiwr. Disgrifir egwyddor ei weithrediad gan y diagram bloc canlynol:

- Gadewch imi eich atgoffa bod digwyddiad yn hysbysiad bod soced penodol yn gallu cyflawni gweithrediad I/O nad yw'n rhwystro.
- Mae triniwr digwyddiad yn swyddogaeth a elwir gan yr adweithydd I/O pan dderbynnir digwyddiad, sydd wedyn yn cyflawni gweithrediad I/O nad yw'n rhwystro.
Mae'n bwysig nodi bod yr adweithydd I/O trwy ddiffiniad yn un edau, ond nid oes dim yn atal y cysyniad rhag cael ei ddefnyddio mewn amgylchedd aml-edau ar gymhareb o adweithydd 1 edau: 1, a thrwy hynny ailgylchu holl greiddiau CPU.
Gweithredu
Byddwn yn gosod y rhyngwyneb cyhoeddus mewn ffeil , a gweithredu - yn . reactor.h bydd yn cynnwys y cyhoeddiadau a ganlyn:
Dangos datganiadau yn 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);Mae strwythur yr adweithydd I/O yn cynnwys detholwr и , sy'n mapio pob soced i CallbackData (strwythur trafodwr digwyddiad a dadl defnyddiwr ar ei gyfer).
Dangos Data Adweithydd a Alwad
struct reactor {
int epoll_fd;
GHashTable *table; // (int, CallbackData)
};
typedef struct {
Callback callback;
void *arg;
} CallbackData;Sylwch ein bod wedi galluogi'r gallu i drin yn ôl y mynegai. YN reactor.h rydym yn datgan y strwythur reactor, ac yn reactor.c rydym yn ei ddiffinio, gan atal y defnyddiwr rhag newid ei feysydd yn benodol. Dyma un o'r patrymau , sy'n cyd-fynd yn gryno â semanteg C.
Swyddogaethau reactor_register, reactor_deregister и reactor_reregister diweddaru'r rhestr o socedi o ddiddordeb a thrinwyr digwyddiadau cyfatebol yn y dewisydd system a thabl stwnsh.
Dangos swyddogaethau cofrestru
#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;
}Ar ôl i'r adweithydd I/O ryng-gipio'r digwyddiad gyda'r disgrifydd fd, mae'n galw'r triniwr digwyddiad cyfatebol, y mae'n mynd iddo fd, digwyddiadau a gynhyrchir a phwyntydd defnyddiwr i void.
Dangos swyddogaeth 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;
}I grynhoi, bydd y gadwyn o alwadau swyddogaeth yn y cod defnyddiwr ar y ffurf ganlynol:

Gweinydd edau sengl
Er mwyn profi'r adweithydd I / O dan lwyth uchel, byddwn yn ysgrifennu gweinydd gwe HTTP syml sy'n ymateb i unrhyw gais gyda delwedd.
Cyfeiriad cyflym at brotocol HTTP
- dyma'r protocol , a ddefnyddir yn bennaf ar gyfer rhyngweithio gweinydd-porwr.
Gellir defnyddio HTTP yn hawdd drosodd protocol , anfon a derbyn negeseuon mewn fformat penodol .
Fformat Cais
<КОМАНДА> <URI> <ВЕРСИЯ HTTP>CRLF
<ЗАГОЛОВОК 1>CRLF
<ЗАГОЛОВОК 2>CRLF
<ЗАГОЛОВОК N>CRLF CRLF
<ДАННЫЕ>CRLFyn ddilyniant o ddau gymeriad:rиn, gan wahanu llinell gyntaf y cais, penawdau a data.<КОМАНДА>- un oCONNECT,DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT,TRACE. Bydd y porwr yn anfon gorchymyn i'n gweinyddGET, sy'n golygu "Anfon cynnwys y ffeil ataf."<URI>- . Er enghraifft, os URI =/index.html, yna mae'r cleient yn gofyn am brif dudalen y wefan.<ВЕРСИЯ HTTP>— fersiwn o'r protocol HTTP yn y fformatHTTP/X.Y. Y fersiwn a ddefnyddir amlaf heddiw ywHTTP/1.1.<ЗАГОЛОВОК N>yn bâr gwerth allweddol yn y fformat<КЛЮЧ>: <ЗНАЧЕНИЕ>, wedi'i anfon at y gweinydd i'w ddadansoddi ymhellach.<ДАННЫЕ>- data sydd ei angen ar y gweinydd i gyflawni'r llawdriniaeth. Yn aml mae'n syml neu unrhyw fformat arall.
Fformat Ymateb
<ВЕРСИЯ HTTP> <КОД СТАТУСА> <ОПИСАНИЕ СТАТУСА>CRLF
<ЗАГОЛОВОК 1>CRLF
<ЗАГОЛОВОК 2>CRLF
<ЗАГОЛОВОК N>CRLF CRLF
<ДАННЫЕ><КОД СТАТУСА>yw rhif sy'n cynrychioli canlyniad y llawdriniaeth. Bydd ein gweinydd bob amser yn dychwelyd statws 200 (gweithrediad llwyddiannus).<ОПИСАНИЕ СТАТУСА>— cynrychioliad llinynnol o'r cod statws. Ar gyfer cod statws 200 mae hynOK.<ЗАГОЛОВОК N>— pennawd o'r un fformat ag yn y cais. Byddwn yn dychwelyd y teitlauContent-Length(maint ffeil) aContent-Type: text/html(math o ddata dychwelyd).<ДАННЫЕ>— data y gofynnodd y defnyddiwr amdano. Yn ein hachos ni, dyma'r llwybr i'r ddelwedd i mewn .
file (gweinydd edefyn sengl) yn cynnwys ffeil , sy'n cynnwys y prototeipiau swyddogaeth canlynol:
Dangos prototeipiau ffwythiant yn gyffredin.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);Disgrifir y macro swyddogaethol hefyd SAFE_CALL() ac mae'r swyddogaeth wedi'i diffinio fail(). Mae'r macro yn cymharu gwerth y mynegiant gyda'r gwall, ac os yw'r cyflwr yn wir, mae'n galw'r ffwythiant fail():
#define SAFE_CALL(call, error)
do {
if ((call) == error) {
fail("%s", #call);
}
} while (false)Swyddogaeth fail() yn argraffu'r dadleuon a basiwyd i'r derfynell (fel ) ac yn terfynu'r rhaglen gyda'r cod 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);
}Swyddogaeth new_server() yn dychwelyd disgrifydd ffeil y soced "gweinydd" a grëwyd gan alwadau system , и ac yn gallu derbyn cysylltiadau sy'n dod i mewn mewn modd nad yw'n rhwystro.
Dangos swyddogaeth 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;
}- Sylwch fod y soced yn cael ei greu i ddechrau yn y modd di-flocio gan ddefnyddio'r faner
SOCK_NONBLOCKfel bod yn y swyddogaethon_accept()(darllen mwy) galwad systemaccept()ni ataliodd y gweithrediad edefyn. - Os
reuse_portyn hafaltrue, yna bydd y swyddogaeth hon yn ffurfweddu'r soced gyda'r opsiwn trwodd i ddefnyddio'r un porthladd mewn amgylchedd aml-threaded (gweler yr adran “Gweinydd aml-edau”).
Triniwr Digwyddiad on_accept() a elwir ar ôl i'r OS gynhyrchu digwyddiad EPOLLIN, yn yr achos hwn yn golygu y gellir derbyn y cysylltiad newydd. on_accept() yn derbyn cysylltiad newydd, yn ei newid i fodd di-flocio ac yn cofrestru gyda thriniwr digwyddiad on_recv() mewn adweithydd I/O.
Dangos swyddogaeth 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);
}Triniwr Digwyddiad on_recv() a elwir ar ôl i'r OS gynhyrchu digwyddiad EPOLLIN, yn yr achos hwn yn golygu bod y cysylltiad cofrestredig on_accept(), yn barod i dderbyn data.
on_recv() yn darllen data o'r cysylltiad nes bod y cais HTTP wedi'i dderbyn yn llwyr, yna mae'n cofrestru triniwr on_send() i anfon ymateb HTTP. Os bydd y cleient yn torri'r cysylltiad, caiff y soced ei ddadgofrestru a'i gau gan ddefnyddio .
Dangos ffwythiant 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);
}
}Triniwr Digwyddiad on_send() a elwir ar ôl i'r OS gynhyrchu digwyddiad EPOLLOUT, sy'n golygu bod y cysylltiad wedi'i gofrestru on_recv(), yn barod i anfon data. Mae'r swyddogaeth hon yn anfon ymateb HTTP sy'n cynnwys HTML gyda delwedd i'r cleient ac yna'n newid y triniwr digwyddiad yn ôl i on_recv().
Dangos swyddogaeth 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);
}Ac yn olaf, yn y ffeil http_server.c, mewn swyddogaeth main() rydym yn creu adweithydd I/O gan ddefnyddio reactor_new(), creu soced gweinydd a'i gofrestru, dechreuwch yr adweithydd gan ddefnyddio reactor_run() am funud yn union, ac yna rydym yn rhyddhau adnoddau ac yn gadael y rhaglen.
Dangos 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);
}Gadewch i ni wirio bod popeth yn gweithio yn ôl y disgwyl. Wrthi'n llunio (chmod a+x compile.sh && ./compile.sh yng ngwraidd y prosiect) a lansio'r gweinydd hunan-ysgrifenedig, agor yn y porwr a gweld beth oeddem yn ei ddisgwyl:

Mesur perfformiad
Dangoswch fy manylebau car
$ 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
.MMMMMMMMMMMMMMMMMMMGadewch i ni fesur perfformiad gweinydd un edau. Gadewch i ni agor dwy derfynell: mewn un byddwn yn rhedeg ./http_server, mewn gwahanol - . Ar ôl munud, bydd yr ystadegau canlynol yn cael eu harddangos yn yr ail derfynell:
$ 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.19MBRoedd ein gweinydd un edau yn gallu prosesu dros 11 miliwn o geisiadau y funud yn deillio o 100 o gysylltiadau. Ddim yn ganlyniad gwael, ond a ellir ei wella?
Gweinydd aml-threaded
Fel y soniwyd uchod, gellir creu'r adweithydd I / O mewn edafedd ar wahân, a thrwy hynny ddefnyddio'r holl greiddiau CPU. Gadewch i ni roi'r dull hwn ar waith:
Dangos 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);
}
}Nawr pob llinyn adweithydd:
static Reactor *reactor;
#pragma omp threadprivate(reactor)Sylwch fod y ddadl swyddogaeth new_server() eiriolwyr true. Mae hyn yn golygu ein bod yn aseinio'r opsiwn i soced y gweinydd i'w ddefnyddio mewn amgylchedd aml-edau. Gallwch ddarllen mwy o fanylion .
Ail rediad
Nawr gadewch i ni fesur perfformiad gweinydd aml-edau:
$ 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.14MBCynyddodd nifer y ceisiadau a broseswyd mewn 1 munud gan ~3.28 gwaith! Ond dim ond ~XNUMX filiwn oedden ni'n brin o'r rhif crwn, felly gadewch i ni geisio trwsio hynny.
Yn gyntaf, gadewch i ni edrych ar yr ystadegau a gynhyrchwyd :
$ 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, crynhoad gyda -march=native, , cynnydd yn nifer yr ymweliadau , cynyddu MAX_EVENTS a defnydd EPOLLET ni roddodd gynnydd sylweddol mewn perfformiad. Ond beth sy'n digwydd os ydych chi'n cynyddu nifer y cysylltiadau cydamserol?
Ystadegau ar gyfer 352 o gysylltiadau cydamserol:
$ 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.34MBCafwyd y canlyniad dymunol, a chydag ef graff diddorol yn dangos dibyniaeth nifer y ceisiadau wedi'u prosesu mewn 1 munud ar nifer y cysylltiadau:

Gwelwn, ar ôl cwpl o gannoedd o gysylltiadau, fod nifer y ceisiadau a broseswyd ar gyfer y ddau weinydd yn gostwng yn sydyn (mae hyn yn fwy amlwg yn y fersiwn aml-edau). A yw hyn yn gysylltiedig â'r gweithrediad? TCP/IP pentwr LinuxMae croeso i chi rannu eich meddyliau ar ymddygiad y graff hwn a'r optimeiddiadau ar gyfer y fersiynau aml-edau ac un-edau yn y sylwadau.
Fel yn y sylwadau, nid yw'r prawf perfformiad hwn yn dangos ymddygiad yr adweithydd I/O o dan lwythi real, oherwydd bron bob amser mae'r gweinydd yn rhyngweithio â'r gronfa ddata, logiau allbynnau, yn defnyddio cryptograffeg gyda ac ati, ac o ganlyniad mae'r llwyth yn dod yn anwisg (deinamig). Bydd profion ynghyd â chydrannau trydydd parti yn cael eu cynnal yn yr erthygl am yr proactor I/O.
Anfanteision adweithydd I/O
Mae angen i chi ddeall nad yw'r adweithydd I/O heb ei anfanteision, sef:
- Mae defnyddio adweithydd I/O mewn amgylchedd aml-edau braidd yn anoddach, oherwydd bydd yn rhaid i chi reoli'r llifau â llaw.
- Mae ymarfer yn dangos nad yw'r llwyth yn unffurf yn y rhan fwyaf o achosion, a all arwain at logio un edefyn tra bod un arall yn brysur gyda gwaith.
- Os bydd un triniwr digwyddiad yn blocio edefyn, bydd dewisydd y system ei hun hefyd yn blocio, a all arwain at fygiau anodd eu darganfod.
Yn datrys y problemau hyn , sydd yn aml â rhaglennydd sy'n dosbarthu'r llwyth yn gyfartal i gronfa o edafedd, ac mae ganddo hefyd API mwy cyfleus. Byddwn yn siarad amdano yn nes ymlaen, yn fy erthygl arall.
Casgliad
Dyma lle mae ein taith o theori yn syth i'r gwacáu proffiliwr wedi dod i ben.
Ni ddylech aros ar hyn, oherwydd mae yna lawer o ddulliau eraill yr un mor ddiddorol o ysgrifennu meddalwedd rhwydwaith gyda gwahanol lefelau o gyfleustra a chyflymder. Yn ddiddorol, yn fy marn i, rhoddir dolenni isod.
Welwn ni chi eto!
Prosiectau diddorol
- Si
Beth arall i'w ddarllen?
Ffynhonnell: hab.com
