LINUX.ORG.RU

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

 , , ,


3

4

Мой предыдущий тред в 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)
Ответ на: комментарий от anonymous

нужно знать её размер, не доверяя вводу, тоесть если она 4 она должна быть 4.

Это как?
Если пользователь интерактивно вводит данные, то API или некий алгоритм программы должен вести подсчёт количества введенных символов.
Подробнее расскажите о задаче приводящий к такой проблеме.

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

ну тоесть у вас всё получается с std::strlen() хотябы? напишите подробнее как вы находите \0 интересно просто

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

ну тоесть у вас всё получается с std::strlen() хотябы? напишите подробнее как вы находите \0 интересно просто

std, STL, … не использую.
Своё API для работы с строками, …
Даже не знаю что вам ответить.
Не могу даже придумать приведённую вами проблему.
Скорее всего она у вас возникает от какого-то алгоритма/задачи.
Поэтому и попросил рассказать о задаче.
Если вы просто рассматриваете проблему выявления размера некоего буфера, то это реверсеинжиринг.
А просто анализируюя некую область памяти с приведенными вами буферами, вы не сможете определить размер этих буферов.
Точное можно, но это уже задача декомпиляции бинарного кода программы.

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

почему нельзя, вы про реверсинженеринг, я продолжу о теме, с вектором чаров вы можете вызвать размер, но как это решает проблему вопроса если в системе существует char[] у коготорого есть const char* тоесть тогда char[] не должен иметь возможность const char* если мы говорим о литерале и это безопасно, тогда char[] не должен быть уполномочен тому что имеет сейчас. ведь определить после наполнения такими char который требует 0 невозможно же. или возможно?

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

Ничего не понял о чём вы

вы с этого и начали…

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

почему нельзя, вы про реверсинженеринг, я продолжу о теме, с вектором чаров вы можете вызвать размер, но как это решает проблему вопроса если в системе существует char[] у коготорого есть const char* тоесть тогда char[] не должен иметь возможность const char* если мы говорим о литерале и это безопасно, тогда char[] не должен быть уполномочен тому что имеет сейчас. ведь определить после наполнения такими char который требует 0 невозможно же. или возможно?

Просто посмотрите отладчиком как в памяти представлены элементы char[] и сразу получите ответ на вопрос.

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

все верно, и технически можно перейти на строку но строка как и const char связана c char[] очевидно там отобразит тоже что и поиск нуля или нет как вы считаете?

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

все верно, и технически можно перейти на строку но строка как и const char связана c char[] очевидно там отобразит тоже что и поиск нуля или нет как вы считаете?

У компилятора есть та информация, которой нет у программиста.
Компилятор правильно позиционирует требуемый элемент.
Программист в буфере содержащем строки не сможет позиционировать строки если они не содержат в конце нулевой символ.

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

значит char[] должен перестать быть полноправным char[] и он должен изменить квалификатор типа потомучто он требует \0, а значит он не должен требовать \0

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

нет вот я вижу в тесте что компилятор ждёт 0 если передать его в const char*, эту проблему как программист должен решить я, всё верно но квалификатор char[] остался же, и его поведение ждёт нолик еще пока, или можно принять const char* игнорируя поведение его подквалификатора char[]?

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

расскажите как вы поняли это очень интересно.

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

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

В стандарте Си и С++ нет квалификатора того, завершается строка нулём или нет.

просто прекрасно, квалификаторы может и используются… но… char[] - как обьект должен перестать иметь подмножество или принадлежность к const char*, потомучто потомучто архитектурно это два разных мира, они про разное, вообще про разное, для компилятора, но вообще чтобы убрать эту ошибку надо отказаться от связанных квалификаторов такого рода, тогда const char* остается и имеет \0 и [], а char[] должен перестать давать возможность кастоваться в const char* значит должен механизм вытеснения или удаления или отключения такого поведения на char[], что приведёт его к совершенно другому типу сохраняя массивность - тоесть возможности создания массива букв.

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

