C оптимизация вопрос - PullRequest
       1

C оптимизация вопрос

3 голосов
/ 27 июля 2010

Мне интересно, как быстрее всего написать код? У меня есть цикл, который выполняет добавление некоторых целых. Цикл будет выполняться много, много раз, и поэтому я подумал о том, чтобы сделать сравнения, чтобы проверить, являются ли какие-либо операнды нулевыми, поэтому их не следует считать добавленными, как показано ниже:

if (work1 == 0)
{
    if (work2 == 0)
        tempAnswer = toCarry;
    else
        tempAnswer = work2 + toCarry; 
}
else if (work2 == 0)
    tempAnswer = work1 + toCarry;
else
    tempAnswer = work1 + work2 + toCarry;

Я считаю, что вложенный IF наверху уже является оптимизацией, поскольку он быстрее, чем написание серии сравнений с &&, поскольку я проверял бы (work1 == 0) более одного раза.

К сожалению, я не смог бы сказать, насколько часто work1 и work2 будут равны нулю, поэтому предположим, что это будет сбалансированное распределение каждого возможного результата оператора IF.

Итак, в свете этого приведенный выше код быстрее, чем просто написание tempAnswer = work1 + work2 + toCarry, или все сравнения могут привести к значительным потерям?

Спасибо

Ответы [ 9 ]

26 голосов
/ 27 июля 2010

Это бессмыслица.

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

    Кроме того, подумайте об этом логически - зачем выделять ноль как одно значение, которое вы рассматриваете как особый случай?Почему бы не проверить и использовать tempAnswer++?Рассматривая все возможности, вы видите, что это бессмысленное упражнение.

17 голосов
/ 27 июля 2010

Ответ, как всегда, профиль вашего кода .Запишите это обоими способами, рассчитайте время и посмотрите, что быстрее.

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

2 голосов
/ 27 июля 2010

Нет, это не быстрее. Неправильное прогнозирование веток намного более болезненно, чем сложение.

2 голосов
/ 27 июля 2010

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

1 голос
/ 28 июля 2010

Я согласен с общим смыслом других комментариев - «оптимизация» на самом деле является «пессимизацией», которая затрудняет написание, чтение, сопровождение кода.

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

Примеры функций

$ cat yy.c
int optimizable(int work1, int work2, int toCarry)
{
    int tempAnswer;
    if (work1 == 0)
    {
        if (work2 == 0)
            tempAnswer = toCarry;
        else
            tempAnswer = work2 + toCarry; 
    }
    else if (work2 == 0)
        tempAnswer = work1 + toCarry;
    else
        tempAnswer = work1 + work2 + toCarry;

    return tempAnswer;
}
$ cat xx.c
int optimizable(int work1, int work2, int toCarry)
{
    int tempAnswer;
    tempAnswer = work1 + work2 + toCarry;
    return tempAnswer;
}
$

Компилятор

$ gcc --version
gcc (GCC) 4.1.2 20080704 (Red Hat 4.1.2-44)
Copyright (C) 2006 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Размеры объектных файлов с различными уровнями оптимизации

$ gcc -c yy.c xx.c
$ size xx.o yy.o
   text    data     bss     dec     hex filename
     86       0       0      86      56 xx.o
    134       0       0     134      86 yy.o
$ gcc -O -c yy.c xx.c
$ size xx.o yy.o
   text    data     bss     dec     hex filename
     54       0       0      54      36 xx.o
     71       0       0      71      47 yy.o
$ gcc -O1 -c yy.c xx.c
$ size xx.o yy.o
   text    data     bss     dec     hex filename
     54       0       0      54      36 xx.o
     71       0       0      71      47 yy.o
$ gcc -O2 -c yy.c xx.c
$ size xx.o yy.o
   text    data     bss     dec     hex filename
     54       0       0      54      36 xx.o
     70       0       0      70      46 yy.o
$ gcc -O3 -c yy.c xx.c
$ size xx.o yy.o
   text    data     bss     dec     hex filename
     54       0       0      54      36 xx.o
     70       0       0      70      46 yy.o
$ gcc -O4 -c yy.c xx.c
$ size xx.o yy.o
   text    data     bss     dec     hex filename
     54       0       0      54      36 xx.o
     70       0       0      70      46 yy.o
