Анализ кода С - PullRequest
       23

Анализ кода С

2 голосов
/ 01 октября 2010

Вот функция, которую я пишу на 64-битной машине Linux.

void myfunc(unsigned char* arr) //array of 8 bytes is passed by reference
{
   unsigned long a = 0; //8 bytes
   unsigned char* LL = (unsigned char*) &a;

   LL[0] = arr[6];
   LL[1] = arr[3];
   LL[2] = arr[1];
   LL[3] = arr[7];
   LL[4] = arr[5];
   LL[5] = arr[4];
   LL[6] = arr[0];
   LL[7] = arr[2];
}

Теперь мои вопросы:

  1. Будет ли переменная 'a' сохраняться в регистре, чтобы к ней снова и снова не обращались из RAM или chache?
  2. Работая на 64-битной архитектуре, должен ли я предполагать, что массив 'arr' будет храниться в регистре, поскольку параметры функций хранятся в регистре в 64-битной арке?
  3. Насколько эффективно приведение типа Pointer? я думаю, что это должно быть неэффективно на всех?

Любая помощь будет оценена.

С уважением

Ответы [ 4 ]

3 голосов
/ 01 октября 2010
  1. a нельзя сохранить в регистре, так как вы взяли его адрес. (Вальдо правильно указывает, что действительно умный компилятор может оптимизировать доступ к массиву в битовые операции и оставить a в регистре, но я никогда не видел, чтобы компилятор делал это, и я не уверен это было бы быстрее).
  2. arr (сам указатель) сохраняется в регистре (%edi, на amd64). Содержимое массива находится в памяти.
  3. Приведение типа указателя само по себе часто вообще не генерирует код. Однако глупые поступки с приведением типов могут привести к очень неэффективному коду или даже к коду, поведение которого не определено.

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

Между прочим, вы должны установить тип возвращаемого значения для вашей примерной функции unsigned long и поставить return a; в самом конце; тогда вы можете использовать gcc -O2 -S и посмотреть точно, что вы получите от компиляции. Без изменения, возвращающего a, GCC с радостью оптимизирует весь корпус функции, поскольку не имеет никаких видимых извне побочных эффектов.

2 голосов
/ 01 октября 2010

Для этого лучше использовать явные инструкции shift и mask вместо индексирования массива.

Операции с массивами усложнят для компилятора использование регистров для этого, потому что обычно нет инструкций, которые выполняют такие вещи, как «загрузка 8 битов из 3-го байта регистра A». (Оптимизирующий компилятор может выяснить, что это можно сделать с помощью смен / масок, но я не уверен, насколько это вероятно).

0 голосов
/ 01 октября 2010

Зависит от вашего уровня оптимизации.Вы можете изучить сборку, чтобы ответить на ваши вопросы.В gcc используйте флаг "-S".

gcc -S -O0 -o /tmp/xx-O0.s /tmp/xx.c
gcc -S -O3 -o /tmp/xx-O3.s /tmp/xx.c

Сгенерированная сборка полностью отличается.(Обязательно внесите изменения return a;, предложенные Zack .)

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

0 голосов
/ 01 октября 2010
  1. Вопрос о том, будет ли переменная a сохраняться в регистре, является вопросом оптимизации. Поскольку модификатора volatile нет, IMHO, умный компилятор сделает это.

  2. Это вопрос соглашения о вызовах. Если по соглашению в регистр передается один параметр указателя - так будет arr.

  3. Приведение типа указателя не является операцией, которую интерпретирует процессор. Там нет кода, сгенерированного для него. Это просто информация для компилятора о том, что вы имеете в виду.

(На самом деле иногда приведение производит дополнительный код, но это связано с множественным наследованием и полиморфизмом)

...