Яндекс corporate blog
High performance
Programming
C++
Comments 53
+3

Расскажи, что за магия с отложенным вызовом функции при логировании

0
Макрос LOG_DEBUG() описан приблизитолько вот так:
#define LOG_DEBUG() if (GetCurrentLogLevel() >= debug_level) std::cout

Так что если уровень логирования недостаточный — в ветку if не заходим и никакие функции не выполняем.

* только вместо std::cout у нас пара наворотов, чтобы побыстрее формировать строчку (без динамических аллокаций, std::locale и прочего безобразия) и логировать её асинхронно. На C++ Piter я мельком показывал, как оно сделано под капотом.
0
Интересно, можно ли это когданибудь будет написать на чистом C++, без макросов?
+3
Да, сейчас думают добавить lazy evaluated arguments в C++. С ними можно будет писать нечто наподобие
log_debug("{} is not OK of {}", request.id, GetSomeInfoFromDb());
и не вычислять GetSomeInfoFromDb() если логировать не нужно.
0
Как-то так:

logger.debug([&](auto stream){ auto value = heavyCalculation(); stream << "Value is" << value; });
0

А ещё можно ммапить файл с логом и тупо делать memcpy туда. Быстро, асинхронно, устойчиво к падениям приложения (ядро ОС даёт все нужные гарантии).

0
Это будет блокирующей операцией — в зависимости от флажков можно получать page faults при записи или блокирование в mmap. Так что memcpy надо будет запускать в специальном пуле/потоке с блокировкой которого мы готовы мириться.
+1

Этого можно избежать вещами типа madvise, mlock и прочего подобного.


То есть, понятно, что блокировка возможна всегда, но если вы будете делать mmap заранее (возможно, в отдельном потоке) и какой-нибудь mlock заранее (тоже, возможно, в отдельном потоке), то вероятность этого стремится к нулю, и при возникновении такой ситуации у вас система, скорее всего, будет в таком состоянии, что вам не до логов вашего приложения.

0

Как атомарно записать в конец файла в таком случае?


Ядро точно даёт гарантии без msync()?

0

Что значит «атомарно»? Вам точно для записи логов нужны гарантии, что вы не увидите partial write из другого потока (хотя я почти уверен, что ОС их даёт)?

0

У меня в файле 1000 байтов логов. Поток А хочет залоггировать 100 байтов. Поток Б — 200 байтов. Я хочу увидеть в итоге файл на 1300 байтов, а в нём в конце две записи, в каком угодно порядке, но отдельно. А не так, что файл будет на 1100 байтов и в нём — половина лога Б.


open() с O_APPEND и write() атомарны при небольших записях (страница памяти). Как это можно сделать через mmap() и запись в память напрямую?


По идее, держать длину лога рядом в атомарной переменной и когда отображаемый кусок подходит в концу, то придётся блокировать запись в мьютексе, пока там файлу увеличивают размер и меняют маппинг в памяти.


Впрочем, на случай недостатка места в отображаемом куске можно условный memcpy() делать в дополнительный буфер-очередь (просто в памяти), а расширение файла вынести в отдельный поток, который после завершения работы подберёт всё из буфера. Лишь бы буфера хватило.

0

А, ну тут надо идти на компромисс в виде возможно неиспользуемого места. Например, сразу создавать файл на мегабайт (или на гигабайт) и писать в него, пока он не закончится, подменяя указатель на файл, когда он подходит к концу.


По идее, держать длину лога рядом в атомарной переменной и когда отображаемый кусок подходит в концу, то придётся блокировать запись в мьютексе, пока там файлу увеличивают размер и меняют маппинг в памяти.

Это можно сделать так, что дело обойдётся обычным CAS без всяких блокировок во всех реалистичных случаях.

+1

Блокировка всё равно понадобится (в коде), пусть и в оптимистичном случае мы на неё никогда не натолкнёмся. У меня есть маппинг на 1 МБ, куда сейчас пишут. Есть уже готовый маппинг ещё на 1 МБ, куда мы переключимся, когда первый закончится. И за то время, пока заполняется новый маппинг, надо успеть подготовить следующий ему на смену. Но если мы не успеем, то придётся идти и ждать.


Плюс должны быть гарантии, что старый маппинг на 1 МБ не освободят, пока в него не дозаписали все, кто увидел его до переключения. Но это, мне кажется, тоже атомиками можно как-то разрулить.

0

Да, если вы заполняете мегабайт логов быстрее, чем создаётся пустой файл на мегабайт, ему делается mmap и mlock, то да, придётся ждать. Я там рядом об этом писал.