$

Код скомпилирован для 64-битного RedHat Linux на AMD x86-64.

Обе функции несут один и тот же багаж инфраструктуры (3 параметра, 1 локальный, 1 возврат).В лучшем случае оптимизированная функция на 16 байтов длиннее неоптимизированной функции.Чтение дополнительного кода в память - это снижение производительности, а дополнительное время, затрачиваемое на выполнение этого кода, - другое.

1 голос
/ 27 июля 2010

Самый быстрый - относительный термин. Для какой платформы это? у него есть кеш? Если он имеет кэш, он, скорее всего, на платформе, которая может выполнить добавление за один такт, поэтому нет необходимости оптимизировать добавление. Следующая проблема - сравнение - это вычитание, вычитание и сложение. Проходят по одному и тому же alu и занимают то же время, что и сложение, поэтому для большинства платформ старые и новые торговые сравнения (вычитание) для сложения ничего не спасут, в итоге вы смотрите на стоимость ветвления, сбрасывание конвейера и т. д. Даже с платформой ARM вы все равно не используете ни одного, ни несколько. Первое, что вы должны сделать для такой оптимизации, это посмотреть на вывод компилятора, какие инструкции выбирает компилятор? (при условии, что это компилятор, который использует каждый компилятор этого кода и те же параметры компилятора и т. д.). Например, на чипе, где add / sub занимает больше часов, или значительное количество часов, xor или и / или операции могут занимать меньше часов. Вы можете сделать сравнение с нулем на некоторых процессорах, используя побитовую операцию, сохраняя часы. Компилятор понял это и использовал эту более быструю операцию?

Как ответ общего характера на ваш вопрос, основанный на существующих процессорах и шансах, которые вы используете или не используете. Одна строка:

tempAnswer = work1 + work2 + toCarry;

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

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

1 голос
/ 27 июля 2010

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

Современные процессоры хранят как можно больше в кэше процессора или, возможно, на материнской плате. Удар по основной памяти относительно медленный, а чтение на странице памяти сравнительно очень медленное. Существует иерархия от быстрой и маленькой до медленной и большой. Одной из важных вещей для производительности является попытка остаться на «быстрой и маленькой» стороне этой иерархии.

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

Следовательно, при микрооптимизации вы должны стараться, чтобы внутренние циклы содержали небольшой код, что обычно означает простой и короткий. В вашем случае у вас есть три сравнения и несколько добавлений, когда у вас не может быть никаких сравнений и два добавления. Этот код, скорее всего, вызовет ошибку кэша, чем более простой tempAnswer = work1 + work2 + toCarry; .

1 голос
/ 27 июля 2010

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

  if (var1 != 0)
    someobject.property1 += var1;

может сэкономить время, если запись в Propert1 будет медленной, особенно если свойство не оптимизирует запись значения, которое уже существует.В редких случаях может быть полезно:

  if (var1 != 0)
    volatilevar2 += var1;

, если все процессоры часто перечитывают volatilevar2, а var1 обычно равен нулю.Сомнительно, что сравнение, которое было полезно, когда-либо происходило «естественно», хотя можно было бы придумать.Немного менее надуманная версия:

  if (var1 != 0)
    Threading.Interlocked.Add(volatilevar2, var1);

может быть полезна в некоторых естественных сценариях.

Конечно, если целью добавления является локальная временная переменная, которая выиграла 'Если его нельзя разделить с другими процессорами, то вероятность экономии времени практически равна нулю.

0 голосов
/ 28 июля 2010

Вот классическое предупреждение: «избегайте ранней оптимизации».

Функция действительно так важна? Он вызывается так часто, что вам приходится его оптимизировать?

Теперь давайте посмотрим на ответ @ Джонатана и подумаем о «техническом долге», то есть ремонтопригодности. Подумайте в своей конкретной среде: через один или два года кто-то посмотрит на ваш код и ему будет труднее его понять, или, что еще хуже, он / она неправильно поймет его!

Кроме того, сравните xx.c и yy.c: какой фрагмент кода имеет больше шансов на ошибку?

Удачи!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...