Лучшая практика для функции для обработки 1-256 байтов - PullRequest
9 голосов
/ 19 августа 2010

У меня есть некоторые функции, предназначенные для обработки 1-256 байт, работающие на встроенной платформе C, где передача байта намного быстрее и компактнее, чем передача int (одна инструкция против трех), что является предпочтительным способом кодирую это:

  1. Примите int, ранний выход, если ноль, и в противном случае скопируйте младший бит значения счетчика в беззнаковый символ и используйте его в do {} while (- count); цикл (значение параметра 256 будет преобразовано в 0, но будет выполняться 256 раз)
  2. Примите символ без знака, досрочно завершите работу, если ноль, и имейте специальную версию функции для 256 байтов (эти случаи будут известны заранее).
  3. Принять неподписанный символ и запустить 256 раз, если он равен нулю.
  4. Имейте функцию, подобную вышеупомянутой, но вызывайте ее через функции-обертки, которые ведут себя как (0-255) и (только 256).
  5. Имейте функцию, подобную вышеупомянутой, но вызывайте ее через макросы-обертки, которые ведут себя как (0-255) и (только 256).

Ожидается, что внутренний цикл функции, вероятно, будет представлять 15-30% времени выполнения процессора, когда система занята; иногда он будет использоваться для небольшого количества байтов, а иногда для больших. Микросхема памяти, используемая функцией, имеет накладные расходы на транзакцию, и я предпочитаю, чтобы моя функция доступа к памяти выполняла последовательность start-транзакция / do-вещи / конец транзакции внутри.

Наиболее эффективный код будет состоять в том, чтобы просто принять неподписанный символ и рассматривать значение параметра 0 как запрос на выполнение 256 байтов, полагаясь на вызывающего, чтобы избежать любых случайных попыток прочитать 0 байтов. Это кажется немного опасным. Были ли другие проблемы с такими проблемами во встроенных системах? Как они были обработаны?

EDIT Платформа представляет собой PIC18Fxx (кодовое пространство 128 КБ; ОЗУ 3.5 КБ), подключенная к флэш-чипу SPI; чтение 256 байтов, когда ожидается меньшее количество, может привести к переполнению буферов чтения в PIC. Запись 256 байтов вместо 0 повредит данные во флеш-чипе. Порт SPI PIC ограничен одним байтом каждые 12 раз, если не проверять состояние занятости; это будет медленнее, если вы это сделаете. Типичная транзакция записи требует отправки 4 байтов в дополнение к полученным данным; Для чтения требуется дополнительный байт для «Оборота SPI» (самый быстрый способ получить доступ к порту SPI - это прочитать последний байт непосредственно перед отправкой следующего).

Компилятор HiTech PICC-18std.

Мне обычно нравятся компиляторы HiTech PICC-16; HiTech, по-видимому, отводит свою энергию от продукта PICC-18std к своей линии PICC-18pro, которая имеет даже более медленное время компиляции, кажется, требует использования 3-байтовых «константных» указателей, а не двухбайтовых указателей, и имеет свои собственные представления о распределении памяти. Возможно, мне стоит больше взглянуть на PICC-18pro, но когда я попытался скомпилировать свой проект на eval-версии PICC-18pro, это не сработало, и я не понял точно, почему - возможно, что-то с изменяемой компоновкой не соответствует мои процедуры asm - я просто продолжал использовать PICC-18std.

Кстати, я только что обнаружил, что PICC-18 особенно любит do {} while (- bytevar); и особенно не любит do {} while (- intvar); Интересно, что происходит с «разумом» компилятора, когда он генерирует последний?

  do
  {
    local_test++;
    --lpw;
  } while(lpw);

  2533                           ;newflashpic.c: 792: do
  2534                           ;newflashpic.c: 793: {
  2535  0144A8  2AD9                incf    fsr2l,f,c
  2536                           ;newflashpic.c: 795: } while(--lpw);
  2537  0144AA  0E00                movlw   low ?_var_test
  2538  0144AC  6EE9                movwf   fsr0l,c
  2539  0144AE  0E01                movlw   high ?_var_test
  2540  0144B0  6EEA                movwf   fsr0h,c
  2541  0144B2  06EE                decf    postinc0,f,c
  2542  0144B4  0E00                movlw   0
  2543  0144B6  5AED                subwfb  postdec0,f,c
  2544  0144B8  50EE                movf    postinc0,w,c
  2545  0144BA  10ED                iorwf   postdec0,w,c
  2546  0144BC  E1F5                bnz l242