0

А насчёт падений, кстати, классно. Я почитал, что Линкус действительно даёт гарантию, что отображаемые страницы останутся в страничном кеше и — если только система не упадёт или там питание не отключится — то если поток записал что-то в эту память, то оно (со временем) дойдёт до диска. Даже если процесс пристрелит SIGKILL посреди memcpy().


Это всё предполагает, что страницы реально в физической памяти, для этого и всякие mlock() нужны.

0
Из того что я заметил, пробежавшись по документации и примерам:
* там только http сервер (нет баз данных, логирования и прочего)
* нет асинхронности, точнее её можно реализовать через цепочки фьючеров
    response
            .send(Http::Code::Request_Timeout, "Timeout")
            .then([=](ssize_t) { }, PrintException());


Другими словами, pistache.io скорее просто библиотека, а не фреймворк. При этом с ней необходимо использовать callbacks. Вы не сможете писать высокопроизводительный код, который будет выглядеть как обычный синхронный код.
0
На порядки меньше чем на поток. По идее, при выкладывании в open source нам надо будет вынести размер в конфиг, чтобы можно было настраивать фреймворк под любые задачи.
0

Поддерживаю вопрос, очень интересно.
Непонятно, что делать со всей информацией, как это всё использовать, если нет сорцов?

0
чем не подошли существующие опенсорсные решения? Наверняка, вы их тоже пробовали?
Какие получились накладные расходы по ЦПУ и памяти? Какой прирост с существующим опенсорсом?
0
Отсутствием асинхронных драйверов для всего подряд, разрозненностью интерфейсов, производительностью, отсутствием принятых у нас подходов и классов.

Для нормального фреймворка вам мало собрать популярные библиотеки в один проект. Вам важно подружить их, предоставить консистентные интерфейсы, наладить системы сборки, реализовать возможности которых нет у других. Корутиновый движок — малая часть фреймворка.

Если говорить только про корутиновый движок, то например в Boost.Fibers крайне простой шедулер. Нам он не подходит, а компоненты для построения более сложных шедулеров отсутствуют в Boost. И принести их в Boost нельзя, а значит и заапстримить изменения. В Boost так же нет части примитивов синхронизации, wait list примитивов плохо кастомизируются, что мешает их эффективной реализации и т.д. и т.п… В итоге, от изначального кода практически ничего не остаётся.
+1
Не нашёл в статъе ссылку на исходники и документацию.

Пример маловат, не ясно что под капотом.

На чём реализована работа с file handles (sockets) в плане мултиплексирования (epoll/poll/select)?
Можно-ли добавлять собственные классы (от чего наследоваться), для мултиплексирования?

EDIT:

HTTP/2 поддерживается?
+6
Сейчас мы раздумываем, выкладывать ли фреймворк в open source. Если решим, что да, подготовка фреймворка к открытию исходников потребует достаточно больших усилий.

Они просто похвастались, что у них есть такая штука. Ну OK.

0
Можно хотя бы, хоть немного увидеть минимальную версию фреймворка? или хотя бы просто, общую структуру проекта фреймворка? Заявлен достаточно хороший функциональный набор, который бы очень пригодился бы… как раз последнее время думаю, чтобы собрать нечто подобное в единое целое…
+1

asyncio — это совсем не то же самое, о чём идет речь в этой статье и о чём вопрос выше, как мне кажется. Это просто низоуровневая библиотека, механизм если хотите, для поддержки асинхронности и кооперативной многозадачности на уровне языка. В нём нет ни http-сервера/клиента, асинхронных драйверов к БД, быстрого event loop и всего остального о чём тут написано. Чтобы собрать подобный стек понадобится кучка сторонних библиотек поверх asyncio, вроде aiohttp и т.д. И надо заметить, asyncio далеко не идеален. Посмотрите в сторону trio. Это асинхронность с человеческим лицом. :)

0

И...? Я знаю про uvloop и асинхронные драйверы для БД. :) И это никак не противоречит тому, что я написал выше. Всё это отдельные библиотеки, которые надо собирать в стек. Тут же представлен фреймворк (не библиотека), который может сразу всё это из коробки.

0

Они предназначены для asyncio и отлично собираются в быстрый стек) Проблема асинхронности и кооперативной многозадачности для Питона в принципе уже решена — asyncio, gevent, stackless в PyPy, так что нужды в очередном асинхронном монофрейморке нет.
А моя реплика не в укор, а просто так. Эта ветка уже пустилась в пространные рассуждения)

+2

