Как стать автором
Обновить

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

На второй вопрос я для себя ответил так:

На самом деле ответ на второй вопрос кроется в первом пункте заключения. А именно — при прочих равных написание на C сразу ведёт к эффективному коду без излишеств, по сравнению с C++, хоть и занимает, вероятно, больше времени. Простой выбор между качеством результата и скоростью разработки. C++ в этом плане занимает нишу, среднюю между C и PHP — в последнем эффективность продукта по умолчанию вообще не учитывается в угоду финансовой эффективности разработки.

Вы хотя бы пишите за что ставите автору комментария минусы… Я тоже согласен с тем, что написано в комментарии… Более того автор статьи об этом же пишет: "...C «вёл меня» по пути создания быстрого кода, а в случае C++ пришлось заниматься дальнейшим анализом...". В C нет STL с кучей плюшек, поэтому под задачу, как правило, создаются свои, более оптимальные структуры. Но за это приходится расплачиваться более «длинным» кодом и большим временем на отладку.
Выполняю пожелание и пишу, за что минусы:
За heartbleed, за goto fail, за poodle, и за всё прочее в том же духе. А еще за 5 слоёв виртуализации над JS: 1) js-песочница рабочего процесса браузера, 2) пользовательские права процесса, 3) ring-3, 4) ring-0, 5) гипервизор, на случай если враг прорвался в ring-0.

И всё это написано на быстрой сишечке. Настолько быстрой, что как в анекдоте про секретаршу со скоростью набора 600 знаков в минуту «но такая фигня получается!».
Т.е. тот факт, что почти весь линукс, одна из самых надёжных и используемых на серверах операционок написана на сях, это мы не принимаем во внимание? На то, что куча высоконагруженных проектов используют nginx, написанный на сях мы тоже не обращаем внимание? И таких проектов много… Все перечисленные вами проблемы возможны только на C и невозможны на C++ и других языках? Другие языки исключают человеческий фактор и невнимательность?

Но вы ответили на мой вопрос, я так и подумал: зелень с завышенным самомнением и стадным инстинктом — если минус поставили, значит и я поставлю…
НЛО прилетело и опубликовало эту надпись здесь
Мой опыт показывает, что в крупных проектах, во-первых, чистое кодирование занимает максимум 10% времени, всё остальное время уходит на продумывание и тестировании логики, т.е. даже если предположить, что на плюсах код пишется в два раза быстее (что не так для опытного программиста), выигрыш в рамках проекта получается несущественный. Во-вторых, ни разу за 18 лет работы не было, чтобы мне сказали: «Ок, давай теперь займёмся оптимизацией, у тебя есть 3 месяца», код всегда оставался в том виде, как был написан изначально, максимум — это рефакторинг для оптимизации архитектуры кода, но не оптимизации скорости его работы.

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

Ещё один момент — обучение. Когда вы решаете задачу на плюсах, например, пишете LRU кеш, у вас уже есть std::unordered_map и std::list, которые вы будете использовать из коробки. В сях у вас их нет, т.е. вы пойдёте в гугл с вопросом типа «c fastest hash table algo», вы потратите больше времени, но вы будете знать как устроены эти структуры, какие они бывают, что оптимальнее использовать на чисел, что для строк, какая у них скорость работы и т.п.

Да, на плюсах писать быстрее, но как правило, вы не так глубоко погружаетесь в язык и особенности работы компьютеров…

А вообще, язык — это лопата, каждый для своей задачи и каждый для своего пользователя. Программировать надо на том, на чём нравится ;)
Мой опыт показывает, что в крупных проектах, во-первых, чистое кодирование занимает максимум 10% времени, всё остальное время уходит на продумывание и тестировании логики

зависит от назначения конкретного участка кода. Рутину проще реализовать на более высокоуровневом и безопасном с++. Какие-нибудь SIMD-боттлнеки примерно одинаково быстро пишутся на асме/си/с++. Просто рутины количественно больше.
Я говорил в общем по проекту… Конечно же местами кодинг будет занимать больше времени, местами меньше, местами разница в скорости написания на плюсах будет существенно больше, местами будет сравнима… Конечно же у плюсов много плюшек, позволяющих ускорить написание кода, я сам пишу на плюсах…
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
С такой магический язык?

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

Это значит, что более долгое написание на C компенсируется более долгими оптимизациями на C++.
Вообще неплохо бы это изучить один раз в начале карьеры и потом периодически обновлять знания о каких-то новых подходах, которые таки иногда появляются.

Это в идеальном мире. Я собеседовал плюсовиков, нередко встечались такие, что не могли объяснить внутреннее устройство std::list, std::map, std::unordered_map, отличие последних двух…
Не так глубоко, как на С? А почему?

Я уже несколько раз писал, в C нет stl с кучей структур, вам приходится в них разбираться…

Я пишу не о профессионалах, профессионалу всё равно на чём писать, на C или С++. У любого фаната C есть куча наработок по структурам данных и т.п., он реализует LRU-кеш одинаково быстро и на C и на С++. Я пишу о начинающих или тех, у кого не очень большой опыт.

Я уже несколько раз писал, в C нет stl с кучей структур, вам приходится в них разбираться…

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


Сколько раз вы реализовывали RB-дерево самостоятельно? Часто хэш-таблицы пишете? Очереди с приоритетом? Сортировку с гарантированным O(n log(n)) (qsort не даёт никаких гарантий, если что)? В плюсах это всё на расстоянии одного инклюда. Опять же, относительно просто самому реализовать структуру данных, которую можно будет использовать повторно (я лично очень люблю flat containers).


Ну и главная фича плюсов, на мой взгляд — деструкторы. Управление несколькими ресурсами в C — лютый геморрой. Попробовав деструкторы один раз, возвращаться обратно к C уже не захочется.

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

Конечно, проще использовать готовые структуры чем искать новые… Что и приводит к тому, что программисты меньше погружаются в программирование, хуже знают структуры, алгоритмы и т.п. Не все, но как мне кажется, многие… Не могу сказать, что это плохо, просто так случилось :)

Сколько раз вы реализовывали RB-дерево самостоятельно? Часто хэш-таблицы пишете? Очереди с приоритетом? Сортировку с гарантированным O(n log(n)) (qsort не даёт никаких гарантий, если что)? В плюсах это всё на расстоянии одного инклюда. Опять же, относительно просто самому реализовать структуру данных, которую можно будет использовать повторно (я лично очень люблю flat containers).

RB ни разу, один раз AVL, потом нашёл JudyArrays, которая меня полностью удовлетворила. Хеш-таблицы чаще пишу, штук 5-10 наверное уже написал, чаще для тестирования своих и чужих идей. Очереди с приоритетом не нужны были, не писал. По сортировке писал Radix Sort, Merge Sort, всякие пузырьки и перестановки не считаю, это было давно )))