просто прекрасно, квалификаторы может и используются… но… char[] - как обьект должен перестать иметь подмножество или принадлежность к const char*, потомучто потомучто архитектурно это два разных мира, они про разное, вообще про разное, для компилятора, но вообще чтобы убрать эту ошибку надо отказаться от связанных квалификаторов такого рода, тогда const char* остается и имеет \0 и [], а char[] должен перестать давать возможность кастоваться в const char* значит должен механизм вытеснения или удаления или отключения такого поведения на char[], что приведёт его к совершенно другому типу сохраняя массивность - тоесть возможности создания массива букв.

У меня своё API.
Всё API спроектировано на использовании метаданных о: строках, массивах, списков, деревьев, …, объектов любой иерархии и сложности.
API не полагается на тонкости компиляторов, ОС, …
Поэтому для исходного кода можно использовать любой компилятор и любую ОС.
У вас как я понял специфически удобное для вас API, …
Кстати всё API использует свой менеджер памяти и не полагается на те, которые предоставляет ОС.

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

тоесть pty у вас не потечёт да? там же тоже ОС специфика хоть на расте хоть на С/С++ теже нюансы надо соблюсти

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

почему я несу чушь, в годболте я увидел вместо 4 - 10 и задумался, запустил свою hardwarestring на локалке там 4, тоесть подумать есть о чем, например проверяя шаблоном массив не массив, и чото еще делая через что там делается пока даже не знаю.

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

тоесть pty у вас не потечёт да? там же тоже ОС специфика хоть на расте хоть на С/С++ теже нюансы надо соблюсти

API развивается и конечно не скажу, что API панацея.
А какой проект идеален?
Посмотрите issue любого популярного проекта …

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

API не полагается на тонкости компиляторов, ОС

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

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

ну даже если jit писать простенький придётся расставлять акценты платформы, на маке другая конструкция передачи аргументов в фрейм например, это можно посмотреть в обзорах самых простеньких даже.

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

да и на расте джит будет попроще написать, там есть удобная форма крутки регистров.

Самое интересное, что все всё знают и правильно советуют, а воз и ныне там.

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

не не не, вот если руками писать синтакс в джит нулевой скорости = самый простой, там и просто и муторно легко запутаться, а Раст за вас крутанёт нужный свободный, надо только шаблон подсунуть )

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

у Раста сахарный ассемблер, там подсовывается аля-шаблон по умолчанию.

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

Раст за вас крутанёт нужный свободный, надо только шаблон подсунуть )

Вайб кодинг не приветствую.
Как экспертную систему принимаю.
Знаете почему?
ИИ умеет оперировать знаниями, а новые пока он не умеет создавать.
Да и в целом нынешние технологии ИИ больше похожи на маркетинг цель которого заставить
людей покупать более мощные компьютера.
А заодно и баксов подзаработать.
По своему бизнес прав.

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

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

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

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

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

по началу был пдп-11, супер понятный простой, кристально прозрачный если почитать документацию как я понял, теперь масштаб вырос, тоесть происходил обыкновенный рост в технологиях, чтобы точно понимать почему нужен более мощный комп на убунту какой-то там версии, нужно быть на одной волне експириенса по опыту, получается как с обработкой 3д графики, по началу был открытый-простой конвеер, происходил некий брейншторм движение мысли и прочее, и теперь Вулкан, это как просто регистратор устройства с компом, всю рутину человеку надо написать через апи типо, я так думаю, и вот тут происходит с вейландом та же петрушка, был понятный интерфейс иксы, теперь вейланд хочет стать регистратором, а DE + WM на разрабе через какую-то там идею, наверно что-то с владением памяти и прочих тонкостях, так происходит планомерная трансформация при адаптации поидее.

anonymous
()
Ответ на: комментарий от BruteForce

Я, кстати, не оч понимаю зачем тут хешмап? Что там у него было ключом?

