Подтвердить что ты не робот

Стиль С++ и производительность?

Стиль С++ и производительность - использует вещи C-стиля, которые быстрее некоторых эквивалентов С++, что плохой практике? Например:

  • Не используйте atoi(), itoa(), atol() и т.д.! Используйте std::stringstream < - вероятно, иногда это лучше, но всегда? Что плохого с помощью функций C? Да, C-стиль, а не С++, но что? Это С++, мы постоянно ищем производительность.

  • Никогда не используйте исходные указатели, вместо этого используйте интеллектуальные указатели. Хорошо, они действительно полезны, все знают, что я знаю это, я использую все это время, и я знаю, насколько они лучше, чем исходные указатели, но иногда совершенно безопасно использовать исходные указатели. Почему бы и нет? "Не стиль С++? < - достаточно ли этого?

  • Не используйте побитовые операции - тоже C-стиль? WTH? Почему бы и нет, когда вы уверены, что делаете? Например - не выполняйте поразрядный обмен переменных (a ^= b; b ^= a; a ^= b;) - используйте стандартный трехшаговый обмен. Не используйте сдвиг влево для умножения на два. Etc и т.д. (ОК, это не стиль С++ и C-стиль, но все же "не очень хорошая практика" )

  • И, наконец, самый дорогой - "Не используйте enum-s для возврата кодов, это тоже C-стиль, используйте исключения для разных ошибок"? Зачем? Хорошо, когда мы говорим об обработке ошибок на глубоких уровнях - хорошо, но почему всегда? Что это неправильно с этим, например, когда мы говорим о функции, которая возвращает разные коды ошибок, и когда обработка ошибок будет реализована только в функции, которая вызывает первую? Я имею в виду - нет необходимости передавать коды ошибок на верхнем уровне. Исключения довольно медленные, и они являются исключениями для исключительных ситуаций, а не для.. красоты.

  • и т.д. и т.д. и т.д.

Хорошо, я знаю, что хороший стиль кодирования очень, очень важный < - код должен быть легко читаемым и понятным. Я знаю, что нет необходимости в микро-оптимизации, поскольку современные компиляторы очень умны и Оптимизация компилятора очень эффективны. Но я также знаю, насколько дорого стоит обработка исключений, как реализованы (некоторые) smart_pointers и что нет необходимости в smart_ptr все время.. Я знаю, что, например, atoi не является "безопасным", поскольку std::stringstream есть, но все же.. Как насчет производительности?


РЕДАКТИРОВАТЬ. Я не говорю о некоторых действительно трудных вещах, которые имеют только C-стиль. Я имею в виду - не удивляйтесь использованию указателей функций или виртуальных методов и такого рода вещей, которые программист на С++ может и не знать, если никогда не использовал такие вещи (в то время как программисты C делают это все время). Я говорю о более простых и простых вещах, например, в примерах.

4b9b3361

Ответ 1

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

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

Позвольте включить вопрос в его голову. Иногда безопасно использовать необработанные указатели. Разве это одна причина для их использования? Есть ли что-нибудь о сырых указателях, которые на самом деле превосходят умные указатели? Это зависит. Некоторые типы интеллектуальных указателей медленнее, чем исходные указатели. Других нет. Каково обоснование эффективности использования необработанного указателя на std::unique_ptr или boost::scoped_ptr? Ни у кого из них нет накладных расходов, они просто обеспечивают более безопасную семантику.

Это не означает, что вы никогда не должны использовать необработанные указатели. Просто, что вы не должны делать это только потому, что думаете, что вам нужна производительность, или просто потому, что "это кажется безопасным". Сделайте это, когда вам нужно представить что-то, чего не могут сделать интеллектуальные указатели. Как правило, используйте указатели, указывающие на вещи, и умные указатели, чтобы взять на себя ответственность за вещи. Но это эмпирическое правило, а не универсальное правило. Используйте то, что подходит под задачу. Но не слепо предположите, что исходные указатели будут быстрее. И когда вы используете интеллектуальные указатели, убедитесь, что вы знакомы с ними. Слишком много людей просто используют shared_ptr для всего, и это просто ужасно, как с точки зрения производительности, так и с очень неопределенной совместной семантикой владения, которую вы в конечном итоге применяете ко всему.

Не используйте побитовые операции - тоже C-стиль? WTH? Почему бы и нет, когда вы уверены, что делаете? Например, не выполняйте побитовой обмен переменных (a ^ = b; b ^ = a; a ^ = b;) - используйте стандартный трехшаговый обмен. Не используйте сдвиг влево для умножения на два. Etc и т.д. (ОК, это не стиль С++ и C-стиль, но все же "не очень хорошая практика" )

Это правильно. И причина "быстрее". Побитовый обмен проблематичен по-разному:

  • он медленнее на современном процессоре
  • это более тонко и легче ошибиться.
  • он работает с очень ограниченным набором типов

