
тааныштыруу
(бир жип ) көптөгөн популярдуу чечимдерде колдонулган жогорку жүктөмдүү программаны жазуу үлгүсү:
- ...
Бул макалада биз I/O реакторунун инструменттерин жана анын кантип иштешин карап чыгабыз, ишке ашырууну 200 саптан аз код менен жазабыз жана 40 миллиондон ашык суроо-талап/мүнөттө жөнөкөй HTTP сервер процессин жасайбыз.
сөздөр
- Макала I/O реакторунун иштешин түшүнүүгө жардам берүү үчүн жазылган, ошондуктан аны колдонууда тобокелдиктерди түшүнүү.
- Макаланы түшүнүү үчүн негиздерди билүү талап кылынат. жана тармактык тиркемелерди иштеп чыгуу боюнча кээ бир тажрыйба.
- Бардык код C тилинде так жазылган (сак: узун PDF) Linux үчүн жана жеткиликтүү .
Ал эмне үчүн керек?
Интернеттин популярдуулугунун өсүшү менен веб-серверлер бир эле учурда көп сандагы туташуулар менен иштөөнү талап кыла баштады, ошондуктан эки ыкма аракет кылынды: көп сандагы OS жиптериндеги киргизүү/чыгарууларды бөгөттөө жана блокировка кылбоочу I/O менен айкалышта. окуя кабарлоо системасы, ошондой эле "система селектору" деп аталат (///жана башкалар).
Биринчи ыкма ар бир кирүүчү байланыш үчүн жаңы OS жипти түзүүнү камтыйт. Анын кемчилиги начар масштабдалуу болуп саналат: операциялык система көптөгөн ишке ашырууга туура келет и . Алар кымбат операциялар жана байланыштардын таасирдүү саны менен бош RAM жетишсиздигине алып келиши мүмкүн.
өзгөртүлгөн версия баса белгилейт (жип пулу), ошону менен системанын аткарууну токтотуусуна жол бербейт, бирок ошол эле учурда жаңы көйгөйдү киргизет: эгерде жип пулу учурда узак окуу операциялары менен бөгөттөлсө, анда буга чейин маалыматтарды ала алган башка розеткалар ушундай кыл.
Экинчи ыкманы колдонот (системалык селектор) ОС тарабынан берилген. Бул макалада киргизүү/чыгаруу операцияларына эмес, эскертүүлөргө (окуялар, эскертмелер) негизделген система селекторунун эң кеңири таралган түрү талкууланат. . Аны колдонуунун жөнөкөйлөштүрүлгөн мисалы төмөнкү блок-схема менен көрсөтүлүшү мүмкүн:

Бул ыкмалардын ортосундагы айырма төмөнкүдөй:
- I/O операцияларын бөгөттөө токтотуу колдонуучу агымы ушуга чейин, ушул убакка чейинOS туура болгонго чейин кирүүчү байт агымына (, маалыматтарды кабыл алуу) же ички жазуу буферлеринде кийинки аркылуу жөнөтүү үчүн орун жетишсиз болот. (маалыматтарды жөнөтүү).
- Системалык селектор убакыттын өтүшү менен OS деп программага кабарлайт буга чейин дефрагментацияланган IP пакеттери (TCP, маалыматтарды кабыл алуу) же ички жазуу буферлеринде жетиштүү орун буга чейин жеткиликтүү (маалыматтарды жөнөтүү).
Жыйынтыктап айтканда, ар бир киргизүү/чыгаруу үчүн ОС жибин резервдештирүү эсептөө күчүн текке кетирүү болуп саналат, анткени чындыгында жиптер пайдалуу иштерди жасабайт (ошондуктан бул термин ). Системалык селектор бул көйгөйдү чечип, колдонуучу программасына CPU ресурстарын алда канча үнөмдүү пайдаланууга мүмкүндүк берет.
I/O реакторунун модели
Киргизүү/чыгаруу реактору система селектору менен колдонуучунун коду ортосундагы катмардын ролун аткарат. Анын иштөө принциби төмөнкү блок-схема менен сүрөттөлөт:

- Эске сала кетейин, окуя - бул белгилүү бир розетка бөгөттөлбөгөн киргизүү/чыгаруу операциясын аткара ала тургандыгы жөнүндө билдирүү.
- Окуяны иштетүүчү - бул окуя кабыл алынганда киргизүү/чыгаруу реактору тарабынан чакырылган функция, андан кийин блокировка кылбаган киргизүү/чыгарма операциясын аткарат.
Белгилей кетчү нерсе, киргизүү/чыгаруу реактору аныктамасы боюнча бир жиптүү, бирок концепцияны көп жиптүү чөйрөдө 1 жип: 1 реактор катышында колдонууга эч нерсе тоскоол болбойт, ошону менен бардык CPU өзөктөрү кайра иштетилет.
Реализация
Биз жалпы интерфейсти файлга жайгаштырабыз , жана ишке ашыруу - жылы . reactor.h төмөнкү кулактандыруулардан турат:
Декларацияларды 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);Киргизүүчү/чыгаруучу реактордун түзүмү төмөнкүлөрдөн турат селектор и , ар бир розеткага карта CallbackData (окуяны иштетүүчү структурасы жана ал үчүн колдонуучунун аргументи).
Reactor жана CallbackData көрсөтүү
struct reactor {
int epoll_fd;
GHashTable *table; // (int, CallbackData)
};
typedef struct {
Callback callback;
void *arg;
} CallbackData;Биз иштетүү мүмкүнчүлүгүн иштеткенибизди эске алыңыз индекси боюнча. IN reactor.h структурасын жарыялайбыз reactorжана reactor.c биз аны аныктайбыз, ошону менен колдонуучу анын талааларын ачык өзгөртүүсүнө жол бербейбиз. Бул үлгүлөрдүн бири болуп саналат , ал кыскача С семантикасына туура келет.
милдеттери reactor_register, reactor_deregister и reactor_reregister Кызыккан розеткалардын тизмесин жана система селекторундагы жана хэш таблицасында тиешелүү окуяны иштетүүчүлөрдү жаңыртыңыз.
Каттоо функцияларын көрсөтүү
#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;
}I/O реактору окуяны дескриптор менен кармап алгандан кийин fd, ал өтө турган тиешелүү окуяны иштеткичти чакырат fd, түзүлгөн окуялар жана колдонуучу көрсөткүчү void.
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/O реакторун жогорку жүктөмдө сынап көрүү үчүн биз каалаган суроого сүрөт менен жооп берген жөнөкөй HTTP веб серверин жазабыз.
HTTP протоколуна тез шилтеме
- Бул протокол , негизинен сервер-браузер өз ара аракеттенүүсү үчүн колдонулат.
HTTP оңой эле колдонсо болот протокол , белгиленген форматта билдирүүлөрдү жөнөтүү жана кабыл алуу .
Сурам форматы
<КОМАНДА> <URI> <ВЕРСИЯ HTTP>CRLF
<ЗАГОЛОВОК 1>CRLF
<ЗАГОЛОВОК 2>CRLF
<ЗАГОЛОВОК N>CRLF CRLF
<ДАННЫЕ>CRLFэки белгинин ырааттуулугу болуп саналат:rиn, суроо-талаптын биринчи сабын, баш жана маалыматтарды бөлүп.<КОМАНДА>- бириCONNECT,DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT,TRACE. Браузер биздин серверге буйрук жөнөтөтGET, "Файлдын мазмунун мага жөнөт" дегенди билдирет.<URI>- . Мисалы, эгерде URI =/index.html, анда кардар сайттын негизги барагын сурайт.<ВЕРСИЯ HTTP>— форматтагы HTTP протоколунун версиясыHTTP/X.Y. Бүгүнкү күндө эң көп колдонулган версия болуп саналатHTTP/1.1.<ЗАГОЛОВОК N>форматындагы ачкыч-маанилик жуп болуп саналат<КЛЮЧ>: <ЗНАЧЕНИЕ>, андан ары талдоо үчүн серверге жөнөтүлдү.<ДАННЫЕ>— операцияны аткаруу үчүн сервер талап кылган маалыматтар. Көп учурда бул жөнөкөй же башка формат.
Жооп форматы
<ВЕРСИЯ HTTP> <КОД СТАТУСА> <ОПИСАНИЕ СТАТУСА>CRLF
<ЗАГОЛОВОК 1>CRLF
<ЗАГОЛОВОК 2>CRLF
<ЗАГОЛОВОК N>CRLF CRLF
<ДАННЫЕ><КОД СТАТУСА>операциянын натыйжасын билдирген сан болуп саналат. Биздин сервер ар дайым 200 статусун кайтарып берет (ийгиликтүү иштөө).<ОПИСАНИЕ СТАТУСА>— статус кодун сап көрсөтүү. Статус коду 200 үчүн булOK.<ЗАГОЛОВОК N>— суроо-талаптагыдай форматтагы баш. Биз наамдарды кайтарып беребизContent-Length(файлдын өлчөмү) жанаContent-Type: text/html(кайтаруучу маалымат түрү).<ДАННЫЕ>— колдонуучу сураган маалыматтар. Биздин учурда, бул сүрөттүн жолу .
билэ (бир жиптүү сервер) файлды камтыйт , ал төмөнкү функциянын прототиптерин камтыйт:
Жалпы функциянын прототиптерин көрсөтүү.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);Функционалдык макро да сүрөттөлгөн SAFE_CALL() жана функциясы аныкталган fail(). Макрос туюнтумдун маанисин ката менен салыштырат жана шарт чын болсо, функцияны чакырат fail():
#define SAFE_CALL(call, error)
do {
if ((call) == error) {
fail("%s", #call);
}
} while (false)милдети fail() терминалга берилген аргументтерди басып чыгарат (мисалы ) жана программаны код менен токтотот 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);
}милдети new_server() системалык чакыруулар менен түзүлгөн "сервер" розеткасынын файл дескрипторун кайтарат , и жана блоктолбогон режимде кирүүчү байланыштарды кабыл алууга жөндөмдүү.
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;
}- Эскертүү, розетка алгач желектин жардамы менен блоктолбогон режимде түзүлөт
SOCK_NONBLOCKошондуктан функциядаon_accept()(көбүрөөк окуу) системалык чалууaccept()жиптин аткарылышын токтоткон жок. - эгер
reuse_portбарабарtrue, анда бул функция розетка опциясы менен конфигурациялайт аркылуу бир эле портту көп агымдуу чөйрөдө колдонуу үчүн («Көп агымдуу сервер» бөлүмүн караңыз).
Окуяларды иштетүүчү on_accept() OS окуяны жараткандан кийин чакырылат EPOLLIN, бул учурда жаңы байланышты кабыл алууга болот дегенди билдирет. on_accept() жаңы туташууну кабыл алып, аны бөгөттөгөн режимге которот жана окуяны иштетүүчү менен каттайт on_recv() I/O реакторунда.
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);
}Окуяларды иштетүүчү on_recv() OS окуяны жараткандан кийин чакырылат EPOLLIN, бул учурда байланыш катталганын билдирет on_accept(), маалыматтарды алууга даяр.
on_recv() HTTP сурамы толугу менен алынганга чейин туташуудан маалыматтарды окуйт, андан кийин ал иштеткичти каттайт on_send() HTTP жооп жөнөтүү үчүн. Эгерде кардар байланышты үзсө, розетка каттоодон чыгарылат жана колдонуу менен жабылат .
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);
}
}Окуяларды иштетүүчү on_send() OS окуяны жараткандан кийин чакырылат EPOLLOUT, байланыш катталган дегенди билдирет on_recv(), дайындарды жөнөтүүгө даяр. Бул функция кардарга сүрөтү бар HTML камтыган HTTP жообун жөнөтөт жана андан кийин окуяны иштеткичти кайра өзгөртөт on_recv().
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);
}Анан акырында, файлда http_server.c, функцияда main() колдонуп I/O реакторун түзөбүз reactor_new(), сервер розеткасын түзүп, аны каттаңыз, реакторду колдонуп баштаңыз reactor_run() так бир мүнөт, анан биз ресурстарды бошотуп, программадан чыгабыз.
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);
}Баары күтүлгөндөй иштеп жатканын текшерип көрөлү. түзүү (chmod a+x compile.sh && ./compile.sh долбоордун тамырында) жана өз алдынча жазылган серверди ишке киргизиңиз, ачыңыз браузерде жана биз күткөн нерсени көрүңүз:

