LINUX.ORG.RU

C++ — туда и обратно, или зачем нужен Boost

 , , ,


3

5

Мой предыдущий тред в Development собрал самое большое число ответов аж с сентября, то есть за последние 9 месяцев, и это лишний раз подтверждает упадок этого форума. Полагаю, кто-то должен это изменить, и поэтому мы с тобой, ЛОР, поговорим сегодня про C++.

Начиная с C++26 вместо std::function вводится пачка новых классов: std::copyable_function, std::move_only_function (доступна с C++23) и std::function_ref. Что же не так с оригинальным std::function, ты можешь спросить? А вот что:

#include <functional>
#include <print>

struct call_me {
    int x = 0;
    void operator()() {
        std::print("x was {}\n", x++);
    }
};

int main() {
    const std::function<void()> f = call_me{};
    f();
}

Несмотря на то, что переменная f объявлена константной (люблю оксюмороны!), у неё есть внутреннее состояние и оно меняется при вызовах. Компилятор это без проблем хавает.

Так вот, ковыряясь в том, зачем и кто вообще смог так насрать себе в штаны сам, я наткнулся на статью ребят, которые подводят список подобных косяков комитета C++, когда фичи живут по 10 лет и объявляются устаревшими.

Небольшой список фич, которые были придуманы, оказались не нужны/бесполезны/вредны и выкинуты:

  • Известный vector<bool>, живущий издревле в STL и про который все говорят, что его надо избегать. Частично заменяется std::bitset.
  • std::auto_ptr. Бесполезен, ломает контейнеры, выкинут на помойку в C++17.
  • Указание исключений у функции в формате throw(X, Y). Так же выкинуто в C++17.
  • std::iterator объявили устаревшим в C++17, собираются удалить в C++26.
  • std::aligned_storage и std::aligned_union добавлены в C++11, объявлены устаревшими в C++23, скоро удалят.
  • Ключевое слово register удалено в C++17, хотя всё ещё доступно в Си.
  • std::get_temporary_buffer и std::raw_storage_iterator удалены в C++20.
  • Потрясающее по эпичности фиаско с интерфейсом для сборщиков мусора. std::declare_reachable сотоварищи были добавлены в C++11. Выяснилось, что сборщики мусора для C++ писать либо никто не умеет, либо никто не хочет, поэтому в C++23 это всё удалили и сделали вид, что ничего не было.
  • Абсолютное безумие вокруг концептов, модулей и поддержки сети. Предложения одобряли, вновь отклоняли, переделывали, и по итогу теми же модулями до сих пор никто не пользуется.
  • Сопрограммы (coroutines). В том виде, в котором они есть в C++, это просто ужас. Достаточно того, что корутины требуют выделения памяти из кучи во время работы, а значит вообще не подходят для случаях, когда требуется серьёзная производительность. Например, в любом коде, требующим работы в реальном времени и не позволяющем делать системные вызовы.

Просто лютый трешак, который никто подчищать пока не собирается:

  • std::regex – лютый тормоз, рекомендуется не использовать.
  • Мертворождённый std::simd, добавленный в C++26 и уже с ходу не нужный вообще никому, потому что код с std::simd в два-три раза тормознее чем со сторонними библиотеками, просто голыми интринсиками, и даже медленнее чем просто цикл for.
  • std::async. Дескруктор ждёт завершения асинка и поэтому может залочить весь код тебе. Наконец починили в C++26, но эта штука была сломана 15 лет.
  • Отвратительно спроектированный <iostream>. Его наконец можно выкинуть и использовать std::print, но не все про это знают.
  • Абсолютно тормозные контейнеры map, set, unordered_map. Вместо первого можно использовать flat_map из C++23. Контейнеры в стандартной библиотеке Rust (BTreeMap) и другие реализации B-Tree Map их обгоняют по производительности, но тем не менее в C++ выбирают убогий дефолт.

Решения многих из этих проблем существуют в Boost и сделаны там гораздо лучше. Но в то же время возникает важный вопрос: зачем вообще нужен настолько плохо спроектированный язык, где каждое следующее поколение инженеров, работающих над ним, отменяет решения предыдущих, а код под новые стандарты часто нужно переписывать если не с нуля, то очень близко к тому? Даже процесс разработки Rust с его поехавшими клоунами в юбках на этом фоне выглядит адекватным.

В общем, всё печально, ЛОР. Такие дела.



Последнее исправление: CrX (всего исправлений: 3)
Ответ на: комментарий от yorshka

ChkTag пока существует только на бумаге