В плюсах относительно просто реализовать структуру, которая будет покрывать множество типов без дублирования кода, потому как есть шаблоны, их реально не хватает в C.

Я наверное псих, как, думаю, почти любой C-ник, предпочитающий C плюсам при прочих равных… Я люблю заморочиться (когда есть возможность) оптимизацией структур и алгоритмов под конкретную задачу, мне это жутко интересно. Потому да, я достаточно часто пишу новые структуры и алгоритмы.
а могли бы вместо этого написать (std/boost/whatever)::hash а всё остальное время потратить на полезную работу.
А почему вы решили, что написание собственной хеш-таблицы — это не полезно ;)
Потому что, судя по вашим постам, вы не полезной работой занимаетесь, а переписываете то, что уже за вас написано и явно в лучше виде. Если ваша работа писать круглые сутки структуры данных, то да, но что-то я очень сомневаюсь. Это не большая наука почитать про устройство этих структур данных и написать самому. Для общего развития почитать и хватит, а писать — зачем оно нужно? Страшилки, что программисты повсеместно плохими от этого стали, что хэш таблицы сами не пишут каждый день — это оставьте студентам с их максимализмом.
Во-первых, вы ошиблись с выводами, то, что я люблю структуры и алгоритмы не значит, что я занимаюсь только ими…

Во-вторых, вы уверены, что не зная меня, можете делать выводы, полезной работой я занимаюсь или нет?

Вы третьих, вы уверены, что всё то, что я написал явно хуже того, что уже написано?

Если бы было так, как вы пишете, то была бы всего одна реализация хеш-таблицы, дерева, списка и т.п.

Судя по тону и минусу, ты таки и есть тот судент-максималист… Изыди, зануда…
Во-вторых, вы уверены, что не зная меня, можете делать выводы, полезной работой я занимаюсь или нет?

Вы третьих, вы уверены, что всё то, что я написал явно хуже того, что уже написано?

Обычно те, кто может написать лучше, только этим и занимается

оффтоп
вы уверены, что не зная меня, можете делать выводы

ты таки и есть тот судент-максималист… Изыди, зануда…

лицемерие
Да нет, кажется это я со студентом попал. А минус я не ставил.

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

И, что интересно, в boost intrusive такая уже есть

Спасибо за хорошую статью и подробной разбор кода и отличий в подходе к программированию на C/C++.

Интересно, а как бы rust себя а этом случае повёл.

Как раз думал написать на расте и сравнить. Но для этого предпочел бы sample-1 из задачи чтобы приложили к исходникам, для пущей убедительности.

Да, пожалуйста, было бы интересно посмотреть на результаты. Данные тут: corosan.ru/data-exp/sample-1.dat
Самое главное, когда пишешь на C++, не скатиться при оптимизации на чистый Си. А то иногда получается, что файл имеет расширение .cpp, и это все, что он имеет от плюсов -)
Бывает и обратная беда — чрезмерное использование перегруженных операторов, тотальная шаблонизация, использование хаков в стиле Александреску типа sfinae повсеместно и т.д. Вообще написание кода на плюсах — это ходьба по лезвию бритвы. Или игра на nightmare. Единственный труъ язык остался, все остальные «пластиковые» какие-то, ими даже застрелиться нельзя, максимум наступить босой ногой на кубик SimpleBeanFactoryAwareAspectInstanceFactory.
А уж насчет сопровождения и отладки чужих C++, это вообще полный восторг. Попытки угадать, какой метод, из какого класса будет вызван сегодня из этой функции обречены на неудачу (если ты не писал сам это приложение).

Если написано func1(), то простой поиск по большинству C++ приложений показывает как минимум 2-3 функции с таким именем, и даже число и тип параметров совпадает — inheritance, sir!
А почему битовый массив самодельный? std::vector это тоже битовый массив
Вы видимо имели в виду std::vector<bool>?

Всё, что про него нужно знать: не используйте. Точка. Он пытается имитировать указатели на биты — и в результате делает это всё равно не слишком корректно, но работает медленно и плохо.

Ну так есть же std::bitset

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

Беда в том, что std::vector<bool> пытается решать сразу много задач — и все решает плохо.

При этом написать замену, решающую конкретно вашу, ограниченную задачу хорошо — обычно несложно, 150-200 строк кода, может чуть больше…

Так что… покажите задачу — можно будет обсуждать замену…
Он решает задачу экономии памяти весьма хорошо. Для конкретных задач он и не проектировался. Не всё в STL должно быть одинаково полезно.
И даже эту задачу он решает плохо. Да, он позволяет упаковать несколько bool'ов в один байт, но при этом порождает столько дополнительного кода, что во многих случаях общее потребление памяти программой возрастает.
Получается, что более элегантный код на С++ требует больше усилий, чтобы достичь той же производительности, чем код на С.
Кстати, а попробуйте компилятор clang, интересно какие у него будут результаты для версий на С, С++ и оптимизированной версии на С++.
не соглашусь. Сравнение си с с++ так же легитимно, как и сравнение ассемблера с си — первый дает небольшой прирост в производительности ценой многократного увеличения объема кода, времени разработки и мат. ожидания числа ошибок. Фактического ускорения может даже и не быть, если человек оптимизирует слабее компилятора. При этом надо понимать, что некоторые оптимизации (чисто алгоритмические) типа использования правильного типа контейнера, стратегии аллокации и т.д. реализуются на с++ куда проще и как правило дают больший выигрыш, чем низкоуровневые микрооптимизации.
Попробовал, добавил внизу статьи Update 1.
в абсолютном плане clang генерирует код чуть-чуть медленнее, чем gcc.
Не понял, кто медленный. Сам компилятор, или код, им скомпилированный?
Код, полученный в результате работы clang'а медленнее, чем код, полученный от gcc.
НЛО прилетело и опубликовало эту надпись здесь
На рабочих проектах у меня clang, как ни странно, тоже слегка проседает по отношению к gcc. Я имею ввиду именно генерируемый им код. Сам компилятор, при этом, компилирует быстрее. И сообщения об ошибках у него более точные. Поэтому мы его в continious integration гоняем.
И сообщения об ошибках у него более точные.

Что значит более точные? Если речь про то, что он на каждую ошибку/предупреждение выдает строк по 5-10 текста в консоль, так это наоборот раздражает и мешает.

Почему странный? Признаться, GCC и раньше был всегда быстрее в смысле оптимизаций. Я пользую clang ради примочек статического анализатора, сами же проекты всегда собираю на GCC, и всегда с профитом.

Мы тут давеча соревновались с коллегой в вопросах Rust vs чистый Си, и я выигрывал стабильно 5-10% просто в счет того, что GCC ведет в оптимизациях.
НЛО прилетело и опубликовало эту надпись здесь
Ну, мало ли… Может быть, вы не сравниваете последние версии компиляторов, может, не всю батарею оптимизаций GCC разворачиваете, может, у вас архитектура целевая какая-то необычная для GCC.

Но вот ей богу, один и тот же прожект, с одними и теми же бенчмарками у меня стабильно показывает от 5% преимущество за GCC.

Опять же, повторюсь, речь идет о чистом Си, плюсы — другая история.
НЛО прилетело и опубликовало эту надпись здесь
На нашем численном решателе полтора года назад gcc выигрывал примерно в 1.5-2 раза. Сейчас всего процентов 5 gcc выигрывает. (gcc 7.2.0 vs clang 5.0.1) Опции -O3 -march=native
Мне лень последние сборки устанавливать, признаться, но вот пример:

www.phoronix.com/scan.php?page=article&item=gcc-clang-eoy2017&num=1

Если вам тоже лень, то у Фороникса выходит, что где-то в 7 из 10 случаев GCC ведет. Хотя разника, конечно, уже совсем не та, что бывало.

Может статься, вы нашли те самые 3 из 10, да и вообще — дело это быстроменяющееся в наши-то дни.
Получается, что более элегантный код на С++ требует больше усилий, чтобы достичь той же производительности, чем код на С.

Неужели? Написать работающую версию на плюсах проще. А оптимизация еще не факт что вообще понадобится.

Если не важна оптимизация — написать на Python еще проще и быстрее.
Зависит от задачи.
Сила питона — в способности быстро, дешево и грязно склеивать в решение задачи библиотеки на других языках.
Задача из этой статьи — явно не для питона, слишком низкоуровневая и высокопроизводительная.
В этом случае отсутствие нужды в оптимизации для плюсов не означает ее отсутствия для питона, а статическая типизация и детерминированная стоимость абстракций играют большую роль, чем возможность быстро наваять прототип.
Если же я пишу сложную управляющую логику с очень разнообразными действиями, не сводящимися к «повторить вычисления A, B, C миллион раз», то лучше взять на вооружение всю мощь типизированного C++.

А может быть тогда имеет смысл взять что-то еще более высокоуровневое, чем C++? Что-то не требуещее компиляции по несколько минут (более-менее сложного кода), имеющее кучу удобных библиотек, позволяющее легко писать тесты? Не то, чтобы я был противником C++ — сам пишу на нем уже лет 15, но вот для своих личных экспериментов последние пару лет реально тянет писать например на связке Python + C.

Python, вы уж извините, это три порядка (десятичных) замедления. Разница в 1.5-2 раза (при использовании C++ или Go) — это одно, а три порядка — это другое.

Если задача — запускать в нужном порядке по определенным условиям число-молотилки или какие-нибудь IO (реализованные опять же на C) — то 2 порядка замедления питона погоды не сделают. Наглядный пример — Keras, который является очень удобной оберткой над в том числе Tensorflow, половина которого опять же на питоне (вторая половина как раз на C/C++), и в питоновском коде программы от силы 10% времени проводят. И тут хоть в 100 раз этот питон ускорить — ощутимого эффекта не будет. Зато продуктивность с этим самым питоном за счет высокоуровневых конструкций и быстрой обратной связи (не надо ждать компиляции, проще организовать автотесты) на порядок выше.

Закон Амдала никто не отменял. У вас на лаптопе, может, «в питоновском коде программы от силы 10% времени проводят», а на сервере с 96 потоками (два сокета, 24 ядра на сокет, плюс гипертрединг)?

Или думаете, что и через 10 лет все будут использовать двух-четырёхядерные системы?
И при чем здесь это? Если питон используется как удобная обертка над высокопроизводительным кодом, то какая разница, сколько там питон выполняется? Хоть сколько там потоков, на них выполняется не питон совсем. Более того, у него скорее всего будет четко фиксированное процессорное время, т.к. он лишь прослойка, призванная раскидать задачи.
Более того, у него скорее всего будет четко фиксированное процессорное время, т.к. он лишь прослойка, призванная раскидать задачи.
Процессорное время — да. А вот просто время (по секундомеру) — нет. Если на одноядерной системе у вас эта прослойка занимает 10% времени, то на 96 ядреной она будет уже занимать 90% процентов времени.

На практике этого я пока не наблюдал, но ситуации, когда в TensorFlow управляющая программа на питоне занимает половину wall-clock time — видел уже.

Ну я в курсе про закон Амдала, и на десктопе у меня реальных 20 ядер (спасибо списанным процам и алиэкспрессу), но только это немного мимо, как уже отписали выше. Если при написании программы/сервиса/whatever была заложена масштабируемость, то при росте числа ядер/узлов доля времени "медленного" кода как минимум не будет расти, а как максимум будет падать. При этом время на разработку на плюсах будет в разы больше, чем на том же питоне, а человекочасы масштабировать гораздо сложнее. Более того, даже в чисто плюсовых проектах регулярный паттерн — два уровня кодовой базы, в нижнем все вылизывается под максимальную производительность (и там вдумчивая работа с памятью, минимизация syscalls и т.п.), а в верхнем пишется в стиле лишь бы побыстрее, и чтобы потом в этом легко разобраться было. При этом основное время выполняется как раз низкоуровневый код, а если это становится не так, то находится очердной "горячий" участок и выделяются очередные низкоуровневые примитивы. И вот этот высокоуровневый код на самом деле часто гораздо удобнее и проще было бы писать на чем-то более высокоуровневом, чем C++. Питон я только для примера привел, скорее как крайность.

При этом основное время выполняется как раз низкоуровневый код, а если это становится не так, то находится очердной «горячий» участок и выделяются очередные низкоуровневые примитивы.
А чем находится? perf'ом? Так он процессорное время показывает!

Это всё не теоретические изыскания: как я писал выше — я уже наблюдал ситуацию с TensorFlow, когда по perf'у время уходило в основном на вычислительную часть, а вот если пересчитать его на количество используемых ядер (получив тем самым wallclock-time), то половина времени проводилась в управляющей программе на питоне, написанной в стиле «лишь бы побыстрее, и чтобы потом в этом легко разобраться было». И как раз исправление этой части давало гораздо больший выигрыш. А если бы её изначально написали не на питоне, а хотя бы на Go? Вопрос, конечно, риторический…

Если при написании программы/сервиса/whatever была заложена масштабируемость, то при росте числа ядер/узлов доля времени «медленного» кода как минимум не будет расти, а как максимум будет падать.
Совершенно неочевидно — с какого перепугу так будет. По нашим наблюдениям доля wall-clock time всяких pyhon'овских скриптов только растёт. Одна из причин перехода на Go в некоторых местах, кстати.
Ну разумеется горячие места всегда находятся в неоптимизированной части. После чего их оптимизируют (выделяют новые абстракции и переписывают их на Си или С++).
В принципе в описанном тут кейсе можно и не переписывать на кресты а изменить логику так, чтобы никто не ждал высокоуровневую часть.
В зависимости от конкретной проблемы можно или распаралелить высокоуровневый язык (тут пайтон) или переходим на асинхронную работу с числодробильной частью. В первом случае пайтон будет работать в 96 потоков, во втором случае пайтон останется в одном потоке, но числодробилка не будет ждать управляющую часть а будет «дробить» параллельно с ней на остальных 95 потоках.
Может возникнуть вопрос о том насколько вообще возможно распаралелить высокоуровневую логику (насколько можно сделать ее работу с числодробилкой асинхронной), но в большинстве случаев это проблема алгоритма а не языка, и если оно не паралелится на пайтоне то и на другом языке не сильно распаралелится.
В принципе в описанном тут кейсе можно и не переписывать на кресты а изменить логику так, чтобы никто не ждал высокоуровневую часть.
Примерно это в TensorFlow и сделали. Теперь на 96 потоках python занимает одно ядро на 50-60% времени, а остальное — занято «числодробилками». Что будет если ядер станет ещё в 5 раз больше?

В зависимости от конкретной проблемы можно или распаралелить высокоуровневый язык (тут пайтон)
Если бы всё было так просто. Python в принципе однопоточный — это в нём в таком количестве мест прописано.

Да, есть костыли, позволяющие как-то разбить задачу на несколько CPU, но после этого вы получаете программу, которая и сложная (так как мы используем нетривиальные костыли), и работает медленно (потому как python).

Может возникнуть вопрос о том насколько вообще возможно распаралелить высокоуровневую логику (насколько можно сделать ее работу с числодробилкой асинхронной), но в большинстве случаев это проблема алгоритма а не языка, и если оно не паралелится на пайтоне то и на другом языке не сильно распаралелится.
Да — но в питоне потребность в параллелизме возникает раньше (где-то лет на 10-15 раньше, если верить в закон Мура), а решается сложнее, чем в Go, Rust'е или том же C++.
Примерно это в TensorFlow и сделали. Теперь на 96 потоках python занимает одно ядро на 50-60% времени, а остальное — занято «числодробилками». Что будет если ядер станет ещё в 5 раз больше?

Тут да, каюсь, вопрос достаточно сферичен и я по инерции притащил контекст своей текущей задачи, а у меня подразумевается что по мере роста мощностей нагрузка будет возрастать в основном на числодробилку. Если такого фактора нет, то действительно с ростом количества ядер время ожидания управляющей подсистемы увеличится.
Если бы всё было так просто. Python в принципе однопоточный — это в нём в таком количестве мест прописано.

Да, есть костыли, позволяющие как-то разбить задачу на несколько CPU, но после этого вы получаете программу, которая и сложная (так как мы используем нетривиальные костыли), и работает медленно (потому как python).

Ну очень сильно зависит от задачи.
Довольно много задач можно паралелить так, что общение потоков будет низкое, а значит можно обойтись банальным pid = os.fork() что не особо то и усложнит задачу.
В конечном итоге может выйти так, что 90% проекта будет на плюсах, а сэкономленное время при разработке оставшихся 10% аукнутся временем, потраченным на биндинги.
На самом деле всего два порядка =)
Но у него другие — более противные и важные недостатки для серьезных задач.

Реальной замены С++ в достаточно большой области применения нет, и в ближайшем будущем и не ожидается.

Статья хороша именно анализом мест просадок — это лишние системные вызовы аллокаций.
Реальной замены С++ в достаточно большой области применения нет, и в ближайшем будущем и не ожидается.


Rust? Go?
го нет, раст да. Если говорить про замену именно плюсам
Это смотря где замена. Go не хватит разве что в высокопроизводительном клиентском коде вроде всяких браузерных движков и игр. Тут Rust отличная замена. На бэкэндах серверов, где high-load и куча потоков, Go вполне себе замена и плюсам. Собственно, для этого и создавался.

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

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

Почему сразу не так? Сборщик мусора у го оптимизирован под минимизацию времени одиночной паузы и сознательно лишен гибкости в плане настроек.
Как результат и по скорости аллокаций, и по скорости очистки, и по потреблению ресурсов (и практически по всему остальному, кроме максимальной длительности одиночной паузы) сборщик мусора го уступает и яве, и дотнету.
На хабре уже была довольно подробная статья на эту тему:
https://habrahabr.ru/company/mailru/blog/318504/

А если Cython использовать?
Удобство/скорость разработки питона и производительность компилируемых языков в одном флаконе.

Или Numba, если нужны суровые числодробилки и не пугает необходимость ставить LLVM не ниже четвертой версии

А какой у него (Cython) оверхед по скорости в сравнении с крестами? Понятно что от задачи зависит, но хоть порядок. А то гуглится только в сравнении с нативным пайтоном.
Сейчас как раз принял решение в одном проекте менять стек языков. Один черт все переписывать. Кресты хороши в числодробилках, но слишком дороги в мозгодробилках.
Для 70% процессорного времени у меня и так используются библиотеки которые есть на большинстве языков, так что нативный си подключить не сложно. Если Cython будет медленнее раза в два-три, то бутерброд из пайтона, Cython и нативного си будет суммарно уступать варианту полностью на крестах на 10-20%, что более чем допустимо учитывая что кодовая база будет достаточно монолитная, с пайтона на другие популярные языки (пхп, javascript, java) значительно проще чем с крестов.
А вот если там будет десятичный порядок или больше, то общая скорость упадет раза в два, и это уже ощутимо…
НЛО прилетело и опубликовало эту надпись здесь
Я тоже люблю и использую Python. Но в этой статье хотел ограничиться сравнением именно C и C++. Потому что переход в проекте на Python — это явный шаг, у которого должны быть веские причины, который должен быть осмыслен и обсуждён с коллегами.

Статья же описывает гипотетическую ситуацию, где весь проект как разрабатывался, так и разрабатывается на C++, но кое-где можно перейти на C, да хоть бы и оставив расширения файлов cpp.
НЛО прилетело и опубликовало эту надпись здесь
Спасибо за хорошую статью! Интересно какой результат покажет clang.
См. Update 1 в конце статьи.
(0.722 seconds)
(7.65 seconds)