И при умножении на два умножьте на два. Компилятор знает об этом трюке и будет применять его, если он быстрее. И еще раз, переключение имеет много же проблем. В этом случае он может быть быстрее (поэтому компилятор сделает это за вас), но все же легче ошибиться и работает с ограниченным набором типов. В paticular он может скомбинировать с типами, которые, по вашему мнению, безопасны, чтобы сделать этот трюк с... И затем взорвать на практике. В частности, смещение битов по отрицательным значениям является минным полем. Пусть компилятор переместит его для вас.

Кстати, это не имеет никакого отношения к стилю "С". Точно такой же совет применяется в C. В C, обычный swap все еще быстрее, чем побитовый взломать, а битвыпуск вместо умножения будет выполняться компилятором, если он действителен и если он быстрее.

Но как программист, вы должны использовать побитовые операции только для одной вещи: выполнять побитовое манипулирование целыми числами. У вас уже есть оператор умножения, поэтому используйте его, когда хотите размножаться. И у вас также есть функция std::swap. Используйте это, если вы хотите поменять два значения. Один из самых важных трюков при оптимизации - возможно, удивительно, чтобы написать читаемый, значимый код. Это позволяет компилятору понять код и оптимизировать его. std::swap может быть специализированным, чтобы сделать наиболее эффективный обмен для конкретного типа, в котором он используется. И компилятор знает несколько способов реализации умножения и выбирает самый быстрый из них в зависимости от обстоятельств... Если вы это расскажете. Если вы скажете это вместо бит, вы просто вводите его в заблуждение. Скажите, чтобы оно умножилось, и оно даст вам самое быстрое умножение.

И, наконец, самый дорогой - "Не использовать enum-s для возврата кодов, это тоже C-стиль, используйте исключения для разных ошибок"?

Зависит от того, кого вы спрашиваете. Большинство программистов на C++ я знаю, как найти место для обоих. Но имейте в виду, что одна неудачная вещь о кодах возврата заключается в том, что их легко игнорировать. Если это неприемлемо, то, возможно, вам следует предпочесть исключение в этом случае. Другое дело, что RAII работает лучше вместе с исключениями, а программист на С++ должен обязательно использовать RAII, где это возможно. К сожалению, поскольку конструкторы не могут возвращать коды ошибок, исключения часто являются единственным способом указания ошибок.

но все же.. Как насчет производительности?

Как насчет этого? Любой достойный программист C с удовольствием сказал бы вам, что вы не должны преждевременно оптимизировать.

Ваш процессор может выполнять, возможно, 8 миллиардов инструкций в секунду. Если вы сделаете два звонка на std::stringstream в эту секунду, это собирается сделать измеримую вмятину в бюджете?

Вы не можете прогнозировать производительность. Вы не можете составить руководство по кодированию, которое приведет к быстрому коду. Даже если вы никогда не бросаете единственное исключение и никогда не используете stringstream, ваш код по-прежнему не будет автоматически быстрым. Если вы попытаетесь оптимизировать время написания кода, тогда вы потратите 90% усилий, оптимизируя 90% кода, который почти никогда не выполняется. Чтобы добиться заметного улучшения, вам нужно сосредоточиться на 10% кода, составляющем 95% времени выполнения. Пытаться сделать все быстро, просто приводит к большому количеству потраченного впустую времени с небольшим количеством показаний для него и значительно уродливой базой кода.

Ответ 2

  • Я рекомендовал бы atoi и atol, как правило, не только по стилю. Они делают практически невозможным обнаружение ошибок ввода. В то время как stringstream может выполнять одно и то же задание, strtol (для примера) - это то, что я обычно рекомендую как прямую замену.
  • Я не уверен, кто дает этот совет. Используйте интеллектуальные указатели, когда они полезны, но когда их нет, нет ничего плохого в использовании необработанного указателя.
  • Я действительно понятия не имею, кто считает "неправдой" использовать побитовые операторы в С++. Если бы не были какие-то конкретные условия, связанные с этим советом, я бы сказал, что это было просто неправильно.
  • Это зависит от того, где вы рисуете линию между исключительным входом и (например) ожидаемым, но не пригодным для ввода. Вообще говоря, если вы принимаете вход напрямую от пользователя, вы не можете (и не должны) классифицировать что-либо как действительно исключительное. Главным преимуществом исключений (даже в такой ситуации) является то, что ошибки не просто игнорируются. OTOH, я не думаю, что всегда единственный критерий, поэтому вы не можете сказать, что это правильный способ справиться с любой ситуацией.

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

Ответ 3

