Сообщение Re[43]: dotnet vs java 2016-2020 от 15.10.2016 8:05
Изменено 15.10.2016 9:16 vdimas
Здравствуйте, ·, Вы писали:
·>Как правило в таких применениях структуры данных довольно плоские и простые (или их специально сводят к таковым).
Как правило для публичного интерфейса к таким структурам — массивам байт все-равно идут ref-объекты (кроме числовых и булевских значений), т.е. КАЖДАЯ операция чтения или записи полей такой структуры часто сопровождается созданием ref-объекта.
·>Если же замутить что-то навороченное даже на плюсах — с классами, с наследованием, виртуальными функциями, овнершипством, то в итоге получится то же самое — индирекции, перемешивание, проблемы удаления и прочие приседания. Чудес не бывает.
Плюсы — это мультипарадигменный язык, позволяющий играть дизайном как душе будет угодно. Виртуальные ф-ии нужны исключительно для абстрактных типов, ну или заменой им можно считать лямбды и прочие std::function.
Вот у тебя некий пост-процессор данных может быть таким абстрактным функциональным типом, но через это пост-процессор прогоняются вполне себе "плоские" данные. Стоимость вызова std::function примерно равна стоимости одного виртуального вызова, т.е. порядка 2-3ns в цикле для современной техники.
·>Угу. Зато работает, а это — самое важное. И ещё раз повторяю, это будет малой долей всего кода системы.
Да не работает оно. Индексы разметки все-равно в виде ref-типов хранятся.
EP>>Не помню точную цитату, но суть в следующем — на заре вычислительной техники кто-то возмутился по поводу компиляторов языков (что-то типа Fortran или Algol) мол зачем нагружать компьютер той рутинной работой (компиляцией), с которой хорошо справляются девушки.
·>Вброс: зачем нагружать компьютер той рутинной работой (сборкой мусора), с которой хорошо справляются сиплюсплюсники.
Потому что в С++ мусор убирать не надо? Может, поэтому?
Ну реально, вы с такими аргументами как с другой планеты.
За лишнюю операцию new в C++ принято больно бить по рукам. Я как-то приводил статистику по очень большому проекту, так там было что-то около 9 операций new на весь проект. Девять на многие мегабайты исходников! ))
Почему так? Потому что объекты создаются иначе, чем в Джаве, а именно:
— в 90% случаев размещаются в памяти объектов владельцев, т.е. хранятся по-значению и так вдоль всей иерархии/композиции;
— еще в 90% от оставшегося случая размещаются прямо по значению в контейнерах — линейных и связанных списках, во всяких set/map и прочих хеш-таблицах, в т.ч. мультииндексных (кстате, эффективное мультииндексное хранение данных тоже для Джавы недоступно, там на каждый индекс лупят отдельную хеш-таблицу).
— еще в 90% случаев от оставшегося создаются прямо на стеке опять по-значению.
И только для разделяемых м/у потоками данных, предназначенных как раз сугубо для перекачки данных м/у потоками или для сущностей самого-самого верхнего уровня оправданно использовать shared_ptr, intrusive_ptr, unique_ptr и т.д. Вот как раз во всём большом проекте набралось 9 таких типов.
В С++ есть сборка мусора в виде сразу нескольких либ и стратегий.
Причем, стратегия mark-and-sweep, принятая в управляемых средах, — она же самая тормозная, разве нет. Но тоже доступна в виде готовых либ.
Просто сравни mark-and-sweep со стратегией "регионального аллокатора":
— пришел запрос, ты берешь такой аллокатор из пула аллокаторов и инициализируешь им поле созданного на стеке по значению некоего "контекста" запроса (этот "контекст" почти всегда есть в серверных приложухах);
— обрабатываешь пришедший запрос, где везде как аргумент протягивается этот самый аллокатор как часть контекста;
— затем выплёвываешь результат обратно в сетку;
— возвращаешь аллокатор в пул.
Всё! Никакой операции по уборке мусора нет. Если мы вернули аллокатор в пул, то считается, что мы весь мусор, связанный с обработкой этого запроса, как бэ и убрали уже, всё... хотя НИКАКИХ действий с памятью на самом деле не производили.
Никаких тебе shared/unique и прочих _ptr, тупо голые указатели и ссылки. ))
Как тебе такой алгоритм уборки "мусора"?
Вот как это выглядит в т.з. подробностей реализации:
— каждый аллокатор хранит несколько относительно крупных страниц памяти, взятых у ОС по наиболее эффективной для неё кратности;
— возврат аллокатора в пул означает возврат этих крупных страниц в пул;
— сам аллокатор — это лишь value-type обертка над двумя полями-указателями на текущую оперируемую страницу и на сам пул, т.е. берет у пула страницы по мере надобности;
— страницы связываются м/у собой в линейный список, но всегда интересует лишь одна текущая страница, остальные считаются уже "израсходованными";
— запрос и возврат страниц в пул выполняются на lock-free алгоритмах;
— трудоёмкость выделения памяти внутри каждой страницы — это трудоёмкость приращения указателя на кратное машинному слову кол-во байт.
По последней операции тоже любопытное замечание: ввиду того, что в плюсах почти всё инлайно, то даже этот несчастный аргумент для приращения указателя "виден" оптимизатору. Источником этого аргумента обычно выступает явная (аналог malloc) или неявная (аналог new) операция sizeof, а эта операция возвращает константу времени компиляции. Т.е., даже эта несчастная операция увеличения курсора внутри страницы максимально эффективна из всех возможных вариантов — к указателю прибавляется константа. ))
Но в реальной жизни всё происходит еще намного-намного циничнее. Если обработкой запросов занимается ТОЛЬКО пул потоков, то у каждого пула уже есть некий личный список страниц памяти — его "буфер". Т.е. никакие аллокаторы, ес-но, ни в какие пулы в таком сценарии возвращать не надо и даже относительно дорогостоящие операции lock-free (там внутри CAS) не нужны. Аллокатор в этом случае хранит всего одно поле — указатель на текущую страницу, где инициализируется первой страницей из пула потока. Если страниц не хватило, то уже в самом аллокаторе будет взята у ОС страница и добавлена в этот однонаправленный список. Усё. Ну и еще плюшки такого сценария — контекст можно протягивать, а можно и нет, ведь он локальный для потока. Это помогает в ситуации, когда надо подцепиться через колбэк-вызов от третьесторонней либы, через которую нельзя протянуть свой контекст.
Скажи, неужели все эти вещи ни разу не интересны? ))
EP>>Так вот эти нарезатели байт-буферов прям как те самые девушки занимаются рутинной механической работой, с которой прекрасно справляются компиляторы, тем более в 2016.
·>И сейчас с супер-пупер код написанный для компьютера, а не для человека (ака оптимизированный код) выглядит плохо по сравнению с остальным кодом, даже в плюсах.
В плюсах это не так.
Самый жутко-оптимизированный код выглядит как совершенно обычный, например:
·>Возможно что аналогичный плюсовый код был бы лучше явового, но другие достоинства managed кода и gc перевешивают недостаток "плохо выглядещего" кода.
Ты зря считаешь, что вся фишка управляемых сред только в GC. Это серьезное заблуждение. Фишка управляемых сред в огромных писанных под них библиотеках/фреймворках. А такое стало возможным не только и не столько из-за ПС, сколько из-за сознательной изолированности от конкретного железа/архитектуры и освобождения от вот этих гирь "необходимости соблюдать совместимость/портируемость". Ну и плюс чудовищные финансовые вложения, ес-но. Ну и плюс всё это было отдано Sun людям даром, хотя в итоге были затрачены сотни миллионов на разработку. Это щедрый прощальный предсмертный подарок этой конторы.
А так-то дай тебе голый GC и буквально только базовые типы и ты сольёшь в скорости разработки любому плюсовику с такими же начальными условиями, но БЕЗ GC. Потому что девять new на огромный проект погоды не делают.
·>Как правило в таких применениях структуры данных довольно плоские и простые (или их специально сводят к таковым).
Как правило для публичного интерфейса к таким структурам — массивам байт все-равно идут ref-объекты (кроме числовых и булевских значений), т.е. КАЖДАЯ операция чтения или записи полей такой структуры часто сопровождается созданием ref-объекта.
·>Если же замутить что-то навороченное даже на плюсах — с классами, с наследованием, виртуальными функциями, овнершипством, то в итоге получится то же самое — индирекции, перемешивание, проблемы удаления и прочие приседания. Чудес не бывает.
Плюсы — это мультипарадигменный язык, позволяющий играть дизайном как душе будет угодно. Виртуальные ф-ии нужны исключительно для абстрактных типов, ну или заменой им можно считать лямбды и прочие std::function.
Вот у тебя некий пост-процессор данных может быть таким абстрактным функциональным типом, но через это пост-процессор прогоняются вполне себе "плоские" данные. Стоимость вызова std::function примерно равна стоимости одного виртуального вызова, т.е. порядка 2-3ns в цикле для современной техники.
·>Угу. Зато работает, а это — самое важное. И ещё раз повторяю, это будет малой долей всего кода системы.
Да не работает оно. Индексы разметки все-равно в виде ref-типов хранятся.
EP>>Не помню точную цитату, но суть в следующем — на заре вычислительной техники кто-то возмутился по поводу компиляторов языков (что-то типа Fortran или Algol) мол зачем нагружать компьютер той рутинной работой (компиляцией), с которой хорошо справляются девушки.
·>Вброс: зачем нагружать компьютер той рутинной работой (сборкой мусора), с которой хорошо справляются сиплюсплюсники.
Потому что в С++ мусор убирать не надо? Может, поэтому?
Ну реально, вы с такими аргументами как с другой планеты.
За лишнюю операцию new в C++ принято больно бить по рукам. Я как-то приводил статистику по очень большому проекту, так там было что-то около 9 операций new на весь проект. Девять на многие мегабайты исходников! ))
Почему так? Потому что объекты создаются иначе, чем в Джаве, а именно:
— в 90% случаев размещаются в памяти объектов владельцев, т.е. хранятся по-значению и так вдоль всей иерархии/композиции;
— еще в 90% от оставшегося случая размещаются прямо по значению в контейнерах — линейных и связанных списках, во всяких set/map и прочих хеш-таблицах, в т.ч. мультииндексных (кстате, эффективное мультииндексное хранение данных тоже для Джавы недоступно, там на каждый индекс лупят отдельную хеш-таблицу).
— еще в 90% случаев от оставшегося создаются прямо на стеке опять по-значению.
И только для разделяемых м/у потоками данных, предназначенных как раз сугубо для перекачки данных м/у потоками или для сущностей самого-самого верхнего уровня оправданно использовать shared_ptr, intrusive_ptr, unique_ptr и т.д. Вот как раз во всём большом проекте набралось 9 таких типов.
В С++ есть сборка мусора в виде сразу нескольких либ и стратегий.
Причем, стратегия mark-and-sweep, принятая в управляемых средах, — она же самая тормозная, разве нет. Но тоже доступна в виде готовых либ.
Просто сравни mark-and-sweep со стратегией "регионального аллокатора":
— пришел запрос, ты берешь такой аллокатор из пула аллокаторов и инициализируешь им поле созданного на стеке по значению некоего "контекста" запроса (этот "контекст" почти всегда есть в серверных приложухах);
— обрабатываешь пришедший запрос, где везде как аргумент протягивается этот самый аллокатор как часть контекста;
— затем выплёвываешь результат обратно в сетку;
— возвращаешь аллокатор в пул.
Всё! Никакой операции по уборке мусора нет. Если мы вернули аллокатор в пул, то считается, что мы весь мусор, связанный с обработкой этого запроса, как бэ и убрали уже, всё... хотя НИКАКИХ действий с памятью на самом деле не производили.
Никаких тебе shared/unique и прочих _ptr, тупо голые указатели и ссылки. ))
Как тебе такой алгоритм уборки "мусора"?
Вот как это выглядит в т.з. подробностей реализации:
— каждый аллокатор хранит несколько относительно крупных страниц памяти, взятых у ОС по наиболее эффективной для неё кратности;
— возврат аллокатора в пул означает возврат этих крупных страниц в пул;
— сам аллокатор — это лишь value-type обертка над двумя полями-указателями на текущую оперируемую страницу и на сам пул, т.е. берет у пула страницы по мере надобности;
— страницы связываются м/у собой в линейный список, но всегда интересует лишь одна текущая страница, остальные считаются уже "израсходованными";
— запрос и возврат страниц в пул выполняются на lock-free алгоритмах;
— трудоёмкость выделения памяти внутри каждой страницы — это трудоёмкость приращения указателя на кратное машинному слову кол-во байт.
По последней операции тоже любопытное замечание: ввиду того, что в плюсах почти всё инлайно, то даже этот несчастный аргумент для приращения указателя "виден" оптимизатору. Источником этого аргумента обычно выступает явная (аналог malloc) или неявная (аналог new) операция sizeof, а эта операция возвращает константу времени компиляции. Т.е., даже эта несчастная операция увеличения курсора внутри страницы максимально эффективна из всех возможных вариантов — к указателю прибавляется константа. ))
Но в реальной жизни всё происходит еще намного-намного циничнее. Если обработкой запросов занимается ТОЛЬКО пул потоков, то у каждого пула уже есть некий личный список страниц памяти — его "буфер". Т.е. никакие аллокаторы, ес-но, ни в какие пулы в таком сценарии возвращать не надо и даже относительно дорогостоящие операции lock-free (там внутри CAS) не нужны. Аллокатор в этом случае хранит всего одно поле — указатель на текущую страницу, где инициализируется первой страницей из пула потока. Если страниц не хватило, то уже в самом аллокаторе будет взята у ОС страница и добавлена в этот однонаправленный список. Усё. Ну и еще плюшки такого сценария — контекст можно протягивать, а можно и нет, ведь он локальный для потока. Это помогает в ситуации, когда надо подцепиться через колбэк-вызов от третьесторонней либы, через которую нельзя протянуть свой контекст.
Скажи, неужели все эти вещи ни разу не интересны? ))
EP>>Так вот эти нарезатели байт-буферов прям как те самые девушки занимаются рутинной механической работой, с которой прекрасно справляются компиляторы, тем более в 2016.
·>И сейчас с супер-пупер код написанный для компьютера, а не для человека (ака оптимизированный код) выглядит плохо по сравнению с остальным кодом, даже в плюсах.
В плюсах это не так.
Самый жутко-оптимизированный код выглядит как совершенно обычный, например:
MyType * obj = new(context) MyType(args);
·>Возможно что аналогичный плюсовый код был бы лучше явового, но другие достоинства managed кода и gc перевешивают недостаток "плохо выглядещего" кода.
Ты зря считаешь, что вся фишка управляемых сред только в GC. Это серьезное заблуждение. Фишка управляемых сред в огромных писанных под них библиотеках/фреймворках. А такое стало возможным не только и не столько из-за ПС, сколько из-за сознательной изолированности от конкретного железа/архитектуры и освобождения от вот этих гирь "необходимости соблюдать совместимость/портируемость". Ну и плюс чудовищные финансовые вложения, ес-но. Ну и плюс всё это было отдано Sun людям даром, хотя в итоге были затрачены сотни миллионов на разработку. Это щедрый прощальный предсмертный подарок этой конторы.
А так-то дай тебе голый GC и буквально только базовые типы и ты сольёшь в скорости разработки любому плюсовику с такими же начальными условиями, но БЕЗ GC. Потому что девять new на огромный проект погоды не делают.
Re[43]: dotnet vs java 2016-2020
Здравствуйте, ·, Вы писали:
·>Как правило в таких применениях структуры данных довольно плоские и простые (или их специально сводят к таковым).
Как правило для публичного интерфейса к таким структурам — массивам байт все-равно идут ref-объекты (кроме числовых и булевских значений), т.е. КАЖДАЯ операция чтения или записи полей такой структуры часто сопровождается созданием ref-объекта.
·>Если же замутить что-то навороченное даже на плюсах — с классами, с наследованием, виртуальными функциями, овнершипством, то в итоге получится то же самое — индирекции, перемешивание, проблемы удаления и прочие приседания. Чудес не бывает.
Плюсы — это мультипарадигменный язык, позволяющий играть дизайном как душе будет угодно. Виртуальные ф-ии нужны исключительно для абстрактных типов, ну или заменой им можно считать лямбды и прочие std::function.
Вот у тебя некий пост-процессор данных может быть таким абстрактным функциональным типом, но через это пост-процессор прогоняются вполне себе "плоские" данные. Стоимость вызова std::function примерно равна стоимости одного виртуального вызова, т.е. порядка 2-3ns в цикле для современной техники.
·>Угу. Зато работает, а это — самое важное. И ещё раз повторяю, это будет малой долей всего кода системы.
Да не работает оно. Индексы разметки все-равно в виде ref-типов хранятся.
EP>>Не помню точную цитату, но суть в следующем — на заре вычислительной техники кто-то возмутился по поводу компиляторов языков (что-то типа Fortran или Algol) мол зачем нагружать компьютер той рутинной работой (компиляцией), с которой хорошо справляются девушки.
·>Вброс: зачем нагружать компьютер той рутинной работой (сборкой мусора), с которой хорошо справляются сиплюсплюсники.
Потому что в С++ мусор убирать не надо? Может, поэтому?
Ну реально, вы с такими аргументами как с другой планеты.
За лишнюю операцию new в C++ принято больно бить по рукам. Я как-то приводил статистику по очень большому проекту, так там было что-то около 9 операций new на весь проект. Девять на многие мегабайты исходников! ))
Почему так? Потому что объекты создаются иначе, чем в Джаве, а именно:
— в 90% случаев размещаются в памяти объектов владельцев, т.е. хранятся по-значению и так вдоль всей иерархии/композиции;
— еще в 90% от оставшегося случая размещаются прямо по значению в контейнерах — линейных и связанных списках, во всяких set/map и прочих хеш-таблицах, в т.ч. мультииндексных (кстате, эффективное мультииндексное хранение данных тоже для Джавы недоступно, там на каждый индекс лупят отдельную хеш-таблицу).
— еще в 90% случаев от оставшегося создаются прямо на стеке опять по-значению.
И только для разделяемых м/у потоками данных, предназначенных как раз сугубо для перекачки данных м/у потоками или для сущностей самого-самого верхнего уровня оправданно использовать shared_ptr, intrusive_ptr, unique_ptr и т.д. Вот как раз во всём большом проекте набралось 9 таких типов.
В С++ есть сборка мусора в виде сразу нескольких либ и стратегий.
Причем, стратегия mark-and-sweep, принятая в управляемых средах, — она же самая тормозная, разве нет. Но тоже доступна в виде готовых либ.
Просто сравни mark-and-sweep со стратегией "регионального аллокатора":
— пришел запрос, ты берешь такой аллокатор из пула аллокаторов и инициализируешь им поле созданного на стеке по значению некоего "контекста" запроса (этот "контекст" почти всегда есть в серверных приложухах);
— обрабатываешь пришедший запрос, где везде как аргумент протягивается этот самый аллокатор как часть контекста;
— затем выплёвываешь результат обратно в сетку;
— возвращаешь аллокатор в пул.
Всё! Никакой операции по уборке мусора нет. Если мы вернули аллокатор в пул, то считается, что мы весь мусор, связанный с обработкой этого запроса, как бэ и убрали уже, всё... хотя НИКАКИХ действий с памятью на самом деле не производили.
Никаких тебе shared/unique и прочих _ptr, тупо голые указатели и ссылки. ))
Как тебе такой алгоритм уборки "мусора"?
Вот как это выглядит с т.з. подробностей реализации:
— каждый аллокатор хранит несколько относительно крупных страниц памяти, взятых у ОС по наиболее эффективной для неё кратности;
— возврат аллокатора в пул означает возврат этих крупных страниц в пул;
— сам аллокатор — это лишь value-type обертка над двумя полями-указателями на текущую оперируемую страницу и на сам пул, т.е. берет у пула страницы по мере надобности;
— страницы связываются м/у собой в линейный список, но всегда интересует лишь одна текущая страница, остальные считаются уже "израсходованными";
— запрос и возврат страниц в пул выполняются на lock-free алгоритмах;
— трудоёмкость выделения памяти внутри каждой страницы — это трудоёмкость приращения указателя на кратное машинному слову кол-во байт.
По последней операции тоже любопытное замечание: ввиду того, что в плюсах почти всё инлайно, то даже этот несчастный аргумент для приращения указателя "виден" оптимизатору. Источником этого аргумента обычно выступает явная (аналог malloc) или неявная (аналог new) операция sizeof, а эта операция возвращает константу времени компиляции. Т.е., даже эта несчастная операция увеличения курсора внутри страницы максимально эффективна из всех возможных вариантов — к указателю прибавляется константа. ))
Но в реальной жизни всё происходит еще намного-намного циничнее. Если обработкой запросов занимается ТОЛЬКО пул потоков, то у каждого потока уже есть некий личный список страниц памяти — его "буфер". Т.е. никакие аллокаторы, ес-но, ни в какие пулы в таком сценарии возвращать не надо и даже относительно дорогостоящие операции lock-free (там внутри CAS) не нужны. Аллокатор в этом случае хранит всего одно поле — указатель на текущую страницу, где инициализируется первой страницей из пула потока. Если страниц не хватило, то уже в самом аллокаторе будет взята у ОС страница и добавлена в этот однонаправленный список. Усё. Ну и еще плюшки такого сценария — контекст можно протягивать, а можно и нет, ведь он локальный для потока. Это помогает в ситуации, когда надо подцепиться через колбэк-вызов от третьесторонней либы, через которую нельзя протянуть свой контекст.
Скажи, неужели все эти вещи ни разу не интересны? ))
EP>>Так вот эти нарезатели байт-буферов прям как те самые девушки занимаются рутинной механической работой, с которой прекрасно справляются компиляторы, тем более в 2016.
·>И сейчас с супер-пупер код написанный для компьютера, а не для человека (ака оптимизированный код) выглядит плохо по сравнению с остальным кодом, даже в плюсах.
В плюсах это не так.
Самый жутко-оптимизированный код выглядит как совершенно обычный, например:
·>Возможно что аналогичный плюсовый код был бы лучше явового, но другие достоинства managed кода и gc перевешивают недостаток "плохо выглядещего" кода.
Ты зря считаешь, что вся фишка управляемых сред только в GC. Это серьезное заблуждение. Фишка управляемых сред в огромных писанных под них библиотеках/фреймворках. А такое стало возможным не только и не столько из-за ПС, сколько из-за сознательной изолированности от конкретного железа/архитектуры и освобождения от вот этих гирь "необходимости соблюдать совместимость/портируемость". Ну и плюс чудовищные финансовые вложения, ес-но. Ну и плюс всё это было отдано Sun людям даром, хотя в итоге были затрачены сотни миллионов на разработку. Это щедрый прощальный предсмертный подарок этой конторы.
А так-то дай тебе голый GC и буквально только базовые типы и ты сольёшь в скорости разработки любому плюсовику с такими же начальными условиями, но БЕЗ GC. Потому что девять new на огромный проект погоды не делают.
·>Как правило в таких применениях структуры данных довольно плоские и простые (или их специально сводят к таковым).
Как правило для публичного интерфейса к таким структурам — массивам байт все-равно идут ref-объекты (кроме числовых и булевских значений), т.е. КАЖДАЯ операция чтения или записи полей такой структуры часто сопровождается созданием ref-объекта.
·>Если же замутить что-то навороченное даже на плюсах — с классами, с наследованием, виртуальными функциями, овнершипством, то в итоге получится то же самое — индирекции, перемешивание, проблемы удаления и прочие приседания. Чудес не бывает.
Плюсы — это мультипарадигменный язык, позволяющий играть дизайном как душе будет угодно. Виртуальные ф-ии нужны исключительно для абстрактных типов, ну или заменой им можно считать лямбды и прочие std::function.
Вот у тебя некий пост-процессор данных может быть таким абстрактным функциональным типом, но через это пост-процессор прогоняются вполне себе "плоские" данные. Стоимость вызова std::function примерно равна стоимости одного виртуального вызова, т.е. порядка 2-3ns в цикле для современной техники.
·>Угу. Зато работает, а это — самое важное. И ещё раз повторяю, это будет малой долей всего кода системы.
Да не работает оно. Индексы разметки все-равно в виде ref-типов хранятся.
EP>>Не помню точную цитату, но суть в следующем — на заре вычислительной техники кто-то возмутился по поводу компиляторов языков (что-то типа Fortran или Algol) мол зачем нагружать компьютер той рутинной работой (компиляцией), с которой хорошо справляются девушки.
·>Вброс: зачем нагружать компьютер той рутинной работой (сборкой мусора), с которой хорошо справляются сиплюсплюсники.
Потому что в С++ мусор убирать не надо? Может, поэтому?
Ну реально, вы с такими аргументами как с другой планеты.
За лишнюю операцию new в C++ принято больно бить по рукам. Я как-то приводил статистику по очень большому проекту, так там было что-то около 9 операций new на весь проект. Девять на многие мегабайты исходников! ))
Почему так? Потому что объекты создаются иначе, чем в Джаве, а именно:
— в 90% случаев размещаются в памяти объектов владельцев, т.е. хранятся по-значению и так вдоль всей иерархии/композиции;
— еще в 90% от оставшегося случая размещаются прямо по значению в контейнерах — линейных и связанных списках, во всяких set/map и прочих хеш-таблицах, в т.ч. мультииндексных (кстате, эффективное мультииндексное хранение данных тоже для Джавы недоступно, там на каждый индекс лупят отдельную хеш-таблицу).
— еще в 90% случаев от оставшегося создаются прямо на стеке опять по-значению.
И только для разделяемых м/у потоками данных, предназначенных как раз сугубо для перекачки данных м/у потоками или для сущностей самого-самого верхнего уровня оправданно использовать shared_ptr, intrusive_ptr, unique_ptr и т.д. Вот как раз во всём большом проекте набралось 9 таких типов.
В С++ есть сборка мусора в виде сразу нескольких либ и стратегий.
Причем, стратегия mark-and-sweep, принятая в управляемых средах, — она же самая тормозная, разве нет. Но тоже доступна в виде готовых либ.
Просто сравни mark-and-sweep со стратегией "регионального аллокатора":
— пришел запрос, ты берешь такой аллокатор из пула аллокаторов и инициализируешь им поле созданного на стеке по значению некоего "контекста" запроса (этот "контекст" почти всегда есть в серверных приложухах);
— обрабатываешь пришедший запрос, где везде как аргумент протягивается этот самый аллокатор как часть контекста;
— затем выплёвываешь результат обратно в сетку;
— возвращаешь аллокатор в пул.
Всё! Никакой операции по уборке мусора нет. Если мы вернули аллокатор в пул, то считается, что мы весь мусор, связанный с обработкой этого запроса, как бэ и убрали уже, всё... хотя НИКАКИХ действий с памятью на самом деле не производили.
Никаких тебе shared/unique и прочих _ptr, тупо голые указатели и ссылки. ))
Как тебе такой алгоритм уборки "мусора"?
Вот как это выглядит с т.з. подробностей реализации:
— каждый аллокатор хранит несколько относительно крупных страниц памяти, взятых у ОС по наиболее эффективной для неё кратности;
— возврат аллокатора в пул означает возврат этих крупных страниц в пул;
— сам аллокатор — это лишь value-type обертка над двумя полями-указателями на текущую оперируемую страницу и на сам пул, т.е. берет у пула страницы по мере надобности;
— страницы связываются м/у собой в линейный список, но всегда интересует лишь одна текущая страница, остальные считаются уже "израсходованными";
— запрос и возврат страниц в пул выполняются на lock-free алгоритмах;
— трудоёмкость выделения памяти внутри каждой страницы — это трудоёмкость приращения указателя на кратное машинному слову кол-во байт.
По последней операции тоже любопытное замечание: ввиду того, что в плюсах почти всё инлайно, то даже этот несчастный аргумент для приращения указателя "виден" оптимизатору. Источником этого аргумента обычно выступает явная (аналог malloc) или неявная (аналог new) операция sizeof, а эта операция возвращает константу времени компиляции. Т.е., даже эта несчастная операция увеличения курсора внутри страницы максимально эффективна из всех возможных вариантов — к указателю прибавляется константа. ))
Но в реальной жизни всё происходит еще намного-намного циничнее. Если обработкой запросов занимается ТОЛЬКО пул потоков, то у каждого потока уже есть некий личный список страниц памяти — его "буфер". Т.е. никакие аллокаторы, ес-но, ни в какие пулы в таком сценарии возвращать не надо и даже относительно дорогостоящие операции lock-free (там внутри CAS) не нужны. Аллокатор в этом случае хранит всего одно поле — указатель на текущую страницу, где инициализируется первой страницей из пула потока. Если страниц не хватило, то уже в самом аллокаторе будет взята у ОС страница и добавлена в этот однонаправленный список. Усё. Ну и еще плюшки такого сценария — контекст можно протягивать, а можно и нет, ведь он локальный для потока. Это помогает в ситуации, когда надо подцепиться через колбэк-вызов от третьесторонней либы, через которую нельзя протянуть свой контекст.
Скажи, неужели все эти вещи ни разу не интересны? ))
EP>>Так вот эти нарезатели байт-буферов прям как те самые девушки занимаются рутинной механической работой, с которой прекрасно справляются компиляторы, тем более в 2016.
·>И сейчас с супер-пупер код написанный для компьютера, а не для человека (ака оптимизированный код) выглядит плохо по сравнению с остальным кодом, даже в плюсах.
В плюсах это не так.
Самый жутко-оптимизированный код выглядит как совершенно обычный, например:
MyType * obj = new(context) MyType(args);
·>Возможно что аналогичный плюсовый код был бы лучше явового, но другие достоинства managed кода и gc перевешивают недостаток "плохо выглядещего" кода.
Ты зря считаешь, что вся фишка управляемых сред только в GC. Это серьезное заблуждение. Фишка управляемых сред в огромных писанных под них библиотеках/фреймворках. А такое стало возможным не только и не столько из-за ПС, сколько из-за сознательной изолированности от конкретного железа/архитектуры и освобождения от вот этих гирь "необходимости соблюдать совместимость/портируемость". Ну и плюс чудовищные финансовые вложения, ес-но. Ну и плюс всё это было отдано Sun людям даром, хотя в итоге были затрачены сотни миллионов на разработку. Это щедрый прощальный предсмертный подарок этой конторы.
А так-то дай тебе голый GC и буквально только базовые типы и ты сольёшь в скорости разработки любому плюсовику с такими же начальными условиями, но БЕЗ GC. Потому что девять new на огромный проект погоды не делают.