Смутно вспоминая, он там каким-то макаром связанные события отрисовывал в виде графа, ключом в хэшмапе было время события - длинное число, то ли в мкс, то ли мс, т.е. достаточно уникальное.

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

Таки упоролсяпоигрался я и с вашим jjhash_s. Написал с 2 пристрелки раствейно - https://godbolt.org/z/87KWbf5zG. Ассемблеры, конечно, очень интерсные, но нихрена не понятно что должно быть быстрее, пришлось мерять:

unsafe extern "C" {
    fn jjhash_s(s: *const c_char) -> c_uint;
}
fn main() {
    const N:u64 = 200_000_000;
    let t0 = Instant::now();
    let mut hash=0;
    for _ in 0..N {
        hash = black_box(lor_jjhash(black_box("asdrfgvmrkwmvrwkmvgr,vge".as_bytes())));
    }
    let t0 = t0.elapsed().as_millis() as f64;
    println!("{} ms, {}", t0, hash);

    let t1 = Instant::now();
    for _ in 0..N {
        hash = unsafe { jjhash_s( c"asdrfgvmrkwmvrwkmvgr,vge".as_ptr()) };
    }
    let t1 = t1.elapsed().as_millis() as f64;
    println!("{} ms, {}", t1, hash);
    println!(" C/R={}", t1 / t0);
}

black_box-ы нужны чтобы в константу не соптимизировало.

build.rs

fn main() {
    cc::Build::new()
        .file("src/jjhash.c")
        .flag("-O3")
        .compile("libjjhash.a");
    println!("cargo:rerun-if-changed=src/jjhash.c");
}
 Finished `release` profile [optimized] target(s) in 0.31s
     Running `target/release/jjhash`
1221 ms, 1477984367
2167 ms, 1477984367
 C/R=1.7747747747747749

В среднем процентов на 70-80 итераторы обгоняют goto, но это на длинных строках. А вот на коротких (<4), например для 2 символов:

625 ms, 2056255911
749 ms, 2056255911
 C/R=1.1984 (выбрал наименьший)

действительно быстрее, только процентов на 20 отстаёт.

Надеюсь, понимаете, что с вызовами функций по указателю всё будет очень грустно в плане производительности.

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

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

Ещё вопрос по хэшевой предметке. Это норм (с математико-логической стороны) что операция a *= JJHASH_PRIME; в JJHASH_ACCUM_FEED практически гарантированно переполняется, на дебаге у меня сразу стало отваливаться (поэтому у меня wrapping_mul )?

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

Напрямую, в коде же написано (Instant::now, elapsed).

или всё пропускали через Раст?