Пройди дальше по ссылкам, балда.

Так какая модель процессора-то? Или ты, как обычно, назвиздел с три короба, а когда тебя поймали за руку начал судорожно тащить левые ссылки и рассказывать сказки про древние времена царя гороха?

r--r--r--
()
Ответ на: комментарий от r--r--r--

Так какая модель процессора-то?

Intel Core Ultra 200S, линейка Xeon 6 и т.д. Но, повторю ещё раз, pointer tagging используется буквально повсеместно. Включая линуксовое ядро.

Аналогичная штука на ARM64. Прямо сегодня в твоём телефоне, чувак, если он не старше пары лет. В моём пикселе точно есть.

https://www.kernel.org/doc/html/v5.15/arm64/memory-tagging-extension.html

yorshka
() автор топика
Ответ на: комментарий от yorshka

Если ты не хранишь гигабайты прямо внутри элементов вектора, на количество байт просто посрать тут.

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

универсального контейнера

если уж на то пошло, то универсальным контейнером может быть список. Он медленный, но не перемещает элементы в памяти и имеет стабильную производительность. Вектор же будет создавать случайные неожиданные просадки в любом случае. Он был бы полезен для линейных структур, но в их случае как раз нужно то, о чём я говорю - realloc и ручная обработка внутренних ссылок (если они вообще там окажутся)

mittorn ★★★★★
()
Ответ на: комментарий от yorshka

И что, популярен стал этот твой LAM? И чего ты съехал на LAM и Pointer Tagging? Как это связано с поломкой обратной совместимости ABI? Наоборот, не сделали новое несовместимое ABI, а сделали обратно совместимое послабление касательно битовой репрезентации указателя.

shdown ★★
()
Последнее исправление: shdown (всего исправлений: 2)
Ответ на: комментарий от eao197

Т.е. если у вас был вектор, который умещался в 32 байта, а затем вы хотите увеличить его объем вдвое, то скорее всего вам выдадут новый блок в 64 байта из другого пула. А когда с 64 будете увеличивать до 128 – то новый блок из совсем другого пула. И realloc здесь может помогать разве что пока у вас в векторе 1-2-3 значения совсем маленького размера (по 1-2 байта на каждое).

Это имеет смысл при использовании конкретного аллокатора и больших размеров элементов. И даже в этом случае нагрузка от копирования будет значительной.
Однако, в случае последовательного наполнения вектора элементами без известной заранее длины (а иначе можно было бы и без него обойтись) realloc буквально превратит расширение вектора в noop пока есть возможность расширить пул

mittorn ★★★★★
()
Ответ на: комментарий от shdown

А ещё можно придумать новое ABI, где указатели передаются вместе с границами!

И чего ты съехал на LAM и Pointer Tagging? Как это связано с поломкой обратной совместимости ABI?

Про поломку ты потом добавил. Оказалось, ломать не обязательно, когда есть пустые биты.

yorshka
() автор топика
Ответ на: комментарий от PPP328

Ага, и получаете либо переполняющуюся очередь и ООМ либо дроп самых важных сообщений с причиной падения программы, потому что запись в лог асинхронная и причины падения не записались

Если, например, использовать rigtorp::MPMCQueue, то она при переполнении будет лочиться на атомарном спинлоке, просто сводя на нет всю асинхронность. Это конечно плохо, но при должном упорстве вообще любой логгер захлебнется. Универсального решения тут нет, можно разве что поиграть с размером очереди в зависимости от ожидаемой нагрузки. По части падений: гарантированной записи последнего сообщения при падении в асинхронный логгер добиться сложно, хотя на практике часто получается вывести хвост очереди (и даже стектрейс) из обработчиков сигналов / std::terminate. Да, внутри обработчика есть ограничения на безопасные действия, но некоторые трюки всё же работают.

Ну или предложите свою схему, которая по вашему мнению лучше.

m0rph ★★★★★
()
Ответ на: комментарий от mittorn

Однако, в случае последовательного наполнения вектора элементами без известной заранее длины (а иначе можно было бы и без него обойтись) realloc буквально превратит расширение вектора в noop пока есть возможность расширить пул

Разве что в идеальных условиях однопоточной программы, которая не делает ничего кроме заполнения единственного std::vector<int>. Уже на std::vector<std::string> эта картина мира рухнет. Не говоря уже про многопоточку. И не затрагивая случаев, когда для всего приложения в качестве штатного аллокатора задействовали mimalloc/tcmalloc/jemalloc.

