2014 dxdy logo

Научный форум dxdy

Математика, Физика, Computer Science, Machine Learning, LaTeX, Механика и Техника, Химия,
Биология и Медицина, Экономика и Финансовая Математика, Гуманитарные науки




Начать новую тему Ответить на тему На страницу 1, 2  След.
 
 эффективное сложение long и char
Сообщение21.05.2018, 15:04 


07/10/15

2400
Возникла необходимость усреднения данных, измеренных АЦП контроллера. Измеряемые значения помещаются в переменной
unsigned char (8 бит), как вариант - unsigned int (16 бит), но это не обязательно, так как от этого выигрыш небольшой - добавляется только 2 бита и те не очень надёжные. После измерения, измеренные значения прибавляются к переменой signed long int, так как размера int недостаточно (знак нужен для других операций с этой переменной). Сложения происходят часто и скорость выполнения критична. Но вот тут: http://robocraft.ru/blog/677.html я нашел, что сложение (вычитание) переменных long требует от 60 до 80 тактов контроллера.

Вопрос - можно ли как то эффективнее организовать эту операцию? и на сколько достоверны приведённые в ссылке данные?

по моим представлениям на такое сложение должно расходоваться примерно 4 такта, ну может ещё какие то накладные расходы, но для чего требуется 80 тактов - никак не могу понять ...

 Профиль  
                  
 
 Re: эффективное сложение long и char
Сообщение21.05.2018, 15:28 
Заслуженный участник
Аватара пользователя


01/08/06
3133
Уфа
В статье, на которую вы ссылаетесь, дядя Фёдор неправильно ест бутерброд.
Используется синтаксис C
  /*Расчет количества тиков для математических операций с типом long*/
  /*Вычисление для long + long*/
  for (int i = 0; i<= kol; i++){L1 = micros();L2 = micros()-L1;
  x3+=x3;//Операция для которой вычисляется количество тиков  
  L3 = micros()-L1-L2;  L4 += L3;}L5 = L4/kol; L4 = 0;
  Serial.print("  long + long   |           ");Serial.println(L5*16); L6+=L5;

Надо бы сначала пустой цикл посчитать:
Используется синтаксис C
L1 = micros();
L2 = micros()-L1;
for (int i = 0; i<= kol; i++){
  /* Не знаю как, но тут нужно, чтобы оптимизатор компилятора не удалял ничего не делающий цикл! */
}
L3 = micros()-L1-L2; /* Время выполнения пустого цикла */

А потом добавить в цикл операцию, подлежащую измерению:
Используется синтаксис C
L1 = micros();
L2 = micros()-L1;
for (int i = 0; i<= kol; i++){
  x3+=x3; // Операция, для которой вычисляется количество тиков  
}
L4 = micros()-L1-L2; /* Время выполнения цикла с нужной операцией */
L5 = (L4-L3)*16/kol; /* Чистое время в мкс на одну нужную операцию */

 Профиль  
                  
 
 Re: эффективное сложение long и char
Сообщение21.05.2018, 15:50 


07/10/15

2400
Спасибо worm2 Это уже прибавляет оптимизма.

 Профиль  
                  
 
 Re: эффективное сложение long и char
Сообщение21.05.2018, 15:51 
Заслуженный участник


20/08/14
11798
Россия, Москва
Andrey_Kireew, worm2
Уж сколько раз всем везде говорили, никогда не меряйте время одиночной операции, нет, всё равно находятся "умники" ... Как по ссылке. :facepalm:

Andrey_Kireew
Замерьте лучше сами, организовав цикл примерно такого рода:
Используется синтаксис C
unsigned long int a=0; char c=101;
for(i=1;i<10000;i++) {
  a+=c;
  a+=c;
  a+=c;
//Тут ещё 95 повторов этой же команды
  a+=c;
  a+=c;
}
Проверьте по ассемблерному тексту во что это скомпилилось, что все переменные в регистрах весь цикл, замеряйте время выполнения миллиона операций (а если будет меньше нескольких секунд как должно быть - то и десяти миллионов) - вот и будет вам реальная скорость. Или просто прямо по ассемблерному тексту посчитайте длительность.

