Немного туп(л,)
84 subscribers
179 photos
26 videos
40 links
Маленький бложег С++ программиста, увлекающегося Rust'ом. Не столько про пргрмрвне, а вообще.
Download Telegram
#truestory #cpp #jobbing

Ну а теперь история, ради которой этот канал был создан, и она про отладку сторонних библиотек.

Мы используем boost. Но мало того, что мы используем boost, мы используем старый boost, а именно версии 1.62, потому что он поставляется с Astra Linux 1.6. А Астра наша приоритетная целевая ОСь. (На самом деле под виндой у нас boost v1.64, но сути это не меняет)

И вот затащили мы некоторое время назад open-license-manager, в составе которого библиотека licensecc и генератор лицензий lccgen. И вот последний тоже зависит от boost'а. Ограничений на версию в CMakeLists.txt не установлено, а в документации проекта сказано использовать >= 1.57, и с нашей старой версией (напомню, что это 1.62) оно в принципе собирается, но только если отключить сборку тестов, но кому эти тесты нужны, правда?
Однако, при запуске, падает во время разбора аргументов командной строки (используется boost::program_options). Опытным путём выяснили, что если подкинуть версию boost'а 1.71, то падения пропадают. Кажется, даже тесты собираются, но это не точно, они же нинужны, поэтому я не перепроверял.

В принципе, мы генератор лицензий заказчикам не отдаём, поэтому собрали это дело только под окошками с новым boost'ом, забив на сертификацию, и всё бы ничего, но вот теперь потребовалось заиметь генератор и под Астру...
Сейчас я вам расскажу про одну интересную оптимизацию. Нашёл её не я, а мой коллега, но интересной от этого она быть не перестаёт.

#jobbing #cpp #prog

Итак, имелся примерно следующий код на С++:
struct MyType;
using std::vector<MyType> = MyVec;

MyVec storage;

void foo(MyVec& new_elements) {
auto new_capacity = storage.size() + new_elements.size();
storage.reserve(new_capacity);

for (const auto& elem : new_elements) {
// do something with elem
storage.push_back(elem);
}
}

И этот код работал медленно. Даже нет, он работал оооооочень медленно. И мы уверены, что проблема именно тут, на эту функцию показали все анализаторы и профилировщики.

Коллега ускорил это дело примерно в 1000 (sic!) раз, удалив одну строчку.
Угадаете какую?

Правильный ответ будет в следующем посте с объяснением, почему так произошло :)
💩1
Преамбула: я начинал эту заметку ещё несколько месяцев назад, но не дописал совсем чуть-чуть в конце. Сейчас вот наткнулся снова и решил дописать. Всё, что ниже - это текст от "прошлого меня", за исключением последних трёх абзацев.
--------------------
У меня бомбит, поэтому сейчас будет эмоциональная история про пргрмвне (история про пргрмрвне на канале про пргрмрвне!)

#jobbing #prog #cpp #hate

Язык С++ довольно странный предмет, он вроде бы есть, но лучше бы его не было. Два С++-разработчика спокойно могут "говорить" на совершенно разных диалектах, в зависимости от того, какому стандарту они отдают предпочтение. При этом многим кажется, что если они хорошо знают один "диалект", то легко и спокойно перейдут на другой, более новый. Так вот, это не совсем так.

Начиная с С++11 существует такая интересная штука, как std::reference_wrapper.
Если в двух словах, то это хреновина, которая позволяет использовать ссылки так, как обычно их использовать нельзя.
Например: класть в std::vector, или переприсваивать именно "значения ссылок", а не "значения куда указывают ссылки" (актуально для шаблонных алгоритмов).

Зачем вообще это нужно? Чтобы избегать ненужного копирования, как минимум.
Например, очень удобно написать что-то такое:
struct Some {
Some() = default;
~Some() = default;
Some(const Some&) = delete;
Some(Some&&) = delete;
Some& operator=(const Some&) = delete;
Some& operator=(Some&&) = delete;
};

Some global_object;
thread_local Some thread_object;

struct A {
Some m_object;

void foo(Some& object) {
std::vector<std::reference_wrapper<Some>> objects;
objects.emplace_back(object);
objects.emplace_back(m_object);
objects.emplace_back(thread_object);
objects.emplace_back(global_object);

for (const auto& o : objects) {
// some process without copying
}
}
};

Тем самым мы можем обработать обработать кучу данных из разных источников без необходимости их копирования; накладные расходы будут только на внутряночку std::reference_wrapper (а это обычный указатель) и на сам std::vector.

И всё было бы хорошо, если бы оно было хорошо, но, к сожалению, оно не хорошо.
Видя, какое удобство и магию предоставляет этот std::reference_wrapper, люди забывают, что и обращаться с ним нужно так же, как с обычной ссылкой, под которую он мимикрирует.

Вот буквально сегодня я увидел примерно вот такое:
struct Context {
std::optional<std::reference_wrapper<std::string>> m_id = std::nullopt;
}

Context createContext() {
Context ctx;
std::string guid = generateGUID();
ctx.m_id = guid;
return ctx;
}

И всё, пиздец котёнку. Успело попасть в релизную ветку, благо на тестировании стали ловить странные падения.

Как программист вообще мог такое написать и так облажаться? Очень просто: это было два программиста.
Первый - внедрил использование std::optional<std::reference_wrapper<std::string>>, второй не понял что такое этот ваш std::reference_wrapper и поместил туда объект с меньшим временем жизни, чем контекст.