Comments 16
Ожидал почитать про 3D печать, а пришлось про сишку
А всё-таки STL - это лучшее, что случилось с С++
Трюк с последним нулевым байтом в small fbstring - просто шик и блеск. Аплодирую авторам стоя.
Лично я располагаю своим собственным контейнером, наподобие std::vector, который избегает переаллокаций при push_back, и при этом ещё позволяет произвольный доступ по индексу. Помимо обыкновенного последовательного итерирования от одного элемента к следующему или предыдущему.
Ещё хочу добавить, что подчас нужны immutable строки (как в python). То, что в STL по умолчанию строки изменяемые - подчас ограничивает сферу возможных их применений. Нужно порой отказать пользователям строк в возможности их промодифицировать. Да даже обязательность завершающего нулевого символа тоже бывает накладной. Т.к. тогда не получится выдать пользователю строки указатель на произвольный кусок какого-нибудь буфера, объявив его нашей вот строкой. И тем самым избежать лишнего копирования.
Главное правило оптимизации на С++: берешь стандартную библиотеку, выкидываешь ее, пишешь свой велосипед с багами, называешь его EASTL и заставляешь всех страдать))
Автор, статья — огонь, за поднятую тему кэш-локальности однозначно жирный лайк. Но у меня при разборе кода случился жесткий когнитивный диссонанс.Ты пол-поста справедливо разносишь shared_ptr за то, что он размывает память по куче и заставляет проц ловить cache misses. А потом в приватной секции своего «идеального» класса object_t пишешь:std::shared_ptr self_;Объясни плиз, как так вышло? Мы же синтаксически обернули данные в значение, но физически через make_shared всё так же раскидали фигуры по куче. Проц-то точно так же будет прыгать по адресам памяти, как и в классическом ООП. Где профит для железа, кроме красивой обертки?И второй момент, чисто на подумать. Если нам РЕАЛЬНО нужна была кэш-локальность и скорость, почему вообще не сделать по-другому? Что, если я скажу, что можно полностью решить твою задачу, вообще выкинув сущность Фигуры из кода? Убрать все ящики, интерфейсы и скрытые указатели насовсем, чтобы данные лежали монолитной стеной, а скорость улетела в космос?Интересно, догадается кто-нибудь, как завернуть такой фокус без ООП-головного мозга, или мне сразу бенчмарк с кодом выкатить?
Ладно, не буду томить, давайте сожжём эти чёртовы ящики-абстракции насовсем.Автор пытался лечить ООП костылями, маскируя указатели за красивым синтаксисом. Но если включить Data-Oriented Design (DOD) и начать думать данными, а не объектами, то сущность «Фигуры» вообще не нужна. Компьютеру плевать на классы, ему нужны сырые числа, чтобы быстро их переварить.Вместо того чтобы пихать объекты в массив, я полностью разделяю логику и данные (подход SoA — Structure of Arrays). Все радиусы кругов — в один плотный вектор, все стороны квадратов — во второй. А порядок отрисовки — копеечная шпаргалка из enum.Специально для тех, кто сейчас начнёт писать: «Но там же будут тормоза из-за if-else внутри цикла, и предсказатель переходов проца сойдёт с ума!» — я написал честный микробенчмарк. Генерируем 200 000 фигур в абсолютно случайном порядке (худший сценарий для проца) и сравниваем обёртки автора с моим DOD-кодом.Вот мой код, который можно запустить у себя и проверить: #include #include #include #include #include
// Подход автора (ООП со скрытым shared_ptr) struct Concept { virtual void draw() const = 0; virtual ~Concept() = default; }; struct CircleConcept : Concept { double r; CircleConcept(double radius) : r(radius) {} void draw() const override { volatile double dummy = r * 3.14; } }; struct SquareConcept : Concept { double s; SquareConcept(double side) : s(side) {} void draw() const override { volatile double dummy = s * s; } }; struct AuthorObject { std::shared_ptr self; template AuthorObject(T x) : self(std::make_shared(x)) {} void draw() const { self->draw(); } };
// Мой настоящий DOD подход (Без ящиков вообще) struct DODScene { std::vector circle_radii; std::vector square_sides; enum class Type : char { Circle, Square }; std::vector render_order;
void draw_all() const {
size_t c_idx = 0, s_idx = 0;
for (auto type : render_order) {
if (type == Type::Circle) {
volatile double dummy = circle_radii[c_idx++] * 3.14;
} else {
volatile double dummy = square_sides[s_idx++] * s_idx;
}
}
}
};
int main() { const size_t COUNT = 200’000; std::mt19937 rng(42); std::uniform_int_distribution dist(0, 1);
std::vector<AuthorObject> author_docs;
DODScene dod_scene;
for(size_t i = 0; i < COUNT; ++i) {
if (dist(rng) == 0) {
author_docs.push_back(CircleConcept(5.0));
dod_scene.circle_radii.push_back(5.0);
dod_scene.render_order.push_back(DODScene::Type::Circle);
} else {
author_docs.push_back(SquareConcept(10.0));
dod_scene.square_sides.push_back(10.0);
dod_scene.render_order.push_back(DODScene::Type::Square);
}
}
// Тест кода автора
auto s1 = std::chrono::high_resolution_clock::now();
for (const auto& obj : author_docs) obj.draw();
auto e1 = std::chrono::high_resolution_clock::now();
// Тест моего DOD
auto s2 = std::chrono::high_resolution_clock::now();
dod_scene.draw_all();
auto e2 = std::chrono::high_resolution_clock::now();
auto t_author = std::chrono::duration_cast<std::chrono::microseconds>(e1 - s1).count();
auto t_dod = std::chrono::duration_cast<std::chrono::microseconds>(e2 - s2).count();
std::cout << "Код автора (с обертками): " << t_author << " us.\n";
std::cout << "Мой DOD код (без ящиков): " << t_dod << " us.\n";
std::cout << "Итог: Мой код быстрее в " << (double)t_author / t_dod << " раз(а)!\n";
}
При компиляции с -O3 код без ящиков стабильно в 4–8 раз быстрее варианта из статьи.Почему? Да потому что цена промаха по кэшу (Cache Miss) при прыжке в кучу за shared_ptr — это ~300 тактов процессора. А цена ошибки предсказателя переходов на моём if — всего ~15 тактов. Чистая математика железа побеждает абстракции.А если применить пакетную обработку (Batching) и пустить проц сначала по массиву кругов, а потом квадратов, if вообще исчезнет, и буст улетит за х15.Вот это — реальная борьба за перформанс и кэш-локальность, а не перекладывание указателей в красивые обёртки от Adobe. Шах и мат
Спасибо - интересная и полезная статья. Набор "стандартных STL" упомянутых в статье можно еще дополнить библиотекой Abseil - STL от Google.
Путеводитель по чужим STL