Под такое описание может попасть Starlette вместе с FastAPI и databases. Их делают одни и те же люди, оно всё внутри хорошо друг с другом интегрировано.

0
  1. Очень интересно про распределенных блокировки.
  2. Pocoproject содержит ту же функциональность (кроме распределенных блокировок), но там c++ не современный, так что, если уж в комментариях спрашивали про сравнение с другими фреймворками, то спрошу и я, чем лучше userver по сравнению с poco?
0

А как корутина понимает, что данный ответ на тот самый tcp запрос?

0
Чтобы отправить запрос прежде всего надо установить соединение с удалённой машиной. В итоге от ОС мы получаем сокет — нечто что связывает нас и удалённую машину. Теперь мы можем отправить через сокет байты, и сказать ОС «возобнови вот эту корутину, когда на вот этом сокете появятся данные».
0
Это у вас получается по соединению на запрос — тогда да, вопросов нет…
Получается, что если мы хотим работать с короутинами в одном соединении с множеством запросов — нам придется городить некий дополнительный функционал в протоколе?
0
Если у вас одно соединение с множеством не связанных друг с другом запросов — читаете сразу N запросов из сокета, порождайте N независимых корутин, в них обрабатываете данные/делаете запросы к базам данных/делаете запросы к другим микросервисам/… в первоначальной корутине ждёте ответов и отправляете их по сокету обратно.
0
Интересует инфраструктура проекта. У вас СMake?
Какой менеджер пакетов используете и используете ли? (Conan, vcpkg, qpm, что-то свое)
Корутины свои запилили или взяли из Boost?

Очень интересная для меня тема. Сейчас используем Kotlin/JVM в проде для микросервисов, но из-за JVM они не совсем «микро». Go идеален в плане минимальных системных требований, но как язык я его не приемлю. Посматриваю на плюсы в качестве базы для микросервисов, но видны следующие недостатки:
— нет интроспекции
— пакетные менеджеры не распространены и в тех что есть мало библиотек
— нет некоторых нужных библиотек (GraphQL например)
0
Да, CMake.
Используем скрипты CMake, которые при отсутствии нужных библиотек говорят какие системные команды надо выполнить, чтобы их поставить (например 'No compiler found. Please run `sudo apt install clang++`').
Корутины базируются на библиотеке Boost.Context + lock-free библиотеки + Boost библиотеки для умных указателей и интрузивных контейнеров.

С интроспекцией в C++17 и правда плохо, но для некоторых вещей хорошо подходит библиотека magic_get или constexpr функции с «рукописной помощью» для интроспекции.
0
А почему не взяли Boost.Coroutine2, который тоже на Boost.Context основан, какие у него недостатки по вашему мнению?
+1
Если будете оперсорсить, уберите пожалуйста PascalCase :3

Лучше придерживаться стиля snake_case из std::

Кодстайл это, кажется, одна из основных болей всех проектов, которые хотя�� заопенсорситься.
+1
PascalCase мы менять не будем потому что многие люди его используют, и недолюбливают snake_case. Тут либо одним не угодишь, либо другим :(
+1
Проходил курс от Яндекса на Coursera и столкнулся с совершенно непривычным CodeStyle… Названия функций с больших букв… Ну, тут да, кто как привык… просто, как мне кажется snake_case, гораздо читабельнее для функций и названий внутренних переменных, а PascalCase для названий классов.
+1
Действите��ьно всем не угодишь. snake_case мне тоже не нравится, я люблю camelCase, но мой опрос в плюсовых конфах показал, многие считают, что для публичных библиотек лучше придерживаться стиля стандартной библиотеки.
+1

А какое у вас отношение к stackless coroutines? Не получится ли так что вот у вас есть фреймворк для stackfull, который надо будет переписать на stackless?


Это наверное старые новости, но недавно видел упоминание на stackless vs stackfull у Raymond Chen https://devblogs.microsoft.com/oldnewthing/20191011-00/?p=102989 в котором была ссылка на статью Gor Nishanov http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2018/p1364r0.pdf где вкратце он топит за то что stackless считается более перспективным чем stackfull. В частности он там пишет что Facebook пытается уйти от stackfull по ряду причин и такую же штуке сделали в Rust.

0
У них разные характеристики:
* stackfull позволяют пользователям фреймворка не задумываться о внутренней реализации, о co_await, co_return
* stackless быстрее отменять и они расходуют меньше оперативной памяти

Возможно мы когда-нибудь сделаем возможность пользоваться во фреймворке сразу обоими разновидностями корутин. Но это не приоритетная задача.
Only those users with full accounts are able to leave comments., please.