Сравнение кода: какой код лучше использовать с точки зрения эффективности?

#c #string #copy-constructor #assignment-operator

#c #строка #копировать-конструктор #оператор присваивания

Вопрос:

Какой код лучше использовать: для инициализации строки?

 bool flag = /*function call...*/
string str = "abc";
if(flag)
  str = "bcd";
  

Или

 string str;
if(flag)
  str = "bcd";
else
  str = "abc";
  

Или

 string str("abc");
if(flag) 
  str = "bcd";
  

Заранее спасибо.

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

1. Этот вопрос лучше подходит для codereview.stackexchange.com

2. @MarkusDeibel Нет, он был бы закрыт, поскольку там отсутствует контекст .

3. Я полагаю, вы хотите сравнить это. Я подозреваю, что результаты будут другими, если вы используете строки по 40 символов вместо 3 символов

4. string str = flag ? "bcd" : "abc";

Ответ №1:

Это микрооптимизация, которой вам не следует заниматься. Программа на C — это не описание сборки в удобочитаемом формате. Это высокоуровневое описание поведения, которое должна иметь ваша программа. Задача компилятора — создать сборку, которая демонстрирует указанное нами поведение.

Вы не указали, как вы используете результирующую строку, но если я сделаю несколько предположений и превращу ваш код в небольшие функции…

 #include <string>

std::string foo1(bool flag) {
    std::string str = "abc";
    if(flag)
        str = "bcd";
    return str;
}

std::string foo2(bool flag) {
    std::string  str;
    if(flag)
        str = "bcd";
    else
        str = "abc";
    return str;
}

std::string foo3(bool flag) {
    std::string str("abc");
    if(flag) 
        str = "bcd";
    return str;
}
  

.. Clang 8.0 создаст практически эквивалентную сборку для трех случаев (и особенно для foo1 и foo3 )1. Вы можете увидеть это на Godbolt.

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



1 — Ну, в есть заметная ветвь foo2 , но код, который будет выполняться во время выполнения, все равно всегда будет одним из потенциальных выделений и копией содержимого строки. Здесь могут быть некоторые затраты на неправильное предсказание ветвей, но если это не в действительно узком цикле, я бы не считал, что это стоит рассматривать. Более того, использование std::string в таком цикле само по себе может показаться сомнительным.

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

1. Тем не менее, это не та же сборка для случая 2. Если вы внимательно посмотрите, есть 2 вызова string::replace в случае 2 (по одному для каждой ветви). Также эффект производительности будет другим, если строка превысит размер буфера оптимизации коротких строк.

2. @rustyx — Возможно, я неправильно подчеркнул. Я имел в виду, что «в значительной степени» должно быть более заметным. Я полагаю, что существуют две ветви, потому что это количество инструкций, которые нужно будет сбросить при неправильном прогнозировании ветвей.

3. @rustyx — Учитывая, что когда-либо будет выполняться только одна ветвь, SOO или нет, я не думаю, что ветвление заслуживает большего, чем сноска об эффективности, если вообще.

Ответ №2:

Способ C заключается в использовании инициализации:

 bool flag = foo();
string str { flag ? "bcd" : "abc" };
  

flag определяет, какой строковый литерал используется при вызове std::string::string(const char*) . Но есть один вызов, создается один строковый объект и нет назначения.

[редактировать] Перевернул строковые литералы. В C , как для if конструкции, так и для ?: конструкта, «истинный» регистр идет первым, а затем «ложный» регистр последним. Но вопрос изменил их местами, «abc» появился первым для flag==false случая.