Это что значит? Вызов внешней «сишной» функции через: «unsafe { jjhash_s( c"asdrfgvmrkwmvrwkmvgr,vge».as_ptr()) };" стоит столько же что и собственной (не встроенной).

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

продолжу вашу тему

const char* s ="asdrfgvmrkwmvrwkmvgr,vge";
    auto start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 200000; ++i) {
        jjhashv2(s, std::strlen(s));//версия с управлением от хвоста на С++
    }
    auto end = std::chrono::high_resolution_clock::now();
    double std_find_time = std::chrono::duration<double, std::nano>(end - start).count();
    std::cout << "jjhash: Среднее время: " << std_find_time / 200000 << " ns\n";



наивно повторил вашу тесту jjhashv2: Среднее время: 0.002905 ns

anonymous
()
Ответ на: комментарий от zurg

извините потом вник, вижу, да вы юзаете билдер, если что я билжу «-std=c++23 -msse4.2 -O3 -funroll-loops» главное не включать -ffast-math

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

но я не уверен, что сдесь правильно время замеряется, и еще там у меня одно условие не уверенное, а по хэшу как у вас (1477984367 -хэш 24 - это длинна)

anonymous
()
Ответ на: комментарий от zurg

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

anonymous
()
Ответ на: комментарий от zurg
fn lor_jjhash(src: &[u8]) -> u32 {

jjhash_s — это функция для сишных строк, а не для pointer-and-length.

Там нам нужно проверять, что байт — не ноль, потому что, если он ноль, то мы можем упасть, прочитав следующий байт.

Для pointer-and-length предназначена jjhash_b.

jjhash_b обгоняет jjhash_s в 2+ два раза.

Попробуйте обогнать хоть какую-то из них (с той же семантикой) на расте. Там bottleneck в умножении.

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

ну переписать std::vector на свой с итератором, с доступом по [],iterator,const iterator, поидее может быть норм, можно добавить менеджер памяти с подсчетом ссылок атомарным, прям встроив в vector, а хеш-мап для счета, ну база - давайте представим наглядную задачку, сделали воксельный мир на хеш-мапах!) имеет логику прыжков по глубине 3(x,y,z). Получается, каждый кубик его вершина и его восемь соседей кидаются куда? я как дилетант назову это контейнером, далее мы, что делаем, даём точку 1 и точку 2, отбросим в сторону пока евристику на земле или в небе, далее запускаем алгоритм дейкстры(ну например), и далее результат - интервал(если он валиден, кидаем в спец функцию rovidePath назовём её, кароче такая функция, которая занимается проведением обьекта из точки а в точку б). Тоесть это метод от обратного… типо помечтали )

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

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

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

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

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

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

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

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

anonymous
()
Ответ на: комментарий от BruteForce

есть 2 фундаментальных подхода, «just do it and it just work», и второй камушек, «изучи классы, чтобы тебе было удобно и организуй ими своё рабочее пространство», то, что там 100 тыщ мильонов паттернов это да, но сути это не меняет, надо тренероваться, в С как это всё будет выглядеть костыльнее если мы о 3д и нормальных концептах, нет деструктора, постоянно приходится делать typedef для структуры, постоянно ставить указатель аля селф, некст-уровень просто начать писать на С++ с деструкторами структурами и методами, а вот далее некст уровень, это организация пространства, чтобы обьектом(ами) было удобно пользоваться, как мы до этого доходим, через нечитаемый синтаксис на Расте или на С++ другой вопрос, но если делать попытки, по началу много раз будет всё по приколу и не получаться, а потом просто надоедает прикалываться и уже просто понимаешь «так мне нужен вектор и управление памятью-ссылками, почитал посмотрел» так я хочу так-так-и-так и всё и погнал и удобно получается.

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

тоесть можно конечно дойти до минимализма Кейси Муратори, но не знаю, это чересчур, хотя многое он делает реально удобно, но тогда уж лучше действительно просто написать std со своей организацией, только до этого надо протестить свои требования и исполнить её своей std.

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

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

В итоге ведущий инженер-программист проекта превратится в истеричную мамочку на детской площадке, каждую минуту орущую во все горло на свое чадо: «Дима! Нельзя!» и подбегающую затем к своей дитятке, чтобы отлупить его по рукам и по заднице.

Зачем выбирать орудие труда, большую часть которого нельзя использовать? Чтобы орать и унижать своих программистов? Сотрудничество гораздо плодотворнее рабского труда.

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

Контракт, я подразумеваю код(Контракт может быть матрёшка) - кирпичик Архитектуры. ну почему если вам интересно пройдитесь по вложеным цикламх3 по (xyz) деревом!(и передайте эту последовательность полученную из дерева в меш или выведите на консоль). или сделайте такой воксельный движок на бсп например, почему нет) никто не против как раз, как бы мы всё равно придём к столкновениям контрактов(потомучто контракт в кодинге жестко завязан на производительности) в той или иной задаче, например, потомучто бсп или бвх не предназначены для такого рода применения, но если хочется очень почему нет.

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

тоесть не тройной цикл например генерирует последовательность, а например дерево - почему нет это новаторски и интересно, тоесть вместо цикла будет изящная рекурсия счетного дерева )

anonymous
()
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.