Лучшая практика для функций для обработки 1-256 байт

У меня есть некоторые функции, которые предназначены для обработки 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-stuff / end-транзакция.

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

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

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

I ' В общем, нам понравились компиляторы 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

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

EDIT 2 даже не используя инструкцию 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

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

EDIT 2 даже не используя инструкцию 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

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

EDIT 2 не может выдать такой хороший код, как 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

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

EDIT 2 не может выдать такой хороший код, как 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

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

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

9
задан supercat 19 August 2010 в 19:48
поделиться

3 ответа

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

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

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

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

0
ответ дан 5 December 2019 в 02:06
поделиться

Ваша внутренняя функция должна скопировать 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))

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

2
ответ дан 5 December 2019 в 02:06
поделиться

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

0
ответ дан 5 December 2019 в 02:06
поделиться
Другие вопросы по тегам:

Похожие вопросы: