Объединяем отсортированные логи на C++!
Несколько файлов уже содержат записи в порядке времени, но для анализа нужен один общий поток. Соберём его через min-heap, читая из каждого файла только текущую запись.
В этой задаче:
Так объём дополнительной памяти зависит от числа файлов, а не от размера логов.
📣 C++ Ready | #ресурс
Несколько файлов уже содержат записи в порядке времени, но для анализа нужен один общий поток. Соберём его через min-heap, читая из каждого файла только текущую запись.
В этой задаче:
• связываем запись с её файлом
• помещаем в очередь первые записи
• после извлечения подгружаем следующую из того же файла
Так объём дополнительной памяти зависит от числа файлов, а не от размера логов.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍4🔥3
Добавляем место вызова в сообщения об ошибках на C++!
Когда ошибка обнаруживается глубоко внутри общей функции, по одному тексту лога бывает трудно найти её источник. В C++20 std::source_location позволяет получить файл и строку вызова без ручной передачи этих параметров.
Подключаем нужные заголовки:
Добавляем source_location последним параметром со значением по умолчанию:
current() вычисляется в месте вызова функции. Поэтому в лог попадёт файл и строка кода, откуда вызвали logError, а не строка внутри самой функции.
Формируем компактную запись с координатами:
Такой формат легко искать в CI-логах и локальном выводе. При необходимости к нему добавляется имя функции через where.function_name().
Вызываем логгер:
Компилятор подставит место этого вызова автоматически.
📣 C++ Ready | #практика
Когда ошибка обнаруживается глубоко внутри общей функции, по одному тексту лога бывает трудно найти её источник. В C++20 std::source_location позволяет получить файл и строку вызова без ручной передачи этих параметров.
Подключаем нужные заголовки:
#include <iostream>
#include <source_location>
#include <string_view>
Добавляем source_location последним параметром со значением по умолчанию:
void logError(std::string_view message,
std::source_location where =
std::source_location::current()) {
current() вычисляется в месте вызова функции. Поэтому в лог попадёт файл и строка кода, откуда вызвали logError, а не строка внутри самой функции.
Формируем компактную запись с координатами:
std::cerr << where.file_name()
<< ":" << where.line()
<< " " << message << "\n";
}
Такой формат легко искать в CI-логах и локальном выводе. При необходимости к нему добавляется имя функции через where.function_name().
Вызываем логгер:
logError("config file is missing");Компилятор подставит место этого вызова автоматически.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤11👍4🔥4
Как игровой движок распределяет тысячи корутин между потоками?
В статье разбирают shuttle_async, экспериментальный планировщик задач для Vulkan-движка ShuttleEngine. Автор показывает, как локальные очереди и work stealing помогают распределять работу между потоками.
В статье разбирают:
• как устроены локальные деки Chase-Lev и кража задач
• зачем корутинам нужен диспетчер и structured concurrency
• как устроены три режима запуска задач
• что показали замеры на малых, больших и неравномерных задачах
Результаты относятся к конкретному проекту и тестовой машине.
📣 C++ Ready | #статья
В статье разбирают shuttle_async, экспериментальный планировщик задач для Vulkan-движка ShuttleEngine. Автор показывает, как локальные очереди и work stealing помогают распределять работу между потоками.
В статье разбирают:
• как устроены локальные деки Chase-Lev и кража задач
• зачем корутинам нужен диспетчер и structured concurrency
• как устроены три режима запуска задач
• что показали замеры на малых, больших и неравномерных задачах
Результаты относятся к конкретному проекту и тестовой машине.
Продолжай читать на Habr!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8👍2🤝2👎1🔥1
Знаете, почему optional.value_or может выполнить лишнюю работу?
Аргументы функции вычисляются до её вызова. Поэтому fallback внутри value_or запустится даже тогда, когда optional уже содержит значение.
Например, результат загрузки может не понадобиться:
Здесь loadNameFromDisk вызывается заранее, а не только при пустом cachedName. Для чтения файла, запроса к базе или построения объекта это лишняя работа.
Если запасное значение дорого получать, проверяем optional отдельно:
Условный оператор вычисляет только выбранную ветку. Поэтому загрузка выполняется лишь при отсутствии готового значения.
Для дешёвого значения value_or остаётся коротким и удобным:
Ленивое вычисление лучше выражать условием или отдельной функцией.
📣 C++ Ready | #совет
Аргументы функции вычисляются до её вызова. Поэтому fallback внутри value_or запустится даже тогда, когда optional уже содержит значение.
Например, результат загрузки может не понадобиться:
auto name = cachedName.value_or(
loadNameFromDisk());
Здесь loadNameFromDisk вызывается заранее, а не только при пустом cachedName. Для чтения файла, запроса к базе или построения объекта это лишняя работа.
Если запасное значение дорого получать, проверяем optional отдельно:
auto name = cachedName
? *cachedName
: loadNameFromDisk();
Условный оператор вычисляет только выбранную ветку. Поэтому загрузка выполняется лишь при отсутствии готового значения.
Для дешёвого значения value_or остаётся коротким и удобным:
auto port = configuredPort.value_or(8080);
Ленивое вычисление лучше выражать условием или отдельной функцией.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤7🔥3
Объединяем пересекающиеся интервалы на C++!
Дан список занятых временных интервалов. Нужно объединить пересекающиеся и соприкасающиеся отрезки.
Представляем каждый интервал парой начала и конца. Считаем границы полуоткрытыми, поэтому отрезки [2, 5) и [5, 7) тоже объединяются:
Сначала сортируем пары по началу, а при равенстве по концу. После сортировки каждый следующий интервал можно сравнить только с последним результатом:
Если новый интервал начинается позже конца последнего, добавляем его отдельно. Иначе расширяем конец уже собранного интервала до максимальной границы:
Строгое сравнение оставляет соприкасающиеся интервалы в одной группе. Для полуоткрытых границ результат примера будет таким:
После сортировки алгоритм делает один проход и работает за O(n log n), где основное время уходит на сортировку.
📣 C++ Ready | #практика
Дан список занятых временных интервалов. Нужно объединить пересекающиеся и соприкасающиеся отрезки.
Представляем каждый интервал парой начала и конца. Считаем границы полуоткрытыми, поэтому отрезки [2, 5) и [5, 7) тоже объединяются:
#include <algorithm>
#include <utility>
#include <vector>
using Interval = std::pair<int, int>;
std::vector<Interval> busy{
{1, 3}, {2, 5}, {8, 10}, {10, 12}
};
Сначала сортируем пары по началу, а при равенстве по концу. После сортировки каждый следующий интервал можно сравнить только с последним результатом:
std::sort(busy.begin(), busy.end());
std::vector<Interval> merged;
Если новый интервал начинается позже конца последнего, добавляем его отдельно. Иначе расширяем конец уже собранного интервала до максимальной границы:
for (const auto& current : busy) {
if (merged.empty() ||
merged.back().second < current.first) {
merged.push_back(current);
} else {
merged.back().second = std::max(
merged.back().second, current.second);
}
}Строгое сравнение оставляет соприкасающиеся интервалы в одной группе. Для полуоткрытых границ результат примера будет таким:
[1, 5) [8, 12)
После сортировки алгоритм делает один проход и работает за O(n log n), где основное время уходит на сортировку.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8👍4🔥3
Схема протокола acquire-release в C++!
Release-store и acquire-load помогают одному потоку опубликовать подготовленные данные, а другому безопасно увидеть их после сигнала готовности. Синхронизация возникает, когда acquire-load получает значение от release-store того же atomic-объекта.
На картинке показаны оба потока, общий флаг x и порядок операций до и после передачи сигнала. Подпись отдельно подчёркивает важное условие. Обе операции должны использовать один и тот же atomic-флаг.
📣 C++ Ready | #совет
Release-store и acquire-load помогают одному потоку опубликовать подготовленные данные, а другому безопасно увидеть их после сигнала готовности. Синхронизация возникает, когда acquire-load получает значение от release-store того же atomic-объекта.
На картинке показаны оба потока, общий флаг x и порядок операций до и после передачи сигнала. Подпись отдельно подчёркивает важное условие. Обе операции должны использовать один и тот же atomic-флаг.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6❤3🔥3🤝1
Потолок зарплаты в IT — ВСЁ
Даже Senior-разработчики упираются в потолок. Дальше — менеджмент, смена компании или годы ожидания повышения.
Но есть другой путь — зарабатывать больше на уже накопленном IT-опыте. И для этого необязательно переучиваться с нуля.
Как именно, рассказывает Артём Алехин — экс-тимлид Сбера, работавший на валютных удаленках.
Он совмещал российский найм с валютным контрактом, зарабатывая $8–9 тысяч в месяц. Артем создал проект в рамках которого получил для своих 200 учеников 40+ офферов на 200к+.
Три направления, с которыми работает Артём:
— Валютная удалёнка: выход на зарубежный рынок и поиск офферов от $5 000.
— Монетизация экспертизы: превращать свой IT-опыт в доход через обучение и менторство
— Свой IT-продукт: запуск SaaS с проверкой спроса сервиса для селлеров.
Артём помогает выбрать подходящий путь и выстроить стратегию роста дохода, чтобы перестать зависеть от одного работодателя.
Переходи в канал Артёма и изучай: @artem_itishnaya
Даже Senior-разработчики упираются в потолок. Дальше — менеджмент, смена компании или годы ожидания повышения.
Но есть другой путь — зарабатывать больше на уже накопленном IT-опыте. И для этого необязательно переучиваться с нуля.
Как именно, рассказывает Артём Алехин — экс-тимлид Сбера, работавший на валютных удаленках.
Он совмещал российский найм с валютным контрактом, зарабатывая $8–9 тысяч в месяц. Артем создал проект в рамках которого получил для своих 200 учеников 40+ офферов на 200к+.
Три направления, с которыми работает Артём:
— Валютная удалёнка: выход на зарубежный рынок и поиск офферов от $5 000.
— Монетизация экспертизы: превращать свой IT-опыт в доход через обучение и менторство
— Свой IT-продукт: запуск SaaS с проверкой спроса сервиса для селлеров.
Артём помогает выбрать подходящий путь и выстроить стратегию роста дохода, чтобы перестать зависеть от одного работодателя.
Переходи в канал Артёма и изучай: @artem_itishnaya
👎9😁1
Знали, что ключ в std::map можно переименовать без пересоздания узла?
Ключи внутри map нельзя менять напрямую, потому что контейнер поддерживает порядок элементов. Начиная с C++17 элемент можно временно извлечь как node handle, изменить его ключ и вставить обратно без копирования хранимого значения.
Сначала извлекаем элемент и проверяем, что он найден:
Пустой node handle означает, что такого ключа в контейнере не было. Пока узел извлечён, он не участвует в сортировке map, поэтому ключ можно изменить:
Сам объект и его mapped value остаются внутри узла. Это полезно, когда значение дорого копировать или оно не копируется вовсе:
Теперь пробуем вернуть узел и проверяем результат, потому что newKey мог уже появиться в map:
При конфликте запись не теряется, она остаётся в result.node. Можно восстановить старый ключ и вернуть её в контейнер:
Успешная вставка возвращает тот же узел без пересоздания значения. Такой приём удобен для переименования ключей, если важно сохранить объект и явно обработать возможный конфликт.
📣 C++ Ready | #совет
Ключи внутри map нельзя менять напрямую, потому что контейнер поддерживает порядок элементов. Начиная с C++17 элемент можно временно извлечь как node handle, изменить его ключ и вставить обратно без копирования хранимого значения.
Сначала извлекаем элемент и проверяем, что он найден:
auto node = users.extract(oldKey);
if (node.empty()) return;
Пустой node handle означает, что такого ключа в контейнере не было. Пока узел извлечён, он не участвует в сортировке map, поэтому ключ можно изменить:
auto oldKey = node.key();
node.key() = newKey;
Сам объект и его mapped value остаются внутри узла. Это полезно, когда значение дорого копировать или оно не копируется вовсе:
auto& profile = node.mapped();
profile.lastSeen = now;
Теперь пробуем вернуть узел и проверяем результат, потому что newKey мог уже появиться в map:
auto result = users.insert(std::move(node));
if (result.inserted) {
std::cout << "renamed\n";
}
При конфликте запись не теряется, она остаётся в result.node. Можно восстановить старый ключ и вернуть её в контейнер:
if (!result.inserted) {
result.node.key() = oldKey;
users.insert(std::move(result.node));
}Успешная вставка возвращает тот же узел без пересоздания значения. Такой приём удобен для переименования ключей, если важно сохранить объект и явно обработать возможный конфликт.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍4🔥2
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥12❤4👍3
Please open Telegram to view this post
VIEW IN TELEGRAM
😁6👎4
Почему компаратор с <= может сломать std::sort?
При сортировке чисел по возрастанию хочется написать сравнение так:
Проблема проявляется, когда в диапазоне есть одинаковые значения. Для двух равных чисел компаратор возвращает true, хотя ни одно из них не должно стоять раньше другого.
Проверим самую простую ситуацию:
Результат равен 1. Но std::sort требует строгий слабый порядок, элемент не может быть меньше самого себя. Использование такого компаратора нарушает требования алгоритма.
Правильный вариант для возрастания использует строгое сравнение:
Если у объектов несколько полей, добавь запасной критерий. Например, при одинаковом приоритете можно сравнить id:
Так порядок будет предсказуемым и для равных приоритетов. Если же требуется сохранить исходный порядок равных элементов, используй std::stable_sort с корректным строгим компаратором, а не заменяй < на <=.
📣 C++ Ready | #совет
При сортировке чисел по возрастанию хочется написать сравнение так:
std::sort(values.begin(), values.end(),
[](int a, int b) { return a <= b; });
Проблема проявляется, когда в диапазоне есть одинаковые значения. Для двух равных чисел компаратор возвращает true, хотя ни одно из них не должно стоять раньше другого.
Проверим самую простую ситуацию:
auto lessOrEqual = [](int a, int b) { return a <= b; };
std::cout << lessOrEqual(5, 5);Результат равен 1. Но std::sort требует строгий слабый порядок, элемент не может быть меньше самого себя. Использование такого компаратора нарушает требования алгоритма.
Правильный вариант для возрастания использует строгое сравнение:
std::sort(values.begin(), values.end(),
[](int a, int b) { return a < b; });
Если у объектов несколько полей, добавь запасной критерий. Например, при одинаковом приоритете можно сравнить id:
if (a.priority != b.priority) return a.priority < b.priority;
return a.id < b.id;
Так порядок будет предсказуемым и для равных приоритетов. Если же требуется сохранить исходный порядок равных элементов, используй std::stable_sort с корректным строгим компаратором, а не заменяй < на <=.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3👍3🔥2