Охват к подписчикам
23,6%
ERR
Реакции к просмотрам
1,44%
2 765 на 37 постов
Пересылки к просмотрам
0,00%
1
Постов в день
0,1
всего 37
Где отзываются чаще
доля реакций к просмотрам- 2 маябез подписи4,10%
- 5 янв.без подписи3,01%
- 23 июл.Сколько потоков можно запустить на одной машине? #опытным Из всех утюгов говорят про асинхронность вычислений и экономию ресурсов ОС. Зачем это нужно? И почему я не могу на каждую задачу запускать отдельный поток? Есть ли вообще какой-нибудь предел по количеству потоков? Потоки - вещь хорошая и полезная. Не зря же недавно в питон таки завезли возможность их прямого использования. Но с потоками нужно быть аккуратным. Это ресурс. А ресурсы имеют свойство заканчиваться, когда их нерационально используют. Классический пример: к вам в приложение приходит какой-то запрос на обработку. Первая мысль - а давайте под него выделим свой поток! Они же независимо и параллельно исполняются! Ну и уже здесь мы поймали первую ошибку. Да, потоки могут параллельно исполняться. Но количество реально параллельно исполняемых потоков в системе ограничено. И ограничено оно количеством вычислительных юнитов процессора. Максимальное число реально параллельных потоков можно узнать с помощью std::thread::hardware_concurrency(). Есть кстати технология HyperThreading от Intel, которая позволяет на одном физическом ядре получить 2 логических, увеличивая таким образом число потенциально параллельных потоков в 2 раза. Ускорения в 2 раза конечно не будет, но тем не менее. "Ну хорошо. Не все 100500 потоков, которые я создал, исполняются параллельно. Не всегда нужно параллельное одновременное исполнение всех тредов. Почему всем не нравится много потоков? Поток - это ресурс, который занимает место и им нужно управлять. Как минимум под каждый поток выделяется свой стек. На десктопах это 1-8 МБ. Поделите количество оперативной памяти на размер стека и получите максимально возможное количество потоков, которое в принципе может поместиться в память. В реальности количество будет еще меньше. Есть же еще затраты на управление потоками. Их же нужно переключать и выдавать каждому подходящий квант времени. Переключает потоки шедулер ядра и на это тоже тратится процессорное время. С ростом количества потоков можно прийти к ситуации, когда затраты на управление потоками будут превышать количество полезной работы, совершаемой потоками. Ну и в некоторых системах явно прописано ограничение на количество потоков. В линуксе, например, можно увидеть заветную числеку с помощью команды cat /proc/sys/kernel/threads-max. Асинхронность - это в первую очередь про асинхронный ввод-вывод. Тут все завязано на событийно ориентированное апи системы и файловые дискрипторы, которых фактически может быть гораздо больше, чем количество потоков. Если у вас есть сервис, который активно работает с сетью - без асинхронности не обойтись. Подход с созданием отдельного потока на каждый запрос перестанет адекватно масштабироваться уже после сотни другой одновременных подключений.2,53%
- 7 июл.Засовываем исключение в исключение #опытным Вы знали, что в плюсах есть вложенные исключения? Такие исключения могут хранить в себе несколько исключений. Сегодня мы посмотрим, что это за зверь такой. Начнем с применения. В прошлом посте мы создавали новое исключение на основе строки ошибки обрабатываемого исключения. В этом случае нужно писать определенное количество бойлерплейта и теряется информация о типе изначального исключения. Чтобы избежать этих проблем, мы можем бросить новое исключение, которое будет в себе содержать старое: void ComplicatedCalculations() try { // use db } catch (std::exception& ex) { std::throw_with_nested(std::runtime_error("Complicated Calculations Error")); } void HandlingCalculations() try { ComplicatedCalculations(); } catch (std::exception& ex) { std::throw_with_nested(std::runtime_error("Handling Calculations Error")); } Теперь исключение, которое вылетит из HandlingCalculations будет на самом деле содержать 3 исключения: от базы данных, от ComplicatedCalculations и от HandlingCalculations. Вложенные исключения существуют с С++11 и очень интересно устроены. Рассмотрим несколько упрощенные версии сущностей, которые находятся под капотом механизма вложенных исключений. Есть класс std::nested_exception: class nested_exception { exception_ptr _M_ptr; public: /// The default constructor stores the current exception (if any). nested_exception() noexcept : _M_ptr(current_exception()) { } ... }; Этот класс ответственен за захват текущего исключения с помощью вызова std::current_exception(). Дальше имеется класс, который хранит в себе все множество исключений: template<typename Except> struct Nested_exception : public Except, public nested_exception { explicit Nested_exception(const Except& ex) : Except(ex) { } }; Объекты этого класса наследуются от nested_exception, в котором захвачено старое исключение, и от Except - нового исключения. Ну и последний компонент - std::throw_with_nested: template<typename Tp> [[noreturn]] inline void throw_with_nested(Tp&& t) { throw Nested_exception<remove_cvref_t<Tp>>{std::forward<Tp>(t)}; } При вызове throw_with_nested создается объект Nested_exception на основе переданного типа исключения и, неявно, nested_exception, которых сохраняет в себе указатель на старое исключение. Получается, что мы при каждом вызове throw_with_nested подмешиваем новое исключение к старому с помощью множественного наследования. Очень прикольная техника, которая позволяет строить цепочки объектов. Это как тупл, только расширяемый в рантайме. Это все хорошо и интересно. Прокидывать вложенные исключения мы научились. Но рано или поздно их придется обработать. Как это сделать? Об этом будем говорить в следующем посте. Inherit knowledge from your ancestor. Stay cool.2,27%
- 24 янв.Все вызываемые сущности в С++. Ч1 #новичкам В С++ полно вещей, которые можно вызывать, как функции. Но, как мы убедились в прошлый раз, не только функции в плюсах можно "вызывать". Хотя указатели на поля класса - это единственное такое исключение, давайте перечислим все вызываемые сущности в С++. Поехали! 👉🏿 Функции. Старые-добрые и всем знакомые функции. Очевидный кандидат: void free_function(int a) { std::cout << "Free function: " << a << std::endl; } free_function(42); 👉🏿 Указатели на функцию Если вы хотите куда-то передать функцию, как коллбэк, то скорее всего стриггерите неявное приведение имени функции к указателю на функцию. Вот так например: void free_function(int a) { std::cout << "Free function: " << a << std::endl; } std::invoke(free_function, 42); Внутрь invoke free_function передастся как именно как указатель. Указатель на функцию можно и явно определить и вызвать с его помощью соответствующей функции: auto fun_ptr = &free_function; fun_ptr(42); (*fun_ptr)(42); Когда мы просто передаем имя функции в call_callback, это имя приводится к типу указателя на функцию: void free_function(int a) { std::println("Free function: {}", a); } call_callback(free_function, 42); 👉🏿 Ссылки на функции Да, и такие тоже есть: auto& fun_ref = free_function; fun_ref(42); (*fun_ref)(42); // интересно, что такой синтаксис разрешен Если передать в функцию в качестве параметра по ссылке, то тогда тоже получите ссылку на функцию: void check_name(auto& obj) { // by passing free_function to check_name, obj become function reference } check_name(free_function); 👉🏿 Статические методы классов struct Handler { static void static_method(int a) { std::cout << "Static method: " << a << std::endl; } }; Handler::static_method(42); Также мы можем явно определить указатель или ссылку на статический метод класса, так как это обычная функция: auto static_method_ptr = &Handler::static_method; std::invoke(static_method_ptr, 42); 👉🏿 Функциональные объекты В обывательском понимании функторы - это объекты классов с определенным оператором(). Однако не все так просто. На самом деле с точки стандарта функциональный объект - это одно из 3-х: указатель на функцию, объект класса с определенным оператором() и объект класса с определенным оператором приведения к указателю на функцию. Их всех объединяет одинаковый синтаксис вызова, поэтому их засунули в одну категорию. Указатели на функции мы разобрали, рассмотрим 2 оставшихся. 👉🏿👉🏿 Объект класса с определенным вызова operator(). Это такие стандартные олдскульные(до С++11) функторы, которые использовались, например, для передачи в стандартные алгоритмы. Название, в общем, полностью описывает реализацию. Единственное, что хочется отметить - необходимость таких функторов заключена в их способности хранить стейт, иначе можно было использовать обычные функции. class SequenceGenerator { int current; int step; public: SequenceGenerator(int start = 0, int step_size = 1) : current(start), step(step_size) {} // Оператор вызова без аргументов - возвращает следующее число int operator()() { int val = current; current += step; return val; } }; SequenceGenerator gen; std::vector<int> numbers(5); std::generate(numbers.begin(), numbers.end(), gen); // заполняем вектор с помощью функтора Уже много получилось, все в один пост не влезет. Поэтому в следующих раз будет продолжение. Have all the tools. Stay cool.2,24%
- 30 июн.std::to_address #опытным В этом посте мы поговорили о том, как доставать настоящий адрес объекта с помощью функции std:addressof. В основном она предназначена для получения настоящего адреса любых объектов, даже тех, у кого перегружен оператор взятия адреса. Однако есть и другая, похожая задача. Вам приходит на вход объект, который представляет из себя какого-то рода указатель на объект и из него нужно получить адрес самого объекта. Дело это не совсем тривиальное. Со всеми стандартными классами, типа умных указателей и итераторов(которые называют общим выражением fancy pointer) может прокатить вот такое выражение: obj.operator->(). Однако для простых указателей это не прокатит: они не классы и у них нет методов. Да и не прокатит для любых других объектов, у которых не определен этот оператор. Что делать? Использовать C++20 функцию std::to_address! Вот ее примерная реализация: template<class T> constexpr T* to_address(T* p) noexcept { static_assert(!std::is_function_v<T>); return p; } template<class T> constexpr auto to_address(const T& p) noexcept { if constexpr (requires{ std::pointer_traits<T>::to_address(p); }) return std::pointer_traits<T>::to_address(p); else return std::to_address(p.operator->()); } То есть для указателей она просто вовращает их значения наружу, а для объектов fancy pointer'ов она спрашивает, определено ли свойство std::pointer_traits для этих типов. Если же не определено, то пытается достать указатель с помощью вызова метода operator->(). Обычно эта функция требуется для вызова сишного апи в обобщенном коде: void c_api_func(const int*); template<typename T> void call_c_api_func(T && obj) { c_api_func(std::to_address(obj)); } std::vector<int> data{10, 20, 30}; call_c_api_func(data.begin()); // works auto ptr = std::make_unique<int>(42); call_c_api_func(ptr); // works call_c_api_func(ptr.get()); // also works Use the right tool. Stay cool.2,22%
- 11 маябез подписи2,16%
- 14 маябез подписи2,09%
- 6 маябез подписи2,08%
- 8 маябез подписи2,07%
- 8 июл.std::unordered_map::emplace_hint() std::unordered_map::emplace_hint() позволяет вставлять элементы в хеш-таблицу с подсказкой для оптимизации. Это особенно полезно, если известно, куда примерно должен встать новый элемент, ускоряя операцию вставки.2,07%
- 4 июл.📂 Concurrency и parallelism - это не одно и то же! Concurrency - это про структуру программы: несколько задач могут продвигаться вперёд в перекрывающиеся промежутки времени. Parallelism - это уже физическое одновременное выполнение, например на нескольких ядрах CPU. На картинке — визуальное отличие concurrent execution от parallel execution. Сохрани, чтобы не потерять!2,02%