Өнүмдүүлүктү өлчөө
Менин унаамдын техникалык өзгөчөлүктөрүн көрсөт
$ 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Келгиле, бир жиптүү сервердин иштешин өлчөп көрөлү. Келгиле, эки терминал ачалы: биринде биз иштейбиз ./http_server, башкача айтканда - . Бир мүнөттөн кийин, экинчи терминалда төмөнкү статистика көрсөтүлөт:
$ 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Биздин бир жиптүү серверибиз 11 туташуудан келип чыккан мүнөтүнө 100 миллиондон ашык суроону иштете алды. Натыйжа жаман эмес, бирок аны жакшыртууга болобу?
Көп агымдуу сервер
Жогоруда айтылгандай, I/O реактору өзүнчө жиптерде түзүлүшү мүмкүн, муну менен бардык CPU өзөктөрү колдонулат. Келгиле, бул ыкманы иш жүзүндө колдонолу:
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);
}
}Азыр ар бир жип реактор:
static Reactor *reactor;
#pragma omp threadprivate(reactor)Функциянын аргументин эске алыңыз new_server() турат true. Бул сервер розеткасына опцияны дайындайбыз дегенди билдирет аны көп жиптүү чөйрөдө колдонуу. Сиз көбүрөөк окуй аласыз .
Экинчи чуркоо
Эми көп агымдуу сервердин иштешин өлчөп көрөлү:
$ 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.14MB1 мүнөттө иштетилген сурамдардын саны ~3.28 эсеге көбөйдү! Бирок биз тегерек санга XNUMX миллион гана жетишпей калдык, андыктан аны оңдоого аракет кылалы.
Алгач түзүлгөн статистиканы карап көрөлү :
$ 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, менен компиляция -march=native, , хиттердин санынын өсүшү , жогорулатуу MAX_EVENTS жана колдонуу EPOLLET вндурумдуулугун олуттуу жогорулатууну берген жок. Бирок, бир эле учурда байланыштардын санын көбөйтсөңүз эмне болот?
352 бир мезгилдеги байланыштар боюнча статистика:
$ 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Керектүү натыйжа алынды жана аны менен 1 мүнөттүн ичинде иштетилген сурамдардын санынын байланыштардын санына көз карандылыгын көрсөткөн кызыктуу график алынды:

