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

Какие методы оптимизации C в x86, которые исторически рекомендуется использовать, более не эффективны?

Из-за успехов в компиляторах x86 C (а именно GCC и Clang) многие методы кодирования, которые, как полагали, повышают эффективность, больше не используются, поскольку компиляторы могут лучше оптимизировать код, чем люди (например, смещение бит против умножения).

Какая конкретная практика такова?

4b9b3361

Ответ 1

Из рекомендуемых оптимизаций пара, которая в принципе никогда не бывает плодотворной, учитывая современные компиляторы:

Математические преобразования

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

Оптимизации, такие как преобразование умножения на добавление или постоянное умножение или деление на сдвиг битов, уже выполняются современными компиляторами даже при низких уровнях оптимизации. Примеры этих оптимизаций включают в себя:

x * 2  ->  x + x
x * 2  ->  x << 1

Обратите внимание, что некоторые конкретные случаи могут отличаться. Например, x >> 1 не совпадает с x / 2; нецелесообразно подставлять один для другого!

Кроме того, многие из предложенных оптимизаций на самом деле не быстрее, чем код, который они заменяют.

Глупые трюки кода

Я даже не уверен, что назвать это, но трюки, такие как замена XOR (a ^= b; b ^= a; a ^= b;), вообще не оптимизируются. Они всего лишь партийные трюки - они медленнее и более хрупки, чем очевидный подход. Не используйте их.

Ключевое слово register

Это ключевое слово игнорируется многими современными компиляторами, так как его предполагаемое значение (принудительное сохранение переменной в регистре) не имеет смысла при наличии текущих алгоритмов распределения регистров.

Преобразования кода

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

  • Развертывание цикла. (Это часто на самом деле вредно при применении без разбора, так как он раздувает размер кода.)
  • Вставка функций. (пометьте функцию как static, и она, как правило, будет включена, когда это необходимо, когда включена оптимизация.)

Ответ 2

Одна из таких практик заключается в том, чтобы избежать умножений, используя массивы указателей массива вместо реальных 2D-массивов.


Старая практика:

int width = 1234, height = 5678;
int* buffer = malloc(width*height*sizeof(*buffer));
int** image = malloc(height*sizeof(*image));
for(int i = height; i--; ) image[i] = &buffer[i*width];

//Now do some heavy computations with image[y][x].

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


Но умножение стало быстрым, некоторые процессоры могли выполнять их в одном цикле процессора, в то время как доступ к памяти не отставал. Итак, теперь этот код, скорее всего, будет более результативным:

int width = 1234, height = 5678;
int (*image)[width] = malloc(height*sizeof(*image));

//Now do some heavy computations with image[y][x],
//which will invoke pointer arithmetic to calculate the offset as (y*width + x)*sizeof(int).

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

Ответ 3

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

Эта оптимизация для данной платформы, DSP имеет смысл, если возникнет необходимость в ней. Тогда лучшим первым помощником является ИМХО разумное использование restrict, если компилятор/оптимизатор хорошо его поддерживает. Избегайте алгоритмов, связанных с условиями и неустойчивым кодом (breaks, goto, if, while,...). Это способствует потоковой передаче и позволяет избежать слишком большого числа предсказаний о плохих ветвях. Я бы согласился, что эти намеки являются здравым смыслом.

Вообще говоря, я бы сказал: Любая манипуляция, которая модифицирует код, делая предположения о том, как оптимизировать компилятор, вообще не будет ИМХО.

Скорее, затем переключитесь на сборку (обычная практика для некоторых действительно важных алгоритмов в DSP, где компиляторы, будучи очень хорошими, все еще пропускают последние несколько процентов увеличения производительности циклов CPU/Mem...)

Ответ 4

Одна оптимизация, которая действительно не должна использоваться намного больше, - это #define (расширение на duskwuff немного отвечает).

Препроцессор C - замечательная вещь, и он может сделать некоторые удивительные преобразования кода, и это может сделать очень сложный код намного проще - но использование #define просто для того, чтобы вызвать небольшую операцию, как правило, не подходит больше. Большинство современных компиляторов имеют реальное ключевое слово inline (или эквивалент, например __inline__), и они достаточно умны, чтобы встроить большинство функций static в любом случае, что означает, что этот код выглядит следующим образом:

#define sum(x, y) ((x) + (y))

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

static int sum(int x, int y)
{
    return x + y;
}

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

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