Pull to refresh

Comments 27

Совсем пропущены языки нового поколения (Go/Rust), от которых есть ожидание революции и чуда по смещению Си с на«Си»женных позиций.
--Совсем пропущены языки нового поколения (Go/Rust), от которых есть ожидание революции и чуда по смещению Си с на«Си»женных позиций.

Что-то может и изменится если на этих языках напишут полноценную ОС с драйверами, блекжеком и шлюхами.
А зачем переписывать то, что уже и так отлично работает? Просто чтобы доказать «можем»?

Я думаю, появление быстрого браузера будет достаточным аргументом. Пока что ни один интерпретируемо-опекаемый-gc-в-уютной-песочнице язык приличного браузера не породил, а задачи производительности, которые стоят перед создателями браузеров весьма и весьма близки к задачам, которые решают программисты в районе «ой, как бы мне MSI-X по ядрам распихать поудобнее».
А как насчет оптимизации по попаданию в кэш?
… А так же per-process queue, потому что в NUMA доступ к памяти соседнего сокета очень болезенный. И?

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

Если же кто-то хочет указать на тесную спайку всяких gcc-шных расширений для полу-ассемблерного написания, а то и самого что ни на есть ассембера в виде вставок, так их в любой компилируемый (в нативный код) язык можно делать с одинаковым успехом. Некоторые умники мне даже показывали «ассемблер в хаскеле», что уж про Rust/Go говорить.
Я говорил про NUMA?

Как оптимизировать программы на Си вполне известно. Для Си есть качественные компиляторы с различными оптимизациями. Модель памяти в Си проще, в ней нет GC/подсчета ссылок за которые надо платить.
В Rust'е подсчёта ссылок тоже нет, кстати. И GC нет.

А «есть качественный компилятор» — какое это отношение к языку-то имеет? Кстати, предполагается, что компилятор rust'а некачественный?
UFO just landed and posted this here
Я тоже так думал, пока не столкнулся с embedded разработкой. Там, на одном очень редком, маломощном процессоре приходилось держать несколько tcp соединений, обрабатывать данные по последовательному порту, и предоставить api для управления модулем по сети. Вот тут то я и начал оптимизировать свой код, и, как оказалось, к очень многим выводам пришел как и автор. Только я в итоге понял их методом проб и ошибок, больше года, а здесь всего за семь статей можно было это узнать.
И, мое имхо, на читаемости кода это никак не сказалось в худшую сторону, а во многим местах код стал прозрачнее. Особенно про сереализацию и десериализацию это актуально. Когда вы передает json и храните его представление, и чтобы что -то извлечь читаете его — это выглядит значительно сложнее, чем получить массив данных, единожды засунуть его в константный shared_pointer, а нужные данные извлекать просто по индексу в массиве, разименовывая указатель.
UFO just landed and posted this here
Ну так я заметил, что код в читаемости и гибкости не утратил, но стал быстрее. Поэтому если знаешь, то лучше писать так сразу.
Замечательно, действительно, надо запретить себе даже думать об указателях и представить что их нет. Правда что ли? 14-й стандарт отменяет использование указателей? Наиболее эффективное и оптимизированное построение алгоритма происходит именно на основе умелого управления памятью напрямую из кода C/C++. Можно сколько угодно отговаривать новичков от использования указателей в коде, но в этом случае из них не вырастет специалистов, умело управляющихся с памятью вручную. Именно такая магия и нужна как крупным компаниям, так и маленьким стартапам, и именно от получения этих бесценных навыков ты отговариваешь новичков? Не скажу, что очень уж ценный совет для начинающих.
В 17ом стандарте обещают класс string_view для работы над подстроками, но всегда можно его самому навелосипедить или взять StringRef из llvm, но зачем копирования то заведомо лишние плодить?
UFO just landed and posted this here
Если большинство слов не помещается в изначально зарезервированный в `std::string` буфер размером 16 char

Хм, я понимаю, что это SSO, но не утверждал бы так категорично, как будто это всегда так.

char const *finish = text + std::strlen(text);

Зачем пробегаться по тексту лишний раз, ели всё равно strlen расчитан на null-terminated строку.

Когда нужен именно массив с поэлементным доступом и недорогим увеличением размера, смотри лучше в сторону `std::deque`

Ну вот смотрю я push_back(), а сложность для deque — константная, а для vector — амортизированная константная. Что это значит? Утверждение, без каких либо объяснений.

Для класса field, как по мне нужно хотя бы упомянуть о конструкторе копирования/операторе присваивания и, вообще, m_buffer это целая эпопея для тех, кто знаком с memory alignment.
Это всё здорово, но вычисление длины null-terminated строки фактически не влияет на замеры, по сравнению с заполнение N-го числа std::string это как раз копейки. Можно нивилировать стандартной передачей указателя на конец, аналогичного итератору end(). push_back() у std::vector запросто может пойти за новой памятью, и поэлементно будет перемещать все элементы из старого блока в новый, std::deque'у это не грозит. С alignment знакомы, пожалуй, все, m_buffer на это никак не влияет, разница между выделением памяти внутри самого объекта или в куче, да или на стеке, где угодно, для alignment нулевая.
Вот только когда требуется создать no latency или low latency сервис или нужно сэкономить память и время выполнения узкого места обработки больших объемов данных, то тут же прибегают за помощью к «архаичным» разработчикам на C/C++. Просто потому, что эти ребята умеют вручную управлять памятью и прекрасно представляют, что за начинка у той или иной высокоуровневой операции. Сегодня наша задача — стать на шаг ближе к этим ребятам.

Ну и пафос

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

Термин «векторизация» общепринято означает нечто совершенно другое. Вообще, конечно, «вектор» — дебильное имя для структуры, которая на самом деле называется «динамический массив».

Но если соотношение ключ — значение часто изменяется (удаляются соотношения, добавляются новые, и это делается достаточно интенсивно), то проще смириться с поддержанием `std::map`, чем раз за разом перестраивать внутреннее представление `std::unordered_map`. Ведь внутри `std::unordered_map`, по сути, массив цепочек значений, и чем чаще мы изменяем соотношение ключ — значение, тем менее эффективным становится его использование. Здесь не спасет даже более быстрое извлечение по ключу: перестроение больших массивов — это всегда дорого.

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

Главное, что нужно усвоить, — язык C++, как и си, предоставляет прямой доступ к памяти процесса, причем в первую очередь важна память в стеке, а во вторую — память внутри заранее выделенных и подготовленных к использованию буферов. Никакие Java, C# или Python и близко не подойдут к показателям программ, грамотно написанных на C/C++, именно потому, что защищают программиста от неправильной работы с памятью.

Все можно и в Java, и в C#.
Векторизация памяти — это фактически оптимизация работы с оперативной памятью на уровне инструкций CPU, и std::vector для этого отлично подходит. Имя структуры std::vector совсем никак не связано с понятием «динамический массив», как и std::deque. В Java и C# доступ к памяти напрямую настолько же убог и специфичен, насколько и ограничен, сколько всего нельзя в том же unsafe блоке C#, по факту это аналог ассемблерной вставки в С/C++ зачастую абсолютно ненужная операция для высокоуровневых языков. В С/C++ доступ к памяти осуществляется на уровне понятий языка, оптимизация работает как надо и всё можно, что и обычно, это нормальная практика, в отличие от Java/C#.
Главное, что нужно усвоить, — язык C++, как и си, предоставляет прямой доступ к памяти процесса, причем в первую очередь важна память в стеке, а во вторую — память внутри заранее выделенных и подготовленных к использованию буферов. Никакие Java, C# или Python и близко не подойдут к показателям программ, грамотно написанных на C/C++, именно потому, что защищают программиста от неправильной работы с памятью.

В C# и прямая работа с памятью есть и stackalloc и unsafe блоки. Очередное некачественное сравнение?
Как я уже написал выше, unsafe блок крайне ограниченная по своему применению структура языка C#, что именно там можно, а что нельзя. Можно также вспомнить про Marshal и IntPtr, но это вообще доступ к коду, фактически написанному на C/C++ и больше подходит для обёртки, чем для полноценной работы.
Ни ранее, ни выше, ни между строк я не увидел описания этих ограничений. Marshal и IntPtr вообще не являются средствами языка, а просто обертка для unsafe-операций. Вроде такой.

public static unsafe byte ReadByte(IntPtr ptr, int ofs)
{
	…
	byte *addr = (byte *)ptr + ofs;
	return *addr;
	…
}


К чему Вы их упомянули? В CLR есть много более эффективных способов работы с памятью. В том числе safe, умеющие работать с динамическими структурами, которые не определены на этапе компиляции и т.п. По скорости на порядок опережающие Marshal. Или Вы не знали?
Я знаю C# достаточно хорошо, как и .NET Framework в целом и CLR в частности, я говорю лишь о том, что unsafe конструкции не являются нормальной практикой языка и их использование всегда несёт некоторые ограничения, не говоря уже об обёртках из namespace Marshal. Их применение сродни ассемблерной вставке и в этом случае я предпочитаю написать честный нативный модуль и работать с памятью из C/C++, а уж точно не из C#, который в этом случае использовать неэффективно иначе, кроме как для вызова кода обёрток методов.
Ну раз Вы такой профи, то ситуация еще проще. Давайте расчехлим отладчики и пройдемся по коду. Там и увидим, насколько непопулярен unsafe, как редко используются динамические «ассемблерные вставки». Прям на Вашем же примере JSON'а посмотрим, как NetJSON или Jil «небезопасны», как JSON.NET и почти все другие сериализаторы осуществляют эмиссию кода. Померяемся бенчмарками. А то за все время, что Вы говорите о факте существования ограничений с unsafe их можно было уже десять раз показать. У Вас есть перед глазами результат JITа unsafe кода, что Вы так о нем говорите? Должно же быть какое-то подкрепление всех этих буков.
Ну и чего обижаться-то? Как можно сравнивать листинг IL-кода против нативных инструкций скомпилированного и оптимизированного кода на C/C++? Каким бы шустрым CLR ни был, это всё равно лишняя трансляция в машинные инструкции. Хотите померяться с сишником скоростью выполнения managed кода против нативного. Весьма похвально стремление, с каким отстаивается точка зрения и любимые технологии, но это малость опрометчиво. Сериализацию вообще на C# или Java писать не очень эффективно, но если есть пример, где прямо по бенчмаркам C# библиотека уделывает все сишные, я бы на это посмотрел.
UFO just landed and posted this here
Не стоит забывать о том, что мапа всё-таки сортирована по ключам. Иногда это важно.
UFO just landed and posted this here
Sign up to leave a comment.