Добавляя к ответу @Jerry Coffin, который я считаю чрезвычайно полезным, я хотел бы представить некоторые субъективные наблюдения.

  • Дело в том, что программисты склонны к фантазии. То есть, большинство из нас действительно любят писать причудливый код только ради этого. Это прекрасно, если вы выполняете проект самостоятельно. Помните, что хорошим программным обеспечением является тот, чей двоичный код работает так, как ожидалось, а не тот, чей исходный код чист. Однако, когда речь заходит о крупных проектах, которые разрабатываются и поддерживаются многими людьми, экономически лучше писать более простой код, чтобы никто из команды не терял время, чтобы понять, что вы имели в виду. Даже за счет времени выполнения (естественно, небольшие затраты). Вот почему многие люди, в том числе и я, будут препятствовать использованию трюка xor вместо назначения (вы можете быть удивлены, но там очень много программистов, которые не слышали об хорном трюке). В любом случае xor трюк работает только для целых чисел, и традиционный способ замены целых чисел в любом случае очень быстрый, поэтому использование xor трюка просто фантазии.

  • использование itoa, atoi и т.д. вместо потоков быстрее. Да. Но насколько быстрее? Немного. Если большая часть вашей программы не выполняет только преобразования из текста в строку и наоборот, вы не заметите разницы. Почему люди используют itoa, atoi и т.д.? Ну, некоторые из них, потому что они не знают альтернативы С++. Другая группа делает это, потому что это всего лишь один LOC. Для первой группы - позор вам, для последнего - почему бы не boost:: lexical_cast?

  • исключения... ах... да, они могут быть медленнее, чем коды возврата, но в большинстве случаев это не так. Коды возврата могут содержать информацию, которая не является ошибкой. Исключения должны использоваться для сообщения о серьезных ошибках, те, которые не могут быть проигнорированы. Некоторые люди забывают об этом и используют исключения для моделирования некоторых странных механизмов сигнала/слота (поверьте, я видел это, yuck). Мое личное мнение заключается в том, что нет ничего неправильного в использовании кодов возврата, но с ошибками следует сообщать о серьезных ошибках, если только профайлер не показал, что воздержание от них значительно повысило бы производительность.

  • raw указатели. Мое собственное мнение таково: никогда не используйте интеллектуальные указатели, когда это не касается собственности. Всегда используйте интеллектуальные указатели, когда они касаются владения. Естественно, за некоторыми исключениями.

  • бит-сдвиг вместо умножения на степени двух. Это, я считаю, является классическим примером преждевременной оптимизации. x << 3; Бьюсь об заклад, по крайней мере 25% ваших сотрудников потребуют некоторое время, прежде чем они поймут/поймут, что это означает x * 8; Obfuscated (по крайней мере на 25%) код, для которого точные причины? Опять же, если профайлер говорит вам, что это узкое место (которое, я сомневаюсь, будет иметь место для крайне редких случаев), тогда зеленый свет, сделайте это (оставив комментарий, который на самом деле означает x * 8)

Подводя итог. Хороший профессионал признает "хорошие стили", понимает, почему и когда они хороши, и по праву делает исключения, потому что он знает, что он делает. Средние/плохие профессионалы подразделяются на 2 типа: первый тип не признает хороший стиль, даже не понимает, что и почему он. огонь их. Другой тип относится к стилю как к догме, что не всегда хорошо.

Ответ 4

Какая лучшая практика? Wikipedia слова лучше, чем у меня:

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

[...]

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

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

Ответ 5

  • Функции с изменяемыми аргументами char* в С++ плохи, потому что слишком сложно вручную обрабатывать их память, так как у нас есть альтернативы. Они не являются общими, мы не можем легко переключаться с char на wchar_t, как позволяет basic_string. Кроме того, lexical_cast является более прямой заменой для atoi, itoa.
  • Если вам действительно не нужна умность умного указателя - не используйте его.
  • Чтобы обменять использование swap. Используйте побитовые операции только для побитовых операций - флаги проверки/установки/инвертирования и т.д.
  • Исключения выполняются быстро. Они позволяют удалить ветки состояний проверки ошибок, поэтому, если они действительно "никогда не бывают", они увеличивают производительность.

Ответ 6

Умножение на битвыпуск не улучшает производительность в C, компилятор сделает это за вас. Просто не забудьте умножить или разделить на 2 ^ n значений для производительности.

Обмен битами также является чем-то, что, вероятно, просто путает ваш компилятор.

Я не очень разбираюсь в обработке строк на С++, но из того, что знаю, трудно поверить, что он более гибкий, чем scanf и printf.

Кроме того, эти утверждения "вы никогда не должны", я обычно рассматриваю их как рекомендации.

Ответ 7

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

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

Для конкретного примера, смотрите здесь.

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

Ответ 8

Я думаю, что вы отвечаете на большие части своего вопроса самостоятельно. Я лично предпочитаю легко читаемый код (даже если вы понимаете стиль C, возможно, следующий, чтобы прочитать ваш код, имеет больше проблем с ним) и безопасный код (который предлагает stringstream, исключения, интеллектуальные указатели...)

Если у вас действительно есть что-то, где имеет смысл рассматривать побитовые операции - нормально. Но часто я вижу, что программисты C используют char вместо пары bools. Мне это НЕ нравится.

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

Ответ 9

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

Ответ 10

На самом деле это не "ответ", но если вы работаете в проекте, где важна производительность (например, встроенные/игры), люди обычно выполняют более быстрый способ C, а не медленный С++-способ, описанный вами.

Исключение может быть побитовыми операциями, где не так много получается, как вы могли бы подумать. Например, "Не используйте левый сдвиг для умножения на два". Полупорядочный компилятор сгенерирует тот же код для < < 2 и * 2.