Обновить

Отсекая лишнее: как сократить бинарный код программы на C++ и не потерять нужную функциональность

Уровень сложностиСредний
Время на прочтение12 мин
Охват и читатели14K
Всего голосов 44: ↑44 и ↓0+57
Комментарии34

Комментарии 34

Статья интересная (без шуток) - спасибо!

Но все таки - как уменьшить кодовую базу? Потому что, например, ваш пример с дешаблонизацией кодовую базу только увеличит.

там если уходить от виртуал не получается нужен общий конструктор и ситуации всякие

https://godbolt.org/z/jc5vPaPW6 ну у меня так получилось

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

Я понимаю разницу. Автор поменял название статьи - в оригинале было - как уменьшить кодовую базу.

В текущем названии вопросов нет.

НЛО прилетело и опубликовало эту надпись здесь
if (std::holds_alternative<std::string>(some_variant)) {
    auto v = *std::get_if<std::string>(&some_variant);
    // какие-то действия с v
}

Зачем тут holds_alternative?

Используя holds_alternative проще и быстрее сразу проверить: содержит ли some_variant значение типа строки (без извлечения этого значения), чем сразу с помощью std::get_if извлекать из варианта нечто и для неудачных исходов обрабатывать nullptr.

Или у вас другое мнение?

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

Вас понял. Я же не для спора спросил, сам разберусь со временем.

Думаю, можно ограничиться чтением исходного кода holds_alternative и get_if, что в общей сумме не превышает 10 строк.

Вам специально для таких случаев дали новую форму оператора if:

if (auto ptr = std::get_if<std::string>(&some_variant)) {
    // какие-то действия с *ptr
}

Всё вполне читаемо, но на одну строчку меньше и быстрее. Если не хочется ставить кучу "звёздочек" - можно добавить auto v = *ptr;, строчек будет столько же, но кода меньше.

Чтобы убедиться, что в варианте лежит строка

Все равно при вызове push_back будет проверка: не переполнился ли вектор, не надо ли снова аллоцировать. Поэтому тут мы остановились на варианте с std::vector<std::unique_ptr> без резервации.

Но почему push_back?) C emplace_back было бы компактнее))

    std::vector<std::unique_ptr<I>> v;
    v.emplace_back(new A);
    v.emplace_back(new B);

https://godbolt.org/z/bEdx6hn4G

Если я правильно помню, это утечка памяти, если emplace_back кинет исключение

Так все конструкторы у unique_ptr - они noexcept.

Ну, есть вариант, что контейнер память не сможет выделить, то так ли стоит переживать об утечках, если память кончилась?)

нет, вам правильно сказали. Если не получится выделить память при reserve в векторе, то вылетит исключение и указатель просто повиснет

Как я и сказал - это наименьшая из проблем

А вот и нет :) Я сходил в стандарт (см. раздел util.smartptr.shared.const)

template<class Y> explicit shared_ptr(Y* p);

...

If an exception is thrown, delete p is called
when T is not an array type, delete[] p otherwise

И

template<class Y, class D> shared_ptr(Y* p, D d);

...

If an exception is thrown, d(p)
is called.

Начиная с С++17 такое поведение гарантированно стандартом.

UPD

Прошу прощения, забыл что речь шла про unique_ptr. Но гарантии там те же.

Какое вообще отношение имеют цитированные вами фрагменты к обсуждаемой проблеме?

Проблема в том, что при использовании emplace операция reserve делается до вызова конструктора. А без вызова конструктора unique_ptr деструктор тоже никто не вызовет.

emplace_back - шаблонная функция, поэтому на каждый тип будет генерироваться новая функция.

Или не будет, если её удастся заинлайнить. Что весьма вероятно, если учесть, что вся шаблонность там заключается в том, чтобы передать аргументы в соответствующий конструктор.

И правда, хотя не все компиляторы такие умные (осуждающе смотрю на MSVC).

Выкидывание лишнего барахла, которое GNU-шный компилятор по одним ему ведомым причинам оставляет в исполняемом файле - дело хорошее. Но вот менять сам код, чтобы сэкономить какие-то крохи размера файла - сомнительная затея. Идиоматичность кода должна быть важнее его итогового размера. Да и сколько так можно сэкономить в итоге? Десять килобайт на мегабайт исполняемого файла? Стоит ли овчинка выделки?

Стоит ли овчинка выделки?

В случае из статьи, у вас нет выбора. На старых устройствах просто не хватает памяти для новых прошивок. И поэтому приходится оптимизировать, что бы хоть как-то обновлять старые устройства.

Ответ на ваш вопрос в самом начале статьи:

На старых платформах уже нет возможности расширить накопитель

Овчинка стоит выделки, когда альтернатива - это сказать заказчику, что его дорогое железо больше не будет получать обновления

Полезный разбор полезный, но хотелось бы увидеть больше цифр. Например, "включили флаг -Os - выиграли 200 КБ. Заменили std::visit - еще 50 КБ. Перешли с shared_ptr на unique_ptr - 30 КБ". Без этого сложно оценить реальный вклад каждого из методов

Хозяйке на заметку:

Из интервью с автором Total Commander - Кристианом Гислером. 2011 год
-- Широко известный факт, что вы до сих пор пишете свой файл-менеджер Total Commander на "допотопном" Delphi 2. С чем это связано?
-- Я являюсь обладателем лицензионных версий всех последних Delphi, поэтому достаточно хорошо представляю себе их возможности. Но дело тут вот в чем: компиляция exe-файла в Delphi 2 дает на выходе файл ощутимо меньший по размеру, чем, например, в Delphi 7. Кроме того, тестирование показывает, что exe-шник из-под Delphi 2 работает заметно быстрее, чем его полный аналог, выпущенный компилятором Delphi 7. Я сталкиваюсь с тем, когда люди часто удивляются, что Total по-прежнему работает очень быстро - я собираюсь сохранить эту его особенность, и, отчасти, секрет тут в правильно выбранном компиляторе.
...разработка 32-битной версии TC останется на Delphi 2.
...Добавлю, что кроме этого Delphi 2 генерирует очень универсальный код, например, с полной поддержкой 16-битных приложений или Windows 95/98 - у меня до сих пор хватает таких клиентов.

пора, брат, пора.

UPD 2019: Для 64-битный билдов они перешли на Lazarus, но для 32-бит всё ещё на старом Delphi, потому что делает компактные .exe.

Я у себя столкнулся с такой особенностью виртаульных таблиц.
У меня был код, который использовал базовый класс как интерфейс, но сами реализации не хранились в одном месте через указатель на этот базовый класс. Но сам базовый класс имел виртуальный деструктор, как советуют core гайды.
Удаление этого деструктора, сэкономило мне около 10К в бинарнике.
Утечек нет, потому что не используется владение через указатель на этот базовый класс и деструктор вызывается всегда сразу у потомка.

базовый класс имел виртуальный деструктор, как советуют core гайды

Без виртуального деструктора будет неопределенное поведение, если будет вызван деструктор через базовый класс. Как вы от этого защитились? Сегодня не используется владение через указатель на базовый класс, а завтра появится.

Сегодня не используется владение через указатель на базовый класс

Именно так
Ну и как тут уже упоминали

В embedded мире часто приходится жертвовать идиоматичностью и красотой кода ради соответствия жестким ограничениям по памяти или производительности

Поменяется код придется руками исправлять, забудешь исправить - сам дурак. Это же c++.

Для запуска Bloaty с отладочным файлом используется флаг --debug-file.

bloaty --debug-file=/path/to/debug_symbols_file /path/to/my_stripped_executable


А есть инструкция как этот правильно применяется? Допустим собрали вы свой проект с описанными флагами для оптимизации включая --strip-all, с gcc на линукс с cmake. Где искать `/path/to/debug_symbols_file` ?

Есть ли возможность помочь с этим вопросом?

https://habr.com/ru/companies/timeweb/articles/793152/comments/#comment_29169604

Как сделать так, чтобы между секциями isr_vector и text не было заполнения?

.isr_vector     0x08000000      0x194
                0x08000000                isr_vector_start = .
                0x08000000                . = ALIGN (0x4)
 *(.isr_vector)
 .isr_vector    0x08000000      0x194 build/startup_stm32f401xe.o
                0x08000000                g_pfnVectors
                0x08000194                isr_vector_end = .
                0x08000194                . = (ADDR (.isr_vector) + SIZEOF (.isr_vector))

.text           0x080001c0    0x39758
                0x080001c0                text_start = .
                0x080001c0                . = ALIGN (0x4)

У меня сейчас 0x080001c0-0x08000194 = 44 байта впустую зря пропадают.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Информация

Сайт
yadro.com
Дата регистрации
Дата основания
Численность
5 001–10 000 человек
Местоположение
Россия
Представитель
Ульяна Соловьева