Бир нече жүз туташуулардан кийин эки серверге тең иштетилген суроо-талаптардын саны кескин азайып жатканын көрүп жатабыз (көп агымдуу версияда бул кыйла байкалат). Бул Linux TCP/IP стекинин ишке ашырылышына байланыштуубу? Графиктин бул жүрүм-туруму жана көп жиптүү жана бир жиптүү варианттар үчүн оптималдаштыруу боюнча өз божомолдоруңузду комментарийлерде жазыңыз.
кантип комментарийлерде, бул өндүрүмдүүлүк тести реалдуу жүктөмдө I/O реакторунун жүрүм-турумун көрсөтпөйт, анткени дээрлик ар дайым сервер маалымат базасы менен иштешет, журналдарды чыгарат, криптографияны колдонот. ж.б., мунун натыйжасында жүк бирдей эмес (динамикалык) болуп калат. Үчүнчү тараптын компоненттери менен бирге сыноолор I/O проактору жөнүндө макалада жүргүзүлөт.
I/O реакторунун кемчиликтери
Сиз I/O реакторунун кемчиликтери жок эмес экенин түшүнүү керек, атап айтканда:
- Көп жиптүү чөйрөдө I/O реакторун колдонуу бир аз кыйыныраак, анткени агымдарды кол менен башкарууга туура келет.
- Практика көрсөткөндөй, көпчүлүк учурларда жүк бирдей эмес, бул бир жиптин кесилишине алып келиши мүмкүн, ал эми экинчиси жумуш менен алек.
- Эгерде бир окуя иштеткич жипти бөгөттөсө, система селекторунун өзү да бөгөттөп, табуу кыйын мүчүлүштүктөргө алып келиши мүмкүн.
Бул көйгөйлөрдү чечет , ал көбүнчө жиптердин көлмөсүнө жүктү бирдей бөлүштүрүүчү пландаштыргычка ээ, ошондой эле ыңгайлуураак API'ге ээ. Бул тууралуу кийинчерээк, башка макаламда сүйлөшөбүз.
жыйынтыктоо
Бул жерде биздин теориядан түз профайлдын түтүктөрүнө болгон саякатыбыз аяктады.
Сиз бул жөнүндө көп ойлонбоңуз, анткени ар кандай деңгээлдеги ыңгайлуулук жана ылдамдык менен тармактык программалык камсыздоону жазуу үчүн бирдей кызыктуу ыкмалар бар. Кызыктуу, менин оюмча, шилтемелер төмөндө келтирилген.
Көрүшкөнгө чейин!
Кызыктуу долбоорлор
- B
Дагы эмнени окушум керек?
Source: www.habr.com