eao197 ★★★★★
()
Ответ на: комментарий от yorshka

ChkTag пока существует только на бумаге

Пройди дальше по ссылкам, балда.

Так какая модель процессора-то?

Intel Core Ultra 200S,

Но эта модель процессоров НЕ поддерживает ChkTag. В даташите есть всё, что угодно - от теневого стэка до шифрования памяти, но ChkTag в нём нет.

Стало быть, ты нагло наврал.

r--r--r--
()
Последнее исправление: r--r--r-- (всего исправлений: 1)
Ответ на: комментарий от eao197

которая не делает ничего кроме заполнения единственного

А что ей ещё делать? Семечки щелкать? не, ну если на каждый чих обращаться к куче - конечно смысл потеряется.
Да и многопоиочная программа наиболее вероятно будет спать в других потоках. Случай когда 2 потока одновременно что-то аллрцируют не такой уж и частый,

на std::vector<std::string>

Отличный пример, как делать не нужно, как минимум по этой причине. Заводите список и не страдайте фигнёй. Ладно бы ещё требовалось непрерывное последовательное расположение элементов, но тут в этом смысла нет...

mittorn ★★★★★
()
Ответ на: комментарий от mittorn

А что ей ещё делать?

Простите, а мы сейчас говорим о сферической программе в вакууме?

Или о чем-то практическом? Например, о микросервисе, написанном на C++ и общающемся с внешним миром через REST.

Да и многопоиочная программа наиболее вероятно будет спать в других потоках.

Ох, блин.

Случай когда 2 потока одновременно что-то аллрцируют не такой уж и частый,

Ох, блин. Два раза.

Заводите список и не страдайте фигнёй.

В этих наших интернетиках пишут, что std::list практически всегда хуже, чем std::vector. Тот же Страуструп об этом чуть ли не с конца 1990-х говорит.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Если там разные потоки что-то постоянно переаллоцируют в одном регионе кучи - то и без std::vector с произврдительностью будут беды.
Если большую часть временипоток проводит аллоцируя память, а не работая с данными - то здесь вероятно какие-то проблемы (неправильный аллокатор, неправильно выбранный storage для данных, неправильная архитектура и т.д)
программа доожна стремиьься быстро аллоцировать всё что нужно. Быстро обработать запрос (но работа с данными может идти долго и параллельно), а не дрочить аллокатор конкурентно из разных потоков

Например, о микросервисе, написанном на C++ и общающемся с внешним миром через REST.

И он боольшую часть времени должен заниматься аллокацией объектов? Или всё-таки обработкой запросов?

std::list

Никогда не смотрел что это такое. Как-то не приходило в голову искать в STL списочные структуры, но наверно там что-то ужасное...

mittorn ★★★★★
()
Ответ на: комментарий от r--r--r--

Здесь кстати realloc бы сработал если содержимое будет аллоцироваться в отдельной куче/пулле и вектор только хранит указатели. В этом случае решаются проблемы и внутренних и внешних ссылок. Если предствить что аллокатор максимально ткпой и использует только morecore/brk - смысл в realloc теряется

mittorn ★★★★★
()
Ответ на: комментарий от mittorn

Если там разные потоки что-то постоянно переаллоцируют в одном регионе кучи - то и без std::vector с произврдительностью будут беды.

У вас стандартный аллокатор, стандартные треды и стандартный std::vector. Как вы собираетесь контролировать в одном регионе кучи треды что-то делают или в разных?

Ручек для управления нет.

Если вы меняете штатный аллокатор на какой-то готовый продвинутый (из числа упомянутых выше), то от realloc-а у вас вообще выхлопа не будет.

программа доожна стремиьься быстро аллоцировать всё что нужно

Это из категории лучше быть богатым и здоровым чем бедным и больным.

И он боольшую часть времени должен заниматься аллокацией объектов? Или всё-таки обработкой запросов?

А у вас обработка – это что-то, что не нуждается в выделении памяти от слова совсем?

Распарсить заголовки запроса в которых будут параметры для работы, распарсить тело запроса, создать ответное тело (например в JSON-е) – тут у вас будет куча аллокаций.

Как-то не приходило в голову искать в STL списочные структуры

А откуда список возьмется? Нужно написать вручную свой?

eao197 ★★★★★
()
  • Markdown
Пустая строка (два раза Enter) начинает новый абзац. Знак '>' в начале абзаца выделяет абзац курсивом цитирования.
Внимание: прочитайте описание разметки Markdown.
Используйте Ctrl-Enter для размещения комментария