С ++ стиль против производительности? - PullRequest
27 голосов
/ 13 ноября 2010

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

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

  • Никогда не используйте сырые указатели, вместо этого используйте умные указатели - ОК, они действительно полезны, все это знают, язнаю, что я использую все время, и я знаю, насколько они лучше, чем сырые указатели, но иногда совершенно безопасно использовать сырые указатели .. Почему бы и нет?«Не в стиле C ++? <- этого достаточно? </p>

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

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

  • и т. Д. И т. Д. И т. П.

Хорошо, я знаю, что хороший стиль кодирования очень, очень важен <- код должен быть легким для чтения и понимания.Я знаю, что микро оптимизации не нужны, поскольку современные компиляторы очень умные, а <a href="http://en.wikipedia.org/wiki/Compiler_optimization" rel="noreferrer"> оптимизации компиляторов очень мощные.Но я также знаю, насколько дорогой является обработка исключений , как (некоторые) smart_pointers реализованы, и что в smart_ptr все время нет необходимости ... Я знаю, что, например, atoi не так"безопасно", как и std::stringstream, но все же .. А как насчет производительности?


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

Ответы [ 10 ]

46 голосов
/ 13 ноября 2010

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

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

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

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

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

Это правильно. И причина в том, что «это быстрее». Побитовый обмен проблематичен во многих отношениях:

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

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

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

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

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

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

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

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

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

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

14 голосов
/ 13 ноября 2010
  1. Я бы посоветовал против atoi и atol как правило, но не только по стилю. Они делают практически невозможным обнаружение ошибок ввода. В то время как stringstream может выполнять ту же работу, strtol (для одного примера) - это то, что я обычно советую в качестве прямой замены.
  2. Я не уверен, кто дает этот совет. Используйте умные указатели, когда они полезны, но когда это не так, нет ничего плохого в использовании необработанного указателя.
  3. Я на самом деле понятия не имеет, кто считает "плохой практикой" использовать побитовые операторы в C ++. Если бы к этому совету не было каких-то особых условий, я бы сказал, что это было просто неправильно.
  4. Это сильно зависит от того, где вы проведете линию между исключительным входом и (например) входом, который ожидается, но не может использоваться. Вообще говоря, если вы принимаете ввод непосредственно от пользователя, вы не можете (и не должны) классифицировать что-либо как действительно исключительное. Основным хорошим исключением (даже в такой ситуации) является то, что ошибки не просто игнорируются. ОТО, я не думаю, что это всегда единственный критерий, поэтому нельзя сказать, что это правильный способ справиться с любой ситуацией.

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

9 голосов
/ 13 ноября 2010

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

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

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

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

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

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

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

3 голосов
/ 13 ноября 2010
  1. Функции с изменяемыми char* аргументами плохи в C ++, потому что слишком трудно вручную обрабатывать их память, поскольку у нас есть альтернативы Они не являются общими, мы не можем легко переключиться с char на wchar_t, как позволяет basic_string. Также lexical_cast является более прямой заменой atoi, itoa.
  2. Если вам не нужен умный указатель - не используйте его.
  3. Для обмена используйте swap. Используйте побитовые операции только для побитовых операций - проверка / установка / инвертирование флагов и т. Д.
  4. Исключения бывают быстрыми. Они позволяют удалять ветки условий проверки ошибок, поэтому, если они действительно "никогда не происходят", они повышают производительность.
3 голосов
/ 13 ноября 2010

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

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

[...]

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

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

2 голосов
/ 13 ноября 2010

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

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

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

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

2 голосов
/ 13 ноября 2010

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

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

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

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

1 голос
/ 13 ноября 2010

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

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

1 голос
/ 13 ноября 2010

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

1 голос
/ 13 ноября 2010

Я думаю, что вы отвечаете на большую часть своего вопроса самостоятельно.Я лично предпочитаю легкий для чтения код (даже если вы понимаете стиль C, возможно, у следующего, кто прочитает ваш код, будет больше проблем с ним), и безопасный код (который предлагает stringstream, исключения, умные указатели ...)1002 * Если у вас действительно есть что-то, где имеет смысл рассмотреть побитовые операции - хорошо.Но часто я вижу, что программисты на Си используют char вместо пары bools.Мне НЕ нравится это.

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

...