Andrey_Kireew в сообщении #1313840 писал(а):
по моим представлениям на такое сложение должно расходоваться примерно 4 такта, ну может ещё какие то накладные расходы,
Судя по всему да, 4-7 тактов (обнуления может не быть, или оно занимать 3 такта):
Код:
add R20,R16
clr R16
adc R21,R16
adc R22,R16
adc R23,R16
Только не забывайте если больше ничего рядом не делается, то будут ещё и операции чтения всего это добра из памяти и записи обратно в неё же, а это ещё до 20 тактов.

-- 21.05.2018, 15:58 --

Причём скорее даже 7 тактов будет: сначала обнуление старших трёх байтов второго слагаемого для преобразования к 32-х битному типу, потом стандартная процедура сложения 32-х битных чисел за 4 такта. Хороший компилятор не будет делать её вызов call/ret (ещё плюс минимум 7 тактов), а подставит 4 команды inline. Про плохой компилятор вспоминать не будем, там и сотни тактов могут быть (передача параметров через стек, контроль входных параметров на допустимость, контроль переполнения суммы и стека, ещё что-нибудь ...).

-- 21.05.2018, 16:06 --

worm2 в сообщении #1313854 писал(а):
А потом добавить в цикл операцию, подлежащую измерению:
Это верно только при условии что накладные расходы на организацию цикла (нарушено и Вашим кодом и по ссылке) и погрешность измерения времени (нарушено по ссылке) существенно меньше ожидаемой длительности измеряемой операции. Для таких простых операций как сложение это не так и надо в тело цикла помещать не одну операцию, а десятки и сотни чтобы выполнить условие.
Конкретно в данном случае я бы поместил в цикл хотя бы сотню сложений. А лучше тысячу. При условии что время реально измеряется в мкс, это даст погрешность порядка пяти процентов (или меньше процента для тысячи повторов), что позволит с чистым сердцем округлить до целого числа тактов. ;-)

 Профиль  
                  
 
 Re: эффективное сложение long и char
Сообщение21.05.2018, 16:23 


07/10/15

2400
СпасибоDmitriy40 я сам то и не пытался вычислять. Просто наткнулся на статью и результаты меня сильно удивили. Вообще такие вычисления корректно делать на самом МК. Эмулятор уже не гарантирует адекватных результатов, пару раз с этим сталкивался, и теперь ими не пользуюсь. Двойная работа получается.

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

Я тут прикинул: у atmega8 максимальный делитель у АЦП 128, преобразование в режиме Free Runing выполняется за 2 такта АЦП. Получается между прерываниями от АЦП будет 256 тактов контроллера. В них нужно и уложиться. Со сложением, как я теперь понимаю всё должно быть нормально.

 Профиль  
                  
 
 Re: эффективное сложение long и char
Сообщение21.05.2018, 16:26 
Заслуженный участник


20/08/14
11798
Россия, Москва
Образцовый код для сложения двух 32-х битных чисел в цикле:
Код:
ldi R24,250 ;Количество повторов цикла
loop:
add R20,R16 ;1
adc R21,R17 ;1
adc R22,R18 ;1
adc R23,R19 ;1
dec R24 ;1
brne loop ;2
Как видно занимает 7 тактов на 249 выполнений и 6 тактов на последнее. Плюс "обвязка": преобразование типов переменных, загрузка и сохранение в память, инициализация счётчика цикла (всё уложится в 30 тактов).

При использовании 16-ти битного счётчика цикла компилятор вместо оптимального по времени кода
Код:
subi R24,1 ;1
brnc loop ;2
subi R25,1
brnc loop
скорее всего поставит что удобнее
Код:
sbiw R24,1 ;2
brne loop ;2
что будет выполняться на такт дольше (4 вместо 3), зато кода меньше и он проще.

