Слегка педантичный вопрос: что более эффективно?

#c

#c

Вопрос:

Что более эффективно? Или они оба одинаково эффективны? Что происходит в базовой архитектуре в выделенных строках?

(1)

 for(int i = m_size; i > index; --i)
{
     int k = normalize(i);             ***
     m_data[k] = m_data[k - 1];
}
 

или (2)

 int k = 0; ***

for(int i = m_size; i > index; --i)
{
     k = normalize(i);             ***
     m_data[k] = m_data[k - 1];
}
 

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

1. Вы можете cc -S file.c проверить сгенерированную сборку («базовая архитектура»). Но если вы не пишете игровые движки или драйверы устройств, вам обычно следует больше беспокоиться о удобочитаемости, чем об эффективности.

2. Потрясающе! Я понятия не имел, что вы могли бы это сделать!

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

Ответ №1:

фрагмент # 1 более эффективен для наивного компилятора, потому что второй фрагмент требует загрузки 0 в переменную k.

Хороший компилятор распознает, что k не используется до загрузки k в цикл for, и оптимизирует дополнительное назначение.

Вы можете использовать сниппет 2 без загрузки 0: int k;

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

1. Не будет ли разницы в эффективности в хорошем компиляторе, если я удалю инициализацию?

2. Вам нужно было бы посмотреть на ассемблер. некоторые компиляторы поймали бы это, другие — нет. Я бы объявил int k над циклом без инициализации, но компилятору должно быть все равно.

3. Но теоретически, во фрагменте 1, разве он не воссоздает int на каждой итерации, поскольку k технически выходит за рамки в конце цикла? Конечно, компилятор мог бы оптимизировать это, но я подумал, что теоретически было бы лучше сделать фрагмент 2 из-за этого.

4. @Chipster: Я согласен, что вы можете смотреть на это таким образом.

Ответ №2:

Не беспокойтесь о такой оптимизации на микроуровне. Пусть компилятор / оптимизатор сделает это за вас. Согласно стилю C , 2-й подход лучше, поскольку он объявляет переменную, близкую к использованию (как обсуждается в книге «Code Complete»). Кроме стиля C, первый подход хорош, если вы хотите использовать переменную после цикла.

Обратите внимание, что функция normalize , тип m_data , [] задействованный оператор и т. Д. все играют роль в оптимизации. Размер данных, параллелизм, архитектура процессора также играют важную роль в производительности программы во время выполнения.

Ответ №3:

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

Обратите внимание, что любой удаленный полезный компилятор должен превратить оба ваших примера в один и тот же машинный код…

Ответ №4:

Это не проблема эффективности. Первый подход лучше, потому что он использует RAII (выделение ресурсов — это инициализация). Второй вариант больше похож на C. Вы должны объявлять переменные при его использовании, а не в начале функционального блока. Смотрите этот пример:

 void f1(bool use)
{
    ComplexObject c; // creating an object that has very costly, time-and-memory-consuming initialization

    if (! use)
        return;
    c.someFunction();
}
 

Понимаете, что я имею в виду?