Компилятор загружает указатель на переменную, даже не используя инструкцию LFSR (которая заняла бы два слова), а комбинацию MOVLW / MOVWF (занимающую четыре). Затем он использует этот указатель для уменьшения и сравнения. Пока я признаю, что do {} while (- wordvar); не может выдать такой хороший код, как do {} while (wordvar--); код лучше, чем тот, который на самом деле генерирует последний формат. Выполнение отдельного декремента и while-теста (например, while (--lpw, lpw)) дает разумный код, но это выглядит немного уродливо. Оператор после декремента может дать лучший код для цикла обратного отсчета:

  decf _lpw
  btfss _STATUS,0 ; Skip next inst if carry (i.e. wasn't zero)
   decf _lpw+1
  bc    loop  ; Carry will be clear only if lpw was zero

но вместо этого он генерирует худший код, чем --lpw. Лучший код был бы для повышающего цикла:

  infsnz  _lpw
   incfsz _lpw+1
   bra loop

но компилятор этого не генерирует.

РЕДАКТИРОВАТЬ 2 Другой подход, который я мог бы использовать: выделить глобальную 16-битную переменную для количества байтов и записать функции так, чтобы счетчик всегда обнулялся до выхода. Тогда, если требуется только 8-битное значение, необходимо будет загрузить только 8 бит. Я бы использовал макросы для вещей, чтобы они могли быть настроены для лучшей эффективности. На PIC использование | = для переменной, которая, как известно, равно нулю, никогда не медленнее, чем использование =, а иногда и быстрее. Например, intvar | = 15 или intvar | = 0x300 будут двумя инструкциями (каждый случай должен беспокоить только один байт результата и может игнорировать другой); intvar | = 4 (или любая степень 2) - это одна инструкция. Очевидно, что на некоторых других процессорах intvar = 0x300 будет быстрее, чем intvar | = 0x300; если я использую макрос, его можно настроить соответствующим образом.

Ответы [ 3 ]

2 голосов
/ 19 августа 2010

Ваша внутренняя функция должна копировать count + 1 байт, например,

 do /* copy one byte */ while(count-- != 0);

Если постдекремент медленный, другие альтернативы:

 ... /* copy one byte */
 while (count != 0) { /* copy one byte */; count -= 1; }

или

 for (;;) { /* copy one byte */; if (count == 0) break; count -= 1; }

Вызывающий / упаковщик может сделать:

if (count > 0 && count <= 256) inner((uint8_t)(count-1))

или

if (((unsigned )(count - 1)) < 256u) inner((uint8_t)(count-1))

, если это быстрее в вашем компиляторе

0 голосов
/ 19 августа 2010

FWIW, я бы выбрал вариант варианта # 1.Интерфейс функции остается разумным, интуитивно понятным и, скорее всего, будет вызываться неправильно (возможно, вы захотите подумать о том, что вы хотите сделать, если передано значение больше 256 - утверждение «только отладочная сборка» может быть уместным).

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

Я бы не стал спорить с фантиками, если бы кто-то предпочел их, но я бы лично склонялся к варианту 1 очень-чуть-чуть.

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

0 голосов
/ 19 августа 2010

Если параметр int стоит 3 инструкции, а параметр char - 1, вы можете передать дополнительный параметр char для дополнительного 1-го бита, который вам не хватает. Кажется довольно глупым, что ваш (предположительно 16-битный) int занимает вдвое больше команд, чем 8-битный символ.

...