-- 21.05.2018, 16:41 --

Andrey_Kireew в сообщении #1313878 писал(а):
преобразование в режиме Free Runing выполняется за 2 такта АЦП
Ошибаетесь, преобразование выполняется за 13 тактов АЦП. Кроме первого после включения АЦП, которое занимает 25 тактов. При максимальном делителе 128 это будет 1664 такта процессора (первое не считаю).

Но, если в цикле делается что-то ещё кроме банального сложения, и особенно если с условиями, то подсчитать время выполнения трудно (хотя я делал, но тут нужны внимательность и понимание что как в процессоре происходит), проще замерить на живом процессоре (хоть бы и в симуляторе типа протеус) и заложить запас "на всякий случай".
Если задача состоит именно в накоплении (усреднении) данных, то я делал как-то так: запускал преобразование по аппаратному таймеру (чтобы было порегулярнее во времени) или непрерывное включал, в прерывании АЦП считывал результат и тут же добавлял его к сумме, увеличивал счётчик и по достижении им порога - копировал сумму в другое место в память, обнулял её и счётчик, выставлял флаг что сумма корректна и возврат. В основной программе жду флага, читаю накопленную сумму из другого места, сбрасываю флаг. На прерывание тратилось типа 20-40 тактов, что всегда меньше периода преобразования АЦП (ему нельзя задирать частоту тактов выше 200кГц). Разумеется прерывание писалось на ассемблере и все его переменные в регистрах (которые навечно за ним и не сохраняются в стек). На С это же займёт где-то от 50 до сотни тактов. В основную программу флаг вываливается на порядок реже и там уже время обработки может быть на тот же порядок больше.

 Профиль  
                  
 
 Re: эффективное сложение long и char
Сообщение21.05.2018, 17:09 


07/10/15

2400
Понятно.
У меня это единственное сложение - цикла никакого нет, есть только один условный переход перед ним и всё.

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

Но умножать long на int накладно, и мне совсем не нужно, так что можно умножить только верхние 2 байта от постоянно обновляемой суммы, и тогда в результате снова получится результат long

код: [ скачать ] [ спрятать ]
Используется синтаксис C
......
long int S=0, Pr=0;
int* ss=&S;
int k;

ISR (ADC_vect)      // --- функция обработки прерываний по окончанию преобразования АЦП
{
if (Phase==0)
           {S+=ADCW; Phase++;}   // сложение измеренного значение с A
else
 .............................

Pr=*ss*k;
}

while (1)
{
............
}
 


т.е. я хочу создать указатель типа int* и приравнять его к адресу переменной S типа long,
по моим представлениям, значение по указателю *ss будет содержать верхние разряды S

тогда и умножение займёт не так уж много времени,
правильно ли я это понимаю?

 Профиль  
                  
 
 Re: эффективное сложение long и char
Сообщение21.05.2018, 17:11 
Заслуженный участник


20/08/14
11798
Россия, Москва
Кстати говоря, тот самый предел в 200кГц Вам изначально не даёт поставить делитель на АЦП меньше 64 (на 16МГц тактовой), да и при нём частота будет уже 250кГц, выше рекомендуемой, но не так чтобы намного, пока не гонитесь за суперточностью сойдёт (а когда погонитесь - всплывёт много других подводных камней). Т.е. каждое измерение АЦП будет занимать 832 такта процессора. Даже на С прерывание с суммированием 32-х битных чисел не будет занимать более сотни (ну пусть даже двух сотен если компилятор тупой) тактов - времени навалом.
Если будет интересно могу подробнее рассказать как сам поступаю в похожих случаях (осцил + тестовая нога = forever), но думаю Вы и сами в курсе.

-- 21.05.2018, 17:32 --

