Проблема времени выполнения функции Memset() в C

У меня есть структура typedef, в которой хранятся мои совокупные параметры ADC. Когда я очищаю свою структуру ADC_Sum, для достижения процесса очистки между размерами 1-8 требуется менее 300 нс. Но когда размер больше 8, это занимает около 320 мкс!!!

Я понял, что для размера 16 снова требуется менее 300 нс.

Я работаю над встроенной платой STM32. Мне интересно, почему это происходит, потому что это влияет на мой процесс в реальном времени. Вы можете найти временную таблицу и мой код ниже.

драг

typedef struct
{
   float CH1;
   float CH2;
   float CH3;
   float CH4;
   float CH5;
   float CH6;
   float CH7;
   float CH8;
   float CH9;
   float CH10;
   float CH11;
   float CH12;
   float CH13;
   float CH14;
   float CH15;
   float CH16;
   float CH17;

} Typedef_ADC_Sum;

Typedef_ADC_Sum ADC_Sum;

void clearSumArray(void)
{
    memset(&ADC_Sum, 0, sizeof(ADC_Sum));
}

Введите описание изображения здесь

Интересный результат. Какую платформу разработки и библиотеки вы используете? Похоже, что-то делает компилятор или, возможно, плохо реализована функция memset. Можете ли вы взглянуть на разборку этой функции и посмотреть, объясняет ли это это? Пробовали ли вы написать свою собственную функцию очистки (просто перебирая элементы и очищая каждый из них), чтобы увидеть, как сравнивается скорость?
Да. Я попытался очистить все переменные одну за другой, например, ADC_Sum.CH1 = 0; ..... ADC_Sum.CH17 = 0. Нет проблем, когда я очищаю каждое поле по отдельности.
Но сколько времени это займет?
Я не уверен на 100 *, но это может быть связано с тем, что memset является неприятной и сложной функцией: augias.org/paercebal/tech_doc/doc.en/cp.memset_is_evil.html viva64.com/en/b/0360
Вы, вероятно, узнали бы ответ, если бы посмотрели на ассемблерный код, сгенерированный компилятором.
Я не предполагаю, что данная часть имеет ограниченный кеш данных? Кроме того, что означает «размер функции memset»? Количество байтов, переданных функции? Количество поплавков?

Ответы (1)

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

top:
subs r0,r0,#1
bne top

Какой ЦП может или не может определять, как он выбирает, они обычно выбирают блок инструкций за одну транзакцию шины, например, 4 или 8 инструкций. И затем начните работать над ними, если они поймут, что вы выполняете ветвление назад, им, возможно, не придется выполнять предварительную выборку еще 4, конечно, если они охватывают две строки выборки, как я люблю их называть, вам придется получать их все время ( при условии отсутствия кеша, но та же проблема существует и с включенным кешем, процессор все равно должен выполнять выборку).

Только эти две инструкции и один STM32 позволяют сильно варьировать производительность.

Это может быть частью проблемы, а может и не быть.

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

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

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

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

mov r4,#0
str r4,[r0]

mov r4,#0
mov r5,#0
std r4,[r0]

mov r4,#0
mov r5,#0
std r4,[r0],#8
str r4,[r0]

mov r4,#0
mov r5,#0
std r4,[r0],#8
std r4,[r0]

Работая до петель,

stm r0!,{r4,r5,r6,r7}

или даже развернутые петли

stm r0!,{r4,r5,r6,r7}
stm r0!,{r4,r5,r6,r7}
stm r0!,{r4,r5,r6,r7}
stm r0!,{r4,r5,r6,r7}

а затем переключение передач вниз по мере необходимости.

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

Я бы начал с таких вещей, как

two_word_fill:
   stm r1!,{r2,r3}
   subs r0,r0,#1
   bne two_word_fill:

и назовите это с помощью

two_word_fill(13,ADC_Sum,0,0);

наличие компилятора C для подготовки r2 и r3 для нас. Стек не нужен на стороне сборки, но он, вероятно, будет на стороне C для вызова функции. Вы не сможете обойти это, если не соберете вручную всю функцию, в которой она живет (или не поиграете в встроенные ассемблерные игры).

Вы можете попробовать что-то вроде этого:

test2:
   push {r4,r5,r6,r7}
   mov r4,#0
   mov r5,#0
   mov r6,#0
   mov r7,#0
t2loop:
   stm r0!,{r4,r5,r6,r7}
   stm r0!,{r4,r5,r6,r7}
   stm r0!,{r4,r5,r6,r7}
   stm r0!,{r4,r5,r6,r7}
   subs r1,r1,#1
   bne t2loop
   pop {r4,r5,r6,r7}
   bx lr

Назовите это с помощью:

test2(ADC_Sum,n);

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

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

.align 8
nop
nop
top:
subs r0,r0,#1
bne top

Заставьте его выровняться по границе хорошего размера (8 не обязательно означает, что 8 слов играют с разными значениями, и посмотрите на разборку связанного результата). Затем, добавляя nops, вы можете настроить, где в строке (строках) выборки находится ваш основной цикл.

Ваш массив довольно мал, поэтому вы, вероятно, столкнетесь с другой проблемой точной синхронизации вашего теста, а также с проблемой настройки, необходимой для входа в высокоскоростной цикл, и очистки, если таковая имеется в конце, составляет больший процент от общего времени. по мере того, как массив становится меньше. Если вы посмотрите на собранный вручную код memset и memcpy, вы обнаружите, что иногда они имеют порог. Если меньше N, черт с ним; просто сделайте цикл хранения байтов. Когда вы переступите этот порог, давайте попробуем что-нибудь еще и так далее. Возможно, вы уже видите это в своих тестах. 16 слов легко обнаружить, и они работают быстрее, а слова между 8 и 16 могут вызвать проблемы с производительностью. 8 и ниже могут быть в. Просто быстрее выполнить цикл сохранения символов в N и завершить часть кода.

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

Обратите внимание, что вы, вероятно, запускаете это из флэш-памяти, я полагаю. Флэш-память на этих устройствах работает медленно, и по мере увеличения тактовой частоты состояния ожидания флэш-памяти увеличиваются, поэтому относительно основных часов производительность флэш-памяти может быть в некотором диапазоне ее максимальной скорости, но ОЗУ, как правило, не имеет состояний ожидания. Если вы скопируете этот код в оперативную память и запустите его оттуда, ваша производительность возрастет вдвое, если не больше.