Комментарии 172
На второй вопрос я для себя ответил так:
На самом деле ответ на второй вопрос кроется в первом пункте заключения. А именно — при прочих равных написание на C сразу ведёт к эффективному коду без излишеств, по сравнению с C++, хоть и занимает, вероятно, больше времени. Простой выбор между качеством результата и скоростью разработки. C++ в этом плане занимает нишу, среднюю между C и PHP — в последнем эффективность продукта по умолчанию вообще не учитывается в угоду финансовой эффективности разработки.
За heartbleed, за goto fail, за poodle, и за всё прочее в том же духе. А еще за 5 слоёв виртуализации над JS: 1) js-песочница рабочего процесса браузера, 2) пользовательские права процесса, 3) ring-3, 4) ring-0, 5) гипервизор, на случай если враг прорвался в ring-0.
И всё это написано на быстрой сишечке. Настолько быстрой, что как в анекдоте про секретаршу со скоростью набора 600 знаков в минуту «но такая фигня получается!».
Но вы ответили на мой вопрос, я так и подумал: зелень с завышенным самомнением и стадным инстинктом — если минус поставили, значит и я поставлю…
Далее, когда вы пишите на голых сях, у вас код уже более оптимальный, т.е. вы меньше времени тратите на оптимизацию, а учитывая предыдущий абзац, он просто оптимальнее, потому как ни кто вам не даст много времени на оптимизацию…
Ещё один момент — обучение. Когда вы решаете задачу на плюсах, например, пишете 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 плюсам при прочих равных… Я люблю заморочиться (когда есть возможность) оптимизацией структур и алгоритмов под конкретную задачу, мне это жутко интересно. Потому да, я достаточно часто пишу новые структуры и алгоритмы.
Во-вторых, вы уверены, что не зная меня, можете делать выводы, полезной работой я занимаюсь или нет?
Вы третьих, вы уверены, что всё то, что я написал явно хуже того, что уже написано?
Если бы было так, как вы пишете, то была бы всего одна реализация хеш-таблицы, дерева, списка и т.п.
Судя по тону и минусу, ты таки и есть тот судент-максималист… Изыди, зануда…
Во-вторых, вы уверены, что не зная меня, можете делать выводы, полезной работой я занимаюсь или нет?
Вы третьих, вы уверены, что всё то, что я написал явно хуже того, что уже написано?
Обычно те, кто может написать лучше, только этим и занимается
вы уверены, что не зная меня, можете делать выводы
ты таки и есть тот судент-максималист… Изыди, зануда…
лицемерие
Хеш-таблица, интегрированная в структуру конкретных данных и оптимизированная под неё же на уровне кода (а не настроек) будет чуть-чуть оптимальнее работать, например. Не думаю, что там прямо таки огромный отрыв в быстродействии будет, но всё же польза какая-то есть.
Интересно, а как бы rust себя а этом случае повёл.
Как раз думал написать на расте и сравнить. Но для этого предпочел бы sample-1
из задачи чтобы приложили к исходникам, для пущей убедительности.
Если написано func1(), то простой поиск по большинству C++ приложений показывает как минимум 2-3 функции с таким именем, и даже число и тип параметров совпадает — inheritance, sir!
std::vector<bool>
?Всё, что про него нужно знать: не используйте. Точка. Он пытается имитировать указатели на биты — и в результате делает это всё равно не слишком корректно, но работает медленно и плохо.
Ну так есть же std::bitset
Беда в том, что
std::vector<bool>
пытается решать сразу много задач — и все решает плохо.При этом написать замену, решающую конкретно вашу, ограниченную задачу хорошо — обычно несложно, 150-200 строк кода, может чуть больше…
Так что… покажите задачу — можно будет обсуждать замену…
Кстати, а попробуйте компилятор clang, интересно какие у него будут результаты для версий на С, С++ и оптимизированной версии на С++.
в абсолютном плане clang генерирует код чуть-чуть медленнее, чем gcc.Не понял, кто медленный. Сам компилятор, или код, им скомпилированный?
Мы тут давеча соревновались с коллегой в вопросах Rust vs чистый Си, и я выигрывал стабильно 5-10% просто в счет того, что GCC ведет в оптимизациях.
Но вот ей богу, один и тот же прожект, с одними и теми же бенчмарками у меня стабильно показывает от 5% преимущество за GCC.
Опять же, повторюсь, речь идет о чистом Си, плюсы — другая история.
www.phoronix.com/scan.php?page=article&item=gcc-clang-eoy2017&num=1
Если вам тоже лень, то у Фороникса выходит, что где-то в 7 из 10 случаев GCC ведет. Хотя разника, конечно, уже совсем не та, что бывало.
Может статься, вы нашли те самые 3 из 10, да и вообще — дело это быстроменяющееся в наши-то дни.
Получается, что более элегантный код на С++ требует больше усилий, чтобы достичь той же производительности, чем код на С.
Неужели? Написать работающую версию на плюсах проще. А оптимизация еще не факт что вообще понадобится.
Сила питона — в способности быстро, дешево и грязно склеивать в решение задачи библиотеки на других языках.
Задача из этой статьи — явно не для питона, слишком низкоуровневая и высокопроизводительная.
В этом случае отсутствие нужды в оптимизации для плюсов не означает ее отсутствия для питона, а статическая типизация и детерминированная стоимость абстракций играют большую роль, чем возможность быстро наваять прототип.
Если же я пишу сложную управляющую логику с очень разнообразными действиями, не сводящимися к «повторить вычисления A, B, C миллион раз», то лучше взять на вооружение всю мощь типизированного C++.
А может быть тогда имеет смысл взять что-то еще более высокоуровневое, чем C++? Что-то не требуещее компиляции по несколько минут (более-менее сложного кода), имеющее кучу удобных библиотек, позволяющее легко писать тесты? Не то, чтобы я был противником C++ — сам пишу на нем уже лет 15, но вот для своих личных экспериментов последние пару лет реально тянет писать например на связке Python + C.
Если задача — запускать в нужном порядке по определенным условиям число-молотилки или какие-нибудь IO (реализованные опять же на C) — то 2 порядка замедления питона погоды не сделают. Наглядный пример — Keras, который является очень удобной оберткой над в том числе Tensorflow, половина которого опять же на питоне (вторая половина как раз на C/C++), и в питоновском коде программы от силы 10% времени проводят. И тут хоть в 100 раз этот питон ускорить — ощутимого эффекта не будет. Зато продуктивность с этим самым питоном за счет высокоуровневых конструкций и быстрой обратной связи (не надо ждать компиляции, проще организовать автотесты) на порядок выше.
Или думаете, что и через 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() что не особо то и усложнит задачу.
Но у него другие — более противные и важные недостатки для серьезных задач.
Реальной замены С++ в достаточно большой области применения нет, и в ближайшем будущем и не ожидается.
Статья хороша именно анализом мест просадок — это лишние системные вызовы аллокаций.
Реальной замены С++ в достаточно большой области применения нет, и в ближайшем будущем и не ожидается.
Rust? Go?
На бекендах серверов го сразу кончается там, где начинается что-нибудь не IO-bound.
Сборщик мусора у го отнюдь не быстрый, регулярки тоже, для сложной логики сам язык подходит примерно никак.
А что у него не так со сборщиком?
Почему сразу не так? Сборщик мусора у го оптимизирован под минимизацию времени одиночной паузы и сознательно лишен гибкости в плане настроек.
Как результат и по скорости аллокаций, и по скорости очистки, и по потреблению ресурсов (и практически по всему остальному, кроме максимальной длительности одиночной паузы) сборщик мусора го уступает и яве, и дотнету.
На хабре уже была довольно подробная статья на эту тему:
https://habrahabr.ru/company/mailru/blog/318504/
Удобство/скорость разработки питона и производительность компилируемых языков в одном флаконе.
Сейчас как раз принял решение в одном проекте менять стек языков. Один черт все переписывать. Кресты хороши в числодробилках, но слишком дороги в мозгодробилках.
Для 70% процессорного времени у меня и так используются библиотеки которые есть на большинстве языков, так что нативный си подключить не сложно. Если Cython будет медленнее раза в два-три, то бутерброд из пайтона, Cython и нативного си будет суммарно уступать варианту полностью на крестах на 10-20%, что более чем допустимо учитывая что кодовая база будет достаточно монолитная, с пайтона на другие популярные языки (пхп, javascript, java) значительно проще чем с крестов.
А вот если там будет десятичный порядок или больше, то общая скорость упадет раза в два, и это уже ощутимо…
Статья же описывает гипотетическую ситуацию, где весь проект как разрабатывался, так и разрабатывается на C++, но кое-где можно перейти на C, да хоть бы и оставив расширения файлов cpp.
(7.65 seconds)
Не совсем понял. Разница в 10 раз? У вас первая версия была, вроде на десяток процентов медленнее, но в какой-то момент пошла вот такая разница.
Кроме того, если вы реализуете один и тот-же алгоритм кодирования, разве не должен он на выходе давать одинаковый результат с точностью до бита (чего у вас нет)?
Но результаты тестирования алгоритма архивации были слишком… архивированны :)
Следите за руками:
Сначала приведено сравнение Си и крестов на большом блоке.
Потом приведено сравнение Си и крестов на маленьком блоке.
Потом была проведена оптимизация крестового кода, и приведено…
Нет, не то что интуитивно ожидаемо для наглядности (результаты Си и новой версии крестов для большого блока, потом результаты Си и крестов для маленького) а только то, что изменилось, а именно два результата новой версии крестов — для большого блока и для маленького блока.
С дальнейшей оптимизацией тоже самое.
Так что для того чтобы сравнить результаты после оптимизации нужно взять результаты после оптимизации и сравнить их с результатами Си из первых тестов.
ПС: мне было лень листать назад и я просто читал общий вывод (быстрее на хх%, медленнее на уу%).
При анализе инструментарием «callgrind» видно, что много инструкций тратится на работу с кучей — malloc и free.
priority_queue реализовано самостоятельно
Ваша реализация priority_queue — это прямо-таки бенчмарк для аллокатора. По значению надо всё хранить. std::priority_queue
должен быть ощутимо шустрее.
Но вот очеред с приоритетами и stl должна хорошо себя показать.
Чтобы стала ещё лучше, советую сравнение результатов оформить в виде таблиц, либо картинок. Спасибо!
А "скорость" компиляции C++ — часом не от того, что просто подключается больше хедеров?
Не просто хедеров а хедеров с шаблонами и прочей “черной магией“ плюсов.
Для сравнения — Паскаль — 1-проходный компилятор.
Понять, что дело вовсе не в этих 7 проходах достаточно просто: переименуйте .c файл в .cc файл, исправьте несколько мест, которые перестанут компилироваться, и замерьте время компиляции.
Тормозят в C++ шаблоны и связанная с ними «магия», а не 7 проходов…
Dlang, например, умеет в разы больше метапрограммирования и магии, но компилирует даже большой проект в секунды.
Сейчас плюсы имеют такие жуткие проблемы с заголовками не из-за проходов оптимизатора, отнюдь.Советую на досуге собрать-таки какой-нибудь проект с -O0. Вы будете приятно (или неприятно, я не знаю) удивлены.
По идее шаблоны, или как их называют «generics», по — Русски наверное было бы точнее сказать «переменные типа данных» в с# формально отличаются от С++ тем, что в с# их развертка происходит во время выполнения, а в С++ во время сверстки ( компиляции ) исходных текстов.
Но если отвлечься от формального описания, и попытаться уловить, в чем собственно суть дела, то в С++ оповещение о переменной «тип данных» происходит за счет, так называемых, «include», который передается издательством. В с# «include» распространяется вместе с сверстанным ( откомпилированным ) блоком, в виде метаданных.
В принципе, при всем желании С++ сильно отличаться от С ( и если его напичкать ещё большим количеством стандартного наполнения, то он станет работать ещё в разы медленнее и возможно станет ещё не — похожее ) здесь он копия С. Поскольку его сверстанный интерфейс ни коим образом не привязан к опубликованным в «include» вызовам интерфейса. И если в с# достаточно вставить электронную подпись, чтобы эту связь сделать принудительной, то в С++ это сделать нельзя.
«при всем желании С++ сильно отличаться от С»
То, что Вы описываете, это детали синтаксиса языка. Их всегда можно уточнить в Википедии. За что сердечно признателен :)
То, что Вы описываете, это детали синтаксиса языка.Превращение чего-то, выполняющегося за ограниченное время в что-то, полное по Тьюрингу — это, я извиняюсь, далеко не «детали».
Так как язык C является частью языка C++
Отличное начало, так держать. Cи не является подмножеством С++.
typedef struct array{
size_t size;
int data[0];
} array;
[q]Освоение языка C требует на порядок меньших усилий, значит, больше людей могут поучаствовать в разработке этого ПО.[/q]
я реально выпал в осадок.
всегда считал, что ОО языки в первую очередь разработаны для более легкого вхождения.
то есть по определению должны быть намного легче в освоении, именно что «на порядок»
А у вас наоборот. Может и ассемблер «требует на порядок меньше усилий в освоении»?
вообще если с каждым обновлением в язык вводятся новые костыли и депрекатятся старые трюки, то это говорит о неудачности архитектуры языка.
Это относится и к крестам и к джаве.
Никогда и никак не меняются языки, которыми пользуются 3 с половиной разработчика и которых «и так всё устраивает».
Да в новых стандартах и С умудрились испоганить, но тут уж… увы, от хайперов никуда не денешься.
тут рядом статья «Почему опытные разработчики пишут тупой код и как распознать новичка за километр» примерно об этом и говорит
писать с использованием новомодных оборотов — не есть бест-практис
Эти стандарты почти ничего не изменили в самом языке, только чуть пригладили шероховатости.
Но на самом деле проще считать его «бесплатным» дополнением к атомикам и модели памяти, позволяющей, в рамках стандарта, писать программы совместымые к posix_treads/windows thread.
Ну просто странно выглядел бы стандарт, где атомики и мьютексы были бы, а никакого способа создать поток — не было бы в принципе…
Да и просто посудить, всегда приятнее иметь что-то в стандартной библиотеке, а не какой никакой, но внешней зависимости. Куда больше гарантий это дает.
Другое дело, что большие программы на нём писать сложно.
На C++ я переписал всё это за вечер, и программа сразу получилась корректной. Благодарить ли тут C++ или уже мою подкованность в только что реализованном алгоритме — не знаю. А вот на оптимизацию со всякими valgrind'ами ещё несколько вечеров ушло.
Кстати, одним пакером я проверял другой, потому что они, разумеется, должны выдавать абсолютно одинаковый результат на одном и том же входном файле. И по ходу оптимизации я пару раз ломал C++ имплементацию. И нашёл ещё таким же образом ошибку в C версии. Но тут уже это ни к одному ни к другому не приплюсуешь.
Ваш пример это замечательный 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, например).
ак как язык C является частью языка C++
Язык C никогда не являлся и не является, по крайней мере в реализациях стандартов C99 и С11, частью языка C++. Какие-то старые особенности присутствуют, но самых вкусных там нет. В частности, куча дыр в инициализации структур, массивов и прочих нужных штук. Со статической инициализацией в C++ как-то вообще не сложилось — полиморфизм мешает.
Ну на сколько я понял суть статьи, в очередной раз вівели разницу между низкоуровневым и высокоуровневым языком. С — воплощает собой философию Unix/Linux, потому именно на нем много кода для них и написано. Одна задача — одна программа. С++ это уже все же "швейцарский нож". Все удобнее, все быстрее пишется, зачастую код еще оказывается более переносимый, есть готовые модули, но плата за это производительность. Да можно оптимизировать и добиться лучших результатов если поставтить цель, но в реальной жизни в 90% никто этого делать не будет. Мало кто выбирает язык программирования и пишет и использует его вразрез принятых в конкретном языке парадигм.
Поэтому язык нужно выбирать как раз из этих критериев в первую очередь. Нужно максимальное быстродействие, не нужна переносимость, нет необходимость в чрезвычайно сложных структурах данных и объектах, размер проекта и сроки не ужаты сверх меры в угоду менеджерских целей — можно делать на С. Если же сроки сжаты, колличество меньше, чем хотелось бы и охватить нужно область знаний большую чем вы детально понимаете, а также вы не хотите выжать максимум на каждой операции и вообще вы полагаете что для данного проекта необходимо в несколько раз больше людей/команд — быстрее будет сделать на С++.
а также проблемы при сопровождении, если программа превышает некий предел размера (порядка 5000-10000 строк). Из-за неустойчивости кода — ты никогда не знаешь, действительно ли оператор присвоения это просто оператор присвоения. Ты также никогда не знаешь, какой метод будет реально вызван из этого места поскольку у тебя с десяток классов, наследующих один и тот же класс и имеющих одинаковое имя.
Согласен с LeonidY. Чтобы понять нормальный код, надо только прочитать его и знать язык, на котором он написан. Чтобы сделать аналогичное в обсуждаемом случае — надо либо действовать наугад, либо на каждую строчку кода сверяться ещё с десятком файлов, раскиданных по разным местам. Это обстоятельство никак радовать не может. Причём тут два аспекта.
Первый — уже упомянутая перегрузка стандартных операций, когда, глядя на строчку, непонятно, настоящее ли там присваивание или вызов вороха вложенных функций. И это — особенность именно C++.
Второй — когда вызывается функция (и видно, что это функция), может потребоваться длительное время, чтобы найти её исходный код среди кучи мусора, которым наполнен код проекта. Это — не особенность C++, а особенность плохо спроектированого ООП-кода. То есть, можно написать программу на C++ и без этого дефекта (при этом пользуясь его преимуществами), но, к сожалению, массовым кодерам проще писать с ним. Ну и встречается эта проблема не только в C++, но и, например, в Java и даже в PHP (последние модные тенденции которого представляют из себя бездумную кальку с Java), где оно накладывается на общую дефективность языка.
Не важно какой там ворох функций вызывается из оператора присваивания если результатом является присваивание.
Не важно, до тех пор пока речь не идёт об отладке. А ошибки могут быть в том числе и внутри этого псевдо-присваивания, как явные (которые сразу делают исключение или краш), так и неявные, которые просто записывают куда-то неверные данные, проявляющиеся неожиданно позднее.
Не говоря уже о том, что если без этого всего понятно, что присваивание — это MOV или что-то похожее в ассемблерном листинге, а с этим там может быть что угодно, никак с первого взгляда на присваивание не похожее.
Чтобы понять нормальный код, надо только прочитать его и знать язык, на котором он написан
… и библиотеки, которые используются в коде. В случае c++ — это зачастую stl/boost/qt
Первый — уже упомянутая перегрузка стандартных операций, когда, глядя на строчку, непонятно, настоящее ли там присваивание или вызов вороха вложенных функций
сами по себе операторы безвредны. Взять тот же std::complex — ну кому вместо c = -a*b+d; захочется писать вызов нескольких функций? Проблемы начинаются только если реализация оператора противоречит его назначению. Скажем, если operator == меняет наблюдаемое состояние объекта, это явно отвратительный код.
Второй момент состоит в том, что вы не были готовы к особенностям языка (возможность, а порой и необходимость перегрузки операторов). Опять же, почему в этом виноват язык?
ЗЫ Кмк, тут можно злиться на то, что в C++ малоинформативные ошибки, если не выбрасывается исключение, то без должного логгирования понять где возникла беда очень сложно.
Не думаю что namespace могут как-то просадить производительность, а 0-cost исключения могут даже её повысить. Поэтому какой смысл опускаться до чистого С когда можно испольовать С++ «по минимому»? (не считая сред где нет с++ компиллятора).
Плюс появляется возможность выбрать уровень между фичами и производительностью.
Так что для себя сделал вывод, что, если какой-то кусок проекта хочу написать на 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.
* C: 9.823 sec
* C++: 9.786 sec
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++.
подобные расхождения, это уже малоинформативно, тут куча разных факторов типа фазы луны роль играть начинаютИ это ключевой вывод который был нужен мне. Т.к. для меня оно означает, что нет смысла использовать чистый С, когда этот же С можно писать с некоторыми фичами С++ без потери производительности.
Имхо, подобные расхождения, это уже малоинформативно, тут куча разных факторов типа фазы луны роль играть начинают.Именно. Вот, например: 10% разницы на том же самом байт-в-байт коде.
Наверное, не стоило пытаться сравнивать одну и ту же программу, собранную gcc и g++.Почему нет? Стоило. Но разницу менее, чем в 10%, конечно, в таком «грубом» эксперименте стоит проигнорировать.
А сишную версию не пробовали оптимизировать?
Сравнение производительности C и C++ на примере сжатия Хаффмана