Andrey_Kireew
Во первых код неправильный, но думаю это просто опечатки и смысл в общем понятен.
Во вторых предлагаю во всех критичных по времени местах использовать беззнаковые переменные - это обычно быстрее (особенно в умножении и делении). Если в конце надо результат со знаком - потом преобразуете.
В третьих, время умножения 32 на 16 (да и на 32) не так уж и велико, это всего 8 (16) умножений по 2 такта и десятка три-четыре простых сложений и пересылок, т.е. в сотню тактов явно уложится. Скорее конечно ближе к сотне, наверняка всё будет читаться из памяти и туда же писаться, а не жить в регистрах, плюс сохранение в стек используемых регистров (что компиляторы С очень любят делать по поводу и без оного).
В четвёртых, если уж приравниваете указатель на int к long, то надо брать смещение в два байта чтобы пропустить младшие. У Вас этого не вижу. И Intel и Atmel AVR хранят байты с младшего.
В пятых, я не вижу смысла в каждом прерывании сначала накапливать сумму, а потом ещё и умножать её на число. В каждом-то зачем?! Разве не достаточно будет домножить на число лишь уже всю накопленную сумму, в главной программе? Там ведь и времени больше, и реже происходить будет (не каждое прерывание), и вообще правильнее выносить долгие операции из прерываний.
В шестых, ну зачем Вам тут указатель? Только для преобразования типа переменной? Так объявите union с long и двумя int в структуре, получите удобный способ обращаться и к 32 и по отдельности к 16 битам.
В седьмых, глобальные переменные - обычно зло. Ни S ни ss Вам снаружи явно не нужны, так объявите их в прерывании, они может даже в регистрах поселятся, всё будет быстрее. И логичнее.

 Профиль  
                  
 
 Re: эффективное сложение long и char
Сообщение21.05.2018, 17:29 


07/10/15

2400
Dmitriy40
Мне всё это интересно!

К сожалению я пока пытаюсь на С, но если такие колоссальные различия - то конечно перейду на assembler.

Но тут есть принципиальный вопрос. По поводу 13 тиков:

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

Но внимательно вчитавшись в "мануал" обнаружил - 13 тиков, это преобразование в одиночном режиме, а в режиме Free Runing - там всего 1,5 тика (ну если точно - первое 13,5 тиков, наверное настройка идёт), и каждые 2 тика посылается новое прерывание.

Вот я и хочу в этом режиме работать. Тогда и таймер не нужен. АЦП само им становится. Делитель нужно, разумеется, выставить на максимум, а это 128. И каждые 256 тактов контроллера будет новое прерывание. Тут выигрыш в максимальной частоте дискретизации. Так она у меня будет около 62 kHz, в одиночном режиме частота будет в 6-7 раз меньше.

Что вы думаете по этому поводу?

-- 21.05.2018, 18:43 --

Dmitriy40 в сообщении #1313898 писал(а):
И Intel и Atmel AVR хранят байты с младшего.

Этого я не знал, спасибо!

Dmitriy40 в сообщении #1313898 писал(а):
В пятых, я не вижу смысла в каждом прерывании сначала накапливать сумму, а потом ещё и умножать её на число. В каждом-то зачем?! Разве не достаточно будет домножить на число лишь уже всю накопленную сумму, в главной программе?


Да нет, неизвестно когда она накопится, нужно постоянно контролировать этот результат. Я вообще планировал выполнять эту операцию по таймеру, разумеется намного реже, чем Sample Rate у АЦП, но тут есть проблема - нельзя прекращать подсчёт этой суммы. Если даже в основной программе эта операция будет длительная, то есть подозрение, что она просто вообще никогда не выполнится.

Dmitriy40 в сообщении #1313898 писал(а):
В шестых, ну зачем Вам тут указатель? Только для преобразования типа переменной? Так объявите union с long и двумя int в структуре, получите удобный способ обращаться и к 32 и по отдельности к 16 битам.

Согласен, так будет удобнее.
Dmitriy40 в сообщении #1313898 писал(а):
В седьмых, глобальные переменные - обычно зло. Ни S ни ss Вам снаружи явно не нужны, так объявите их в прерывании, они может даже в регистрах поселятся, всё будет быстрее. И логичнее.


Ага, а как же я потом буду работать с этой суммой если она не глобальная?

 Профиль  
                  
 
 Re: эффективное сложение long и char
Сообщение21.05.2018, 17:45 
Заслуженный участник


20/08/14
11798
Россия, Москва
Andrey_Kireew в сообщении #1313901 писал(а):
Что вы думаете по этому поводу?
Думаю Вы чуть ошибаетесь с чтением pdf. 1.5 такта занимает не всё преобразование, а лишь Sample&Hold, т.е. передача внешнего напряжения на внутреннюю схему АЦП, а всё преобразование занимает 13 тактов. Посмотрите на "Figure 94. ADC Timing Diagram, Free Running Conversion", там явно нарисовано где кончается одно преобразование и начинается следующее - на 13-м такте АЦП. И первые полтора такта идёт Sample&Hold, потом преобразование напряжения внутри в код. Собственно в "Table 73. ADC Conversion Time" чёрным по белому написано "Conversion Time (Cycles)" 13 тактов. Для АЦП везде и всегда написано в самом начале раздела "Up to 15 kSPS at Maximum Resolution" (т.е. 65мкс), 2 такта - мечты. :(
Время преобразования не зависит от режима запуска, оно всегда 13 (или 25) тактов, вопрос лишь от чего их отсчитывать (и что они выравнены на такты самого АЦП).
Потому ставьте делитель 64 и смело надейтесь что С оставит вам до семи сотен тактов на ваши вычисления (остальное - накладные расходы на вызов прерывания, ну и запас чуточку). Ну или 128 если скорость измерений дело третье, вам останется более полутора тысяч тактов.
В остальном подход правильный, что называется "одобрям-с". :-)

-- 21.05.2018, 17:50 --

Andrey_Kireew в сообщении #1313901 писал(а):
Ага, а как же я потом буду работать с этой суммой если она не глобальная?
По коду Вам нужно Pr, а не S. Вот оно и останется глобальным, S же нужна лишь внутри. Возможно я ошибаюсь из-за краткости кода.

Ещё момент, я не знаю зачем Вам с такой скоростью что-то там контролировать, но обычно можно снизить частоту контроля в основной программе до десятков/сотен в секунду - а значит и умножение делать с той же частотой, а не каждое измерение. Экономия.

-- 21.05.2018, 17:54 --

Andrey_Kireew в сообщении #1313901 писал(а):
Да нет, неизвестно когда она накопится, нужно постоянно контролировать этот результат.
Не ждите накопления домноженной суммы, пересчитайте один раз порог делением и ждите накопления самой суммы, не домноженной. Прямо в прерывании, сравнение двух 32-х битных чисел занимает те же тактов 10 т.е. слёзы. Когда накопится - выставляйте флаг главной программе (скопировав сумму в другое место!) и там уже пересчитывайте обратно умножением один раз если надо.

 Профиль  
                  
 
 Re: эффективное сложение long и char
Сообщение21.05.2018, 18:05 


07/10/15

2400
Да уж :( оптимизма явно по убавилось. В смысле 1600 тактов - это конечно хорошо, но 10 кГц - это уже не очень. Но всё равно, для начала думаю пойдёт ...

 Профиль  
                  
 
 Re: эффективное сложение long и char
Сообщение21.05.2018, 18:23 
Заслуженный участник


20/08/14
11798
Россия, Москва
Ну не 10, а почти 20кГц можно получить (250кГц такт АЦП и 13 тактов на преобразование). А если точность понизить, то как бы разрешают и ещё поднять частоту АЦП (правда уже ничего не обещают), можно наверное и 50кГц получить для 6-7 бит точности (остальное будет шум).

Да, мне тоже именно этим АЦП в AVR и не нравится, тормозной. При том что в практически той же цене процессора можно иметь и 12 битный 1МГц АЦП (STM8L052 к примеру, или вообще ARM STM32F030). А совсем чуть подороже и два синхронных АЦП каждый 12 бит 1МГц, плюс 72МГц проц, плюс 32-х битный ARM - STM32F103. Для этих ARM-ов кстати есть вариант работы прямо из ардуино среды, многие скетчи работают сразу, без модификаций (правда сам я не пробовал, мне и С/асм хватает).

 Профиль  
                  
 
 Re: эффективное сложение long и char
Сообщение21.05.2018, 18:44 


07/10/15

2400
Понятно, на перспективу конечно можно и МК другой. Дело тут скорее не в цене. Просто Atmega у меня есть, и есть всё что нужно для работы с ними (ну правда я только на С их программировал). А с другими нужно ещё разобраться ...

У меня ещё такой вопрос. Правильно ли я понимаю - чтобы получить старшие биты long нужно сделать так?
  1. long int S; 
  2. int* ss=&(S+2); 


-- 21.05.2018, 19:51 --

На счёт того, чтобы S сделать локальной, а глобальной только Pr то думаю так не пойдёт. При выходе из обработчика, если S будет локальной, может обнулиться, или принять непонятное значение. Лучше уж её не трогать, пусть "висит" себе.

 Профиль  
                  
 
 Re: эффективное сложение long и char
Сообщение21.05.2018, 19:17 
Заслуженный участник


20/08/14
11798
Россия, Москва
Andrey_Kireew в сообщении #1313917 писал(а):
Правильно ли я понимаю - чтобы получить старшие биты long нужно сделать так?
По моему надо так: int * ss = (int *)&S + 1;, ну или (что хуже!) int * ss = (void *)&S + 2;. Ваш код вообще компилиться не должен.
Разумеется это работает лишь в предположении что два int равны long (и int равен двум байтам для второго варианта). Это не везде и не всегда так.
А самое правильное - через union и без указателя. Или даже через взятие старшего слова из 32-х бит (часто есть такие операции в компиляторе, нечто типа a=HighWord(long int b);), да хотя бы даже int a = long int S >> 16;, это компилятор обычно правильно оптимизирует в просто две команды пересылки. Разумеется long int писать не надо, это лишь для пояснения типа переменной.

-- 21.05.2018, 19:19 --

Andrey_Kireew в сообщении #1313917 писал(а):
При выходе из обработчика, если S будет локальной, может обнулиться, или принять непонятное значение.
Узнайте про ключевое слово static. Оно как раз подойдёт для локальной S.

-- 21.05.2018, 19:28 --

Онлайн компилятор для проверки кусочков кода (размеры всех типов другие, но это в чём-то и хорошо): http://www.compileonline.com/compile_c_online.php

 Профиль  
                  
 
 Re: эффективное сложение long и char
Сообщение21.05.2018, 20:43 


07/10/15

2400
Dmitriy40
извиняюсь за назойливость, но не могли бы Вы пояснить, чем отличается
  1. #define F_CPU 4000000L 

от
  1. #define F_CPU 4000000UL 


я подозреваю, что первый вариант с внешним кварцем (как мне и надо), а второй - это просто с RC генератором. Или может не так?

И тут ещё загвоздка, у меня контроллер atmega8L а у компиллера avr-gcc такого нет, но есть просто atmega8
если я "залью" прогу для atmega8 в atmega8L то это будет считаться нормально, или без толку даже и пробовать?

к стати
  1. int* pR=(int*)(&(Re))+1; 

нормально получается, ну по крайней мере компилируется без ошибок теперь всё

 Профиль  
                  
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 22 ]  На страницу 1, 2  След.

Модераторы: Karan, Toucan, PAV, maxal, Супермодераторы



Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group