Не совсем понял. Разница в 10 раз? У вас первая версия была, вроде на десяток процентов медленнее, но в какой-то момент пошла вот такая разница.

Кроме того, если вы реализуете один и тот-же алгоритм кодирования, разве не должен он на выходе давать одинаковый результат с точностью до бита (чего у вас нет)?
Великолепные вопросы! Прочитайте статью ещё раз, убедитесь что «чего у вас нет» — это ваше воображение… после чего и первый вопрос тоже отпадёт…
Да, извиняюсь — по второму вопросу. Не заметил что при сравнении используется разный размер блока. Поэтому и данные на выходе разные. Но я не понял смысл такого сравнения. Почему для версии на Си не использовали тот-же размер блока при сравнении?
Использовали одинаковые размеры блоков для разных версий.
Но результаты тестирования алгоритма архивации были слишком… архивированны :)
Следите за руками:
Сначала приведено сравнение Си и крестов на большом блоке.
Потом приведено сравнение Си и крестов на маленьком блоке.
Потом была проведена оптимизация крестового кода, и приведено…
Нет, не то что интуитивно ожидаемо для наглядности (результаты Си и новой версии крестов для большого блока, потом результаты Си и крестов для маленького) а только то, что изменилось, а именно два результата новой версии крестов — для большого блока и для маленького блока.
С дальнейшей оптимизацией тоже самое.
Так что для того чтобы сравнить результаты после оптимизации нужно взять результаты после оптимизации и сравнить их с результатами Си из первых тестов.

ПС: мне было лень листать назад и я просто читал общий вывод (быстрее на хх%, медленнее на уу%).
Нда… Понятно. Как-то это неитуитивно, вот и запутался. Спасибо что разъяснили!
Да, объяснение господина Mendel повеселило. Но, на самом деле, мой косяк. Нужно было скопировать тот самый неизменяющийся C результат для наглядности.
Ну да, Huffman tree, обычно, реализуется на очередях с приоритетом, но где ж std::priority_queue в коде на С++? Где std::vector для бинарного кодирования?
priority_queue реализовано самостоятельно. И операции с битовыми массивами — тоже. Чтобы иметь возможность сравнивать как можно более близкие имплементации из C и C++. Более того, про vector выше уже выразили несколько резковатое мнение. С которым я, в прочем, согласен на 100%.
Вы реализовали priority_queue самостоятельно, бинарный вектор — не то. Может отказаться совсем от STL? Почему б не реализовать собственный unique_ptr — это совсем не сложно? И в чем тогда будет заключаться сравнение C++/C?
При анализе инструментарием «callgrind» видно, что много инструкций тратится на работу с кучей — malloc и free.

priority_queue реализовано самостоятельно

Ваша реализация priority_queue — это прямо-таки бенчмарк для аллокатора. По значению надо всё хранить. std::priority_queue должен быть ощутимо шустрее.

Реквестирую проверку кода с std::priority_queue. std::vector<bool> это да несовсем то, что надор.
Но вот очеред с приоритетами и stl должна хорошо себя показать.
Какие очереди с приоритетом? Для дерева Хаффмана достаточно сортировки и двух очередей.
Хорошая, годная статья.

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

А "скорость" компиляции C++ — часом не от того, что просто подключается больше хедеров?

Не просто хедеров а хедеров с шаблонами и прочей “черной магией“ плюсов.

В основном потому, что компиляторы С++ делают >=7-проходов по исходному тексту. Это У.Брайт писал.

Для сравнения — Паскаль — 1-проходный компилятор.
Вот только любой современный компилятор после того, как строит AST делает ещё несколько десятков оптимизирующих проходов.

Понять, что дело вовсе не в этих 7 проходах достаточно просто: переименуйте .c файл в .cc файл, исправьте несколько мест, которые перестанут компилироваться, и замерьте время компиляции.

Тормозят в C++ шаблоны и связанная с ними «магия», а не 7 проходов…
Оптимизирующие проходы по уже готовому абстрактному дереву или внутреннему представлению — одно; хождение многократное коду для построения этого дерева — совершенно другое. Сейчас плюсы имеют такие жуткие проблемы с заголовками не из-за проходов оптимизатора, отнюдь.

Dlang, например, умеет в разы больше метапрограммирования и магии, но компилирует даже большой проект в секунды.
Сейчас плюсы имеют такие жуткие проблемы с заголовками не из-за проходов оптимизатора, отнюдь.
Советую на досуге собрать-таки какой-нибудь проект с -O0. Вы будете приятно (или неприятно, я не знаю) удивлены.
Эм.

А вы будете удивлены, когда узнаете, насколько — относительно больших проектов на плюсах — быстро собирается ядро линукса.

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

По идее шаблоны, или как их называют «generics», по — Русски наверное было бы точнее сказать «переменные типа данных» в с# формально отличаются от С++ тем, что в с# их развертка происходит во время выполнения, а в С++ во время сверстки ( компиляции ) исходных текстов.

Но если отвлечься от формального описания, и попытаться уловить, в чем собственно суть дела, то в С++ оповещение о переменной «тип данных» происходит за счет, так называемых, «include», который передается издательством. В с# «include» распространяется вместе с сверстанным ( откомпилированным ) блоком, в виде метаданных.

В принципе, при всем желании С++ сильно отличаться от С ( и если его напичкать ещё большим количеством стандартного наполнения, то он станет работать ещё в разы медленнее и возможно станет ещё не — похожее ) здесь он копия С. Поскольку его сверстанный интерфейс ни коим образом не привязан к опубликованным в «include» вызовам интерфейса. И если в с# достаточно вставить электронную подпись, чтобы эту связь сделать принудительной, то в С++ это сделать нельзя.
Принципиальное отличие — в том, что generic не поддерживает частичную специализацию, а template поддерживает. Это сильнейшим образом влияет на их мощность как метаязыка.
Поправка: generic не поддерживает специализацию в принципе, не только частичную. И еще generic не поддерживает обращение к статическим элементам параметра-типа.
Вообще — то я воспользовался с#, чтобы оттенить разницу ( её отсутствие ) между С и С++.

«при всем желании С++ сильно отличаться от С»

То, что Вы описываете, это детали синтаксиса языка. Их всегда можно уточнить в Википедии. За что сердечно признателен :)
А вы знаете что существует язык где одновременно присутствуют обобщенные классы (generics) и шаблоны (templates)?
То, что Вы описываете, это детали синтаксиса языка.
Превращение чего-то, выполняющегося за ограниченное время в что-то, полное по Тьюрингу — это, я извиняюсь, далеко не «детали».
Статья об отличие С и С++.
Тюринг о разнице между разумом и машиной.
Мне кажется что Ваши комментарии Тюрингу не соответствуют :)
Нет, полнота по Тьюрингу не имеет никакого отношения к тесту Тьюринга.
Мерилом полноты, на самом деле, является тест. Это паттерна Вашего мышления. Уйти от темы ( теста ) и заменить её некоей «полнотой», которой на самом деле нет.
Как я мог забыть, ведь каждый ученый имеет право только на одно открытие в жизни!
НЛО прилетело и опубликовало эту надпись здесь
Так как язык C является частью языка C++

Отличное начало, так держать. Cи не является подмножеством С++.
Подскажите, пожалуйста, какие действия можно выразить в программе на языке C, которых нельзя было бы выразить тем же способом на языке C++? (мелкие синтаксические различия типа переиспользования ключевого слова auto не в счёт).
a = { .c = 30, .a = 10 }; для структур, VLA из C99, которые правда стали не обязательны в C11.
Например, массив нулевого размера:
typedef struct array{
    size_t size;
    int data[0];
} array;
Причём это скорее следствие поддержки Flexible array members. Здесь data имеет неопределённую длину, но находится внутри структуры. Естественно такое поле может быть только одно и быть только последним.
typedef struct array{
    size_t size;
    int data[];
} array;
Зря минусуют то. Строго говоря C++ не является надмножеством C. Особенно с учётом последних версий C.
Для тех кому нужен чистый Си с нулевым рантаймом, поддержкой юникода, юнит-тестами и прочими фишками есть dlang.org/blog/2017/08/23/d-as-a-better-c
Абстрактный сферический конь в вакууме быстрее абстрактного додэкаэдрического на 10-15%.
после фразы
[q]Освоение языка C требует на порядок меньших усилий, значит, больше людей могут поучаствовать в разработке этого ПО.[/q]

я реально выпал в осадок.
всегда считал, что ОО языки в первую очередь разработаны для более легкого вхождения.
то есть по определению должны быть намного легче в освоении, именно что «на порядок»

А у вас наоборот. Может и ассемблер «требует на порядок меньше усилий в освоении»?
Плюсы с каждым стандартом — это немного свой язык со своими идиомами и deprecated, поэтому лично мне в плюсах сложно.
так и мне сложно.
вообще если с каждым обновлением в язык вводятся новые костыли и депрекатятся старые трюки, то это говорит о неудачности архитектуры языка.
Это относится и к крестам и к джаве.
вообще если с каждым обновлением в язык вводятся новые костыли и депрекатятся старые трюки… то это обозначает, что этим языком люди реально пользуются, только и всего.

Никогда и никак не меняются языки, которыми пользуются 3 с половиной разработчика и которых «и так всё устраивает».
Сотни тысяч разработчиков на С с вами не согласятся.
Да в новых стандартах и С умудрились испоганить, но тут уж… увы, от хайперов никуда не денешься.
А что не так с С? В С99 много хороших обновлений, в С1Х мне generic macro очень понравился, с ним реально проще, а вот зачем threads нужны при наличии posix threads мне неясно
ну вот про такие мелочи я и говорю
тут рядом статья «Почему опытные разработчики пишут тупой код и как распознать новичка за километр» примерно об этом и говорит

писать с использованием новомодных оборотов — не есть бест-практис
только если ты сам или сообщество в целом не реализовывали эти новомодные обороты самостоятельно и с накладными расходами до появления их в языке.
что вполне может говорить об изьянах проектирования. можно самого себя загнать в такой тупик из которого только новомодными оборотами и можно вылезти
Новомодные? :-) Вы про восемнадцатилетний C99? :-) Или, быть может, про семилетний C11?

Эти стандарты почти ничего не изменили в самом языке, только чуть пригладили шероховатости.
Наверное затем, что posix threads к С отношения не имеют. Языку давно нужны были подобные абстракции, которые и так все пишут каждый раз заново, то и дело делая ошибки, плодя уязвимости. Еще бы туда IO кроссплатформенный и много боли на ровном месте можно было бы избежать. Язык то приятный, если бы не все эти тонны велосипедов, которые надо каждый раз самому писать, потому что готовое в большинстве случаев либо вообще отсутствует, либо в составе громадной библиотеки, которую лишний раз тащить не хочется.
На самом деле проблема в Windows. posix_threads там нету, а C — есть. Потому пришлось сделать вот такой костыль.

Но на самом деле проще считать его «бесплатным» дополнением к атомикам и модели памяти, позволяющей, в рамках стандарта, писать программы совместымые к posix_treads/windows thread.

Ну просто странно выглядел бы стандарт, где атомики и мьютексы были бы, а никакого способа создать поток — не было бы в принципе…
Есть у меня подозрения, что далеко не windows сподвиг их это сделать, будучи довольно непопулярной платформой для C. Visual Studio не планирует даже C99 поддерживать полностью, чего о C11 говорить.

Да и просто посудить, всегда приятнее иметь что-то в стандартной библиотеке, а не какой никакой, но внешней зависимости. Куда больше гарантий это дает.
Ассемблер, несомненно требует меньше усилий на освоение и в приличных курсах изучается до C и/или C++.

Другое дело, что большие программы на нём писать сложно.
… и маленькую программу делающую хоть что-нибудь осмысленное на ассемблере написать намного cложнее.

Вон автор обзора написал что программу на С писал три вечера, а на С++ за один вечер управился. Это говорит о том что на базовом уровне С++ намного проще
Для чего собственно его и придумывали.
хорошая статья, спасибо. а можно ещё привести время, потраченное программистом на написание и отладку этого дела, в с и с++? понятно, что второй раз реализовывать тоже самое быстрее, но хотелось бы увидеть некоторые данные — написание с версии, отладка с, написание с++, отладка с++, оптимизация с++. хотя бы примерно. спасибо!
Ну, здесь цифры вообще субъективны вконец. То есть, я просто из головы постараюсь вспомнить, сколько вечеров факультативно я потратил на это. Скажем так, Написание на C заняло три вечера. И я сделал много ошибок с работой с указателями и неправильного преобразования типов, которые, естественно, компилятором не отслеживались. То есть, я сидел в gdb и пытался понять, а что это вообще тут за байты. Скажем, вечер.

На C++ я переписал всё это за вечер, и программа сразу получилась корректной. Благодарить ли тут C++ или уже мою подкованность в только что реализованном алгоритме — не знаю. А вот на оптимизацию со всякими valgrind'ами ещё несколько вечеров ушло.

Кстати, одним пакером я проверял другой, потому что они, разумеется, должны выдавать абсолютно одинаковый результат на одном и том же входном файле. И по ходу оптимизации я пару раз ломал C++ имплементацию. И нашёл ещё таким же образом ошибку в C версии. Но тут уже это ни к одному ни к другому не приплюсуешь.
Microsoft компилятор не пробовали? Как правило он генерирует код побыстрее.
Ваш пример это замечательный benchmark для различных компиляторов.

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

Может такой альтернативный подход ( если gcc его поддерживает ) к оптимизации поможет С++. У него, почти наверняка, размер выполнимого больше.
Microsoft компилятор не пробовали? Как правило он генерирует код побыстрее.
Эта… вы какие вещества-то потребляете? И почему не делитесь?

Microsoft — это самый медленный из «живых» компиляторов. И всегда был самым медленным. Так-то, «на спор» можно программу под любую пару компиляторов написать так, чтобы показать, что компилятор A быстрее компилятора B, но на практике — я не видел кода, не заточенного специально под MSVC, который бы работал быстрее при компиляции этим недоразумением.

P.S. Хотя, впрочем, стоит признать что они делают большие успехи. Последние версии уже обычно сравнимы по скорости с компиляторами и если бы не отдельные приступы сумасшествия (типа такого), то MSVC можно было бы уже реально использовать для написания быстрого кода…
Не стоит заниматься словоблудием.

Автор применил в проэкте некоторые устройства из не стандартных библиотек и #include, которые реализованы только в поздних версиях С стандарта. Что форсирует использование компилятора от Microsoft Visual Studio 2017, а он в свою очередь с одной стороны лучше отлажен но утяжелен воплощением этого самого нового стандарта. Поэтому у меня есть серьёзные причины считать, что если программы «общего пользования» «с ходу» работают побыстрее, именно вот такой крайний случай, для которого создано приложение — на перегонки — будут проблемы.

Пока что работа застопорилась. Тем более мне надо отладить некоторые вещи не связанные с этим примером.

Но хотел бы отметить что компилятор Microsoft в моих экспериментах по выполнению простых арифметических упражнений на скорость в сравнение с Linux gcc показывал скорости от 1,5 до 2х раз быстрее вплоть до 2005-го года. Когда у меня не осталось времени и желания собирать дистрибутивы Linux.
Поэтому у меня есть серьёзные причины считать, что если программы «общего пользования» «с ходу» работают побыстрее, именно вот такой крайний случай, для которого создано приложение — на перегонки — будут проблемы.
У нас история, как правило, ровно противоположная: на известных и/или тривиальных бенчмарках MSVC отрабатывает «супер», а вот на реальных программах — тормозит. Да, конечно, в большинстве случаев происходит это из-за того что он отказывается инлайнить какую-нибудь одну функцию или выкидывать какой-нибудь один кусок кода — но от этого не легче. Потому что совершенно непонятно — что именно в этом конкретном куске или функции его не устраивает и что с этим делать.

Но хотел бы отметить что компилятор Microsoft в моих экспериментах по выполнению простых арифметических упражнений на скорость в сравнение с Linux gcc показывал скорости от 1,5 до 2х раз быстрее вплоть до 2005-го года. Когда у меня не осталось времени и желания собирать дистрибутивы Linux.
Вы всерьёз сравниваете качество кода, сранивая программы собранные для разных платформ? Да ещё, небось, PIC-код с фортификацией для GCC и не-PIC для Windows?

Извините — но это «ни в какие ворота» не лезет. Да, тот факт, что Windows зачастую делает выбор в пользу скорости, а Linux — в пользу переносимости и безпасности, но причём тут компилятор?

И PIC и фортификацию можно отключить, если есть такое желание, а проще всего — сравнивать msvc с mingw (однако там тоже есть подводные камни: SEH vs SJLJ, например).
Похоже опция -О3 подключает оптимизацию по уменьшению размера выполнимого. Когда — то эта опция была эксклюзивной, компилятор оптимизировал или по размеру, или по инструкциям, оптимизация по инструкциям как правило увеличивала выполнимый и удлиняла время выполнения программы :).
ак как язык C является частью языка C++

Язык C никогда не являлся и не является, по крайней мере в реализациях стандартов C99 и С11, частью языка C++. Какие-то старые особенности присутствуют, но самых вкусных там нет. В частности, куча дыр в инициализации структур, массивов и прочих нужных штук. Со статической инициализацией в C++ как-то вообще не сложилось — полиморфизм мешает.

Ну на сколько я понял суть статьи, в очередной раз вівели разницу между низкоуровневым и высокоуровневым языком. С — воплощает собой философию Unix/Linux, потому именно на нем много кода для них и написано. Одна задача — одна программа. С++ это уже все же "швейцарский нож". Все удобнее, все быстрее пишется, зачастую код еще оказывается более переносимый, есть готовые модули, но плата за это производительность. Да можно оптимизировать и добиться лучших результатов если поставтить цель, но в реальной жизни в 90% никто этого делать не будет. Мало кто выбирает язык программирования и пишет и использует его вразрез принятых в конкретном языке парадигм.
Поэтому язык нужно выбирать как раз из этих критериев в первую очередь. Нужно максимальное быстродействие, не нужна переносимость, нет необходимость в чрезвычайно сложных структурах данных и объектах, размер проекта и сроки не ужаты сверх меры в угоду менеджерских целей — можно делать на С. Если же сроки сжаты, колличество меньше, чем хотелось бы и охватить нужно область знаний большую чем вы детально понимаете, а также вы не хотите выжать максимум на каждой операции и вообще вы полагаете что для данного проекта необходимо в несколько раз больше людей/команд — быстрее будет сделать на С++.

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

а также проблемы при сопровождении, если программа превышает некий предел размера (порядка 5000-10000 строк). Из-за неустойчивости кода — ты никогда не знаешь, действительно ли оператор присвоения это просто оператор присвоения. Ты также никогда не знаешь, какой метод будет реально вызван из этого места поскольку у тебя с десяток классов, наследующих один и тот же класс и имеющих одинаковое имя.
Вы про абстракцию (перегрузка операторов) и полиморфизм?
НЛО прилетело и опубликовало эту надпись здесь
Просто достало, извините. Никто из больших адептов C++ перезагрузки операторов не хочет заниматься сопровождением и отладкой больших чужих C++ комплексов.
НЛО прилетело и опубликовало эту надпись здесь
Работа такая. Нужен результат, а он помимо моего, включает в частности и выполнение огромного и чужого кода, который считается написаным и работающим. Но он не работает… иногда, лезешь в него, а там… Становиться специалистом по этому коду и назубок знать, что там перезагружено и какие классы для чего — менять профиль, а рез-т все-таки нужен, и ясно, что где-то элементарная описка. Но где?
а вы хотели править код не вникая?

Согласен с LeonidY. Чтобы понять нормальный код, надо только прочитать его и знать язык, на котором он написан. Чтобы сделать аналогичное в обсуждаемом случае — надо либо действовать наугад, либо на каждую строчку кода сверяться ещё с десятком файлов, раскиданных по разным местам. Это обстоятельство никак радовать не может. Причём тут два аспекта.
Первый — уже упомянутая перегрузка стандартных операций, когда, глядя на строчку, непонятно, настоящее ли там присваивание или вызов вороха вложенных функций. И это — особенность именно C++.
Второй — когда вызывается функция (и видно, что это функция), может потребоваться длительное время, чтобы найти её исходный код среди кучи мусора, которым наполнен код проекта. Это — не особенность C++, а особенность плохо спроектированого ООП-кода. То есть, можно написать программу на C++ и без этого дефекта (при этом пользуясь его преимуществами), но, к сожалению, массовым кодерам проще писать с ним. Ну и встречается эта проблема не только в C++, но и, например, в Java и даже в PHP (последние модные тенденции которого представляют из себя бездумную кальку с Java), где оно накладывается на общую дефективность языка.

И то, и другое — последствия плохого проектирования.

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

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

Чтобы понять нормальный код, надо только прочитать его и знать язык, на котором он написан

… и библиотеки, которые используются в коде. В случае c++ — это зачастую stl/boost/qt
Первый — уже упомянутая перегрузка стандартных операций, когда, глядя на строчку, непонятно, настоящее ли там присваивание или вызов вороха вложенных функций

сами по себе операторы безвредны. Взять тот же std::complex — ну кому вместо c = -a*b+d; захочется писать вызов нескольких функций? Проблемы начинаются только если реализация оператора противоречит его назначению. Скажем, если operator == меняет наблюдаемое состояние объекта, это явно отвратительный код.
НЛО прилетело и опубликовало эту надпись здесь
Да. Бесконтрольное использование этого ведет к проблемам, и я не слышал о серьезных попытках их ограничить или облегчить анализ-отладку. Я работал только с одним случаем, когда это все сильно помогло и делало программу достаточно компактной — C++ парсер для ASN1 сообщения, который (парсер) генерировался. Правда код совершенно нечитаемый получался, и ходят слухи, что один из авторов жаловался, что компиляторы с Algol68 не очень распространены, код для Algol68 сгенерить можно было бы еще проще.
Ну что значит «бесконтрольное»? Программист знает, что делает, ему кажется важным в этом месте выразить мысль через перегрузку. Если он делает что-то плохое, неочевидное в этом месте, то это его проблема, а не инструмента.
Второй момент состоит в том, что вы не были готовы к особенностям языка (возможность, а порой и необходимость перегрузки операторов). Опять же, почему в этом виноват язык?

ЗЫ Кмк, тут можно злиться на то, что в C++ малоинформативные ошибки, если не выбрасывается исключение, то без должного логгирования понять где возникла беда очень сложно.
Хорошо было графики привести с результатами тестирования, а то текст плохо смотрится.
Есть ли смысл писать на чисто С если для «С++» можно писать тот же С + некоторые плюшки от C++, например namespace/class/try-catch, без использования монструозных с++ либ.

Не думаю что namespace могут как-то просадить производительность, а 0-cost исключения могут даже её повысить. Поэтому какой смысл опускаться до чистого С когда можно испольовать С++ «по минимому»? (не считая сред где нет с++ компиллятора).

Плюс появляется возможность выбрать уровень между фичами и производительностью.
Я тут в качестве паранойи решил проверить, что если скомпилировать чистую C программу в C++ режиме. Взял ту же C версию, что использовал для статьи, вообще не переписывал, только расширение файла на cpp поменял, чтобы cmake считал, что у меня тут C++. В паре мест, конечно, пришлось синтаксически подправить, но без изменения структуры/логики вообще. Ожидал, что версия C и версия «C в C++ режиме» будут давать абсолютно одно и то же. Ну, погрешность измерения, понятно. Но результаты должны перекрываться с учётом погрешности. Был удивлён, когда обнаружил, что результаты не перекрываются. Да, просадка по производительности псевдо-C++ версии была всего в 2%. Но она была заметна. Удивлён.

Так что для себя сделал вывод, что, если какой-то кусок проекта хочу написать на C, то это будет чистый C, компилируемый C компилятором. Если уж выдавливать по максимуму.

(Компилятор, как и прежде — gcc-5.4.0 в Ubuntu 16.04)
Грустно, я тестировал такой (бесмысленный) пример, что-бы проверить скорость исключений в с++:
-O3
* C: 100% (~1.14sec)
* C++: 99.73%
* C++ exception: 84.6%

-O2
* C: 100% (~2.12sec)
* C++: 100.51%
* C++ exception: 74.02%

Результат — % от времени, что показал С, меньше % — лучше.

В результате С++ оказался не хуже, а версия с исключениями стала до +35% быстрее.
Результат — среднее из 3-х запусков (разброс незначительный).
Компилятор тот же: gcc-5.4.0 в Ubuntu 16.04.
Взял этот btree тест, исходник для С (но вырезал apt и многопоточность*), получилось:
* C: 9.823 sec
* C++: 9.786 sec
Попробовал тот же исходник, apr оставил, omp убрал. Результаты для C и C++ плавают на одном и том же уровне. Вы по одному эксперименту делали? Я по 5 раз запускаю. Опции одни и те же: -O3 -Wall -march=native.
11.79
11.81
12.28
11.88
11.87
mean: 11.92

C++
12.16
11.90
12.41
11.82
11.85
mean: 12.02

Имхо, подобные расхождения, это уже малоинформативно, тут куча разных факторов типа фазы луны роль играть начинают. Наверное, не стоило пытаться сравнивать одну и ту же программу, собранную gcc и g++.
Я использовал только -O3 (как бы я (не профи) собирал свои проги), проверил остальные опции — С стал чуть быстре, но незначительно менее 1%.

подобные расхождения, это уже малоинформативно, тут куча разных факторов типа фазы луны роль играть начинают
И это ключевой вывод который был нужен мне. Т.к. для меня оно означает, что нет смысла использовать чистый С, когда этот же С можно писать с некоторыми фичами С++ без потери производительности.
Имхо, подобные расхождения, это уже малоинформативно, тут куча разных факторов типа фазы луны роль играть начинают.
Именно. Вот, например: 10% разницы на том же самом байт-в-байт коде.

Наверное, не стоило пытаться сравнивать одну и ту же программу, собранную gcc и g++.
Почему нет? Стоило. Но разницу менее, чем в 10%, конечно, в таком «грубом» эксперименте стоит проигнорировать.
Этот тест:
*C: 4.451 sec
*C++: 4.440 sec

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

А сишную версию не пробовали оптимизировать?

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

Публикации