Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: С и asm-вставки
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > MCS51, AVR, PIC, STM8, 8bit
Xeon
Всем доброго времени суток!!! Использую AVR Stidio 5. Написал программу и теперь надо её оптимизировать... некоторые расчёты уж компилятор сделал больно длинные (в смысле по количеству команд), хотя можно намного проще... Как мне известно локальные переменные обычно хранятся либо в стеке либо в регистрах... Вопрос следующий: как назначить переменной конкретный регистр или как узнать в каком регистре находится переменная, чтоб в дальнейшем иметь к ней доступ?
Dog Pawlowa
Цитата(Xeon @ Mar 30 2012, 09:05) *
Всем доброго времени суток!!! Использую AVR Stidio 5. Написал программу и теперь надо её оптимизировать...

Какие способы оптимизации на С использовались?
А вообще-то на эту тему очень показателен топик:
http://www.avrfreaks.net/index.php?name=PN...ic&t=114878

Сергей Борщ
QUOTE (Xeon @ Mar 30 2012, 09:05) *
Вопрос следующий: как назначить переменной конкретный регистр или как узнать в каком регистре находится переменная, чтоб в дальнейшем иметь к ней доступ?
Неправильный вопрос вы задаете. Спрашивать надо "как на языке С написать этот алгоритм более оптимально?". Своими благими намерениями вы будете только мешать оптимизатору и в пределе можете сделать свою программу работающей только под одной версией компилятора при строго определенном наборе ключей.
Хотите - балуйтесь:
CODE
register uint16_t Tmp __asm__(("r16"))
Чтобы узнать, в каком регистре находится переменная, надо читать листинг. Но это мало что вам даст - добавление одной строчки в другом месте программы может привести к перераспределению регистров и ваша программа работать перестанет. Это во-первых. А во-вторых - локальная переменная за время своей жизни легко может кочевать из одного регистра в другой, на стек и обратно.

Хотите более надежное решение - изучите соглашение о вызовах и напишите отдельный узкий кусок программы чисто на асме или в крайнем случае на инлайн-асме, а лучше выложите свой код и возможно фуромчане насоветукют вам, как его улучшить не прибегая к извращениям. Поверьте, это возможно.
Xeon
Использую оптимизацию -Оs...
MaxiMuz
Чем отличается обьявление:
Код
register uint16_t Tmp __asm__(("r16"))

от :
Код
register uint16_t Tmp asm("r16");
?
Xeon
Нашел немного инфы по asm-вставкам здесь (может кому будет полезна):
http://www.simple-devices.ru/attachments/a...AVR_04_2011.pdf
http://www.simple-devices.ru/attachments/a...AVR_05_2011.pdf

В пдфке по второй ссылке, говориться что в asm-вставку операнды можно передавать по имени, т.е. вместо:
CODE
asm(«in %0, %1» : «=r» (value) : «I» (_SFR_IO_ADDR(PORTD)) );

можно писать:
CODE
asm volatile («in [x1], [x2]» : [x1] «=r» (value) : [x2] «I» (_SFR_IO_ADDR(PORTD)) );

У меня почему то это не вышло, писал следующее:
CODE

asm volatile(
"push R0 \n"
"push R1 \n"

"muls %0, %1 \n" // загрузить синус

"pop R1 \n"
"pop R0"
:
:[SINVAL] "d" (sinVal), [COSVAL] "d" (cosVal)
);

В чём я ошибся?

Цитата(Сергей Борщ @ Mar 30 2012, 09:32) *
Хотите более надежное решение - изучите соглашение о вызовах и напишите отдельный узкий кусок программы чисто на асме или в крайнем случае на инлайн-асме, а лучше выложите свой код и возможно фуромчане насоветукют вам, как его улучшить не прибегая к извращениям. Поверьте, это возможно.

Конечно, последую Вашему совету... А у Вас не найдется, что почитать? По поводу переноса на другой компилятор... сейчас такого вопроса не стоит и скарей всего не когда и не будет.

Вот кусок коды на С который хочу заставить работать быстрей:
CODE

// вычисляем вещественную и мнимую части
int8_t sinVal = 2;// pgm_read_byte(&FsinTable[FLookignForFreq][CurVal]);
int8_t cosVal = -3;//pgm_read_byte(&FcosTable[FLookignForFreq][CurVal]);

FsummRe[FLookignForFreq] += (int32_t) (Values[CurVal]*cosVal);
FsummIm[FLookignForFreq] += (int32_t) (Values[CurVal]*sinVal);

CurVal++;
if(CurVal >= FAMOUNT_POINTS)
{
ADCA.CTRLA = 0;
Flags = ADC_CONVERSION_COMPLETE;
}
DMA.CH0.CTRLB |= (1 << 4) | (1 << 5); // clear interrupt flags

Собствненно узкое место здесь:
CODE

FsummRe[FLookignForFreq] += (int32_t) (Values[CurVal]*cosVal);
FsummIm[FLookignForFreq] += (int32_t) (Values[CurVal]*sinVal);

Здесь делаю перемножение двух знаковых одно-байтовых чисел и после всё кладу в "сумматор", так я должен делать 256 раз (выше сказанное делается 2 раза, 1 раз для FsummRe и 1 раз для FsummIm). 8x8 == 16 разрядов и поскольку всё кладу в сумматор 256 раз, то конечный результат для FsummRe и FsummIm будет 24 разрядный.

Вот, что получаю от компилятора:
CODE

FsummRe[FLookignForFreq] += (int32_t) (Values[CurVal]*cosVal);
000003EF LDS R30,0x2427 Load direct from data space
000003F1 LDI R31,0x00 Load immediate
000003F2 LDS R26,0x2431 Load direct from data space
000003F4 LDI R27,0x00 Load immediate
000003F5 SUBI R26,0xCA Subtract immediate
000003F6 SBCI R27,0xDA Subtract immediate with carry
000003F7 LD R18,X Load indirect
000003F8 CLR R19 Clear Register
000003F9 SBRC R18,7 Skip if bit in register cleared
000003FA COM R19 One's complement
000003FB LSL R30 Logical Shift Left
000003FC ROL R31 Rotate Left Through Carry
000003FD LSL R30 Logical Shift Left
000003FE ROL R31 Rotate Left Through Carry
000003FF MOVW R16,R30 Copy register pair
00000400 SUBI R16,0xD8 Subtract immediate
00000401 SBCI R17,0xDC Subtract immediate with carry
00000402 MOVW R14,R18 Copy register pair
00000403 LSL R14 Logical Shift Left
00000404 ROL R15 Rotate Left Through Carry
00000405 ADD R18,R14 Add without carry
00000406 ADC R19,R15 Add with carry
00000407 COM R19 One's complement
00000408 NEG R18 Two's complement
00000409 SBCI R19,0xFF Subtract immediate with carry
0000040A CLR R20 Clear Register
0000040B SBRC R19,7 Skip if bit in register cleared
0000040C COM R20 One's complement
0000040D MOV R21,R20 Copy register
0000040E MOVW R26,R16 Copy register pair
0000040F LD R22,X+ Load indirect and postincrement
00000410 LD R23,X+ Load indirect and postincrement
00000411 LD R24,X+ Load indirect and postincrement
00000412 LD R25,X Load indirect
00000413 SBIW R26,0x03 Subtract immediate from word
00000414 ADD R18,R22 Add without carry
00000415 ADC R19,R23 Add with carry
00000416 ADC R20,R24 Add with carry
00000417 ADC R21,R25 Add with carry
00000418 ST X+,R18 Store indirect and postincrement
00000419 ST X+,R19 Store indirect and postincrement
0000041A ST X+,R20 Store indirect and postincrement
0000041B ST X,R21 Store indirect
0000041C SBIW R26,0x03 Subtract immediate from word

Честно, особо не вникал как конкретно делает компилятор... По мне можно сделать так:
1) взять нужные значения и перемножить знаково используя MULS
2) после произвести знаковое сложение 24 разрядного "сумматора" и полученного 16 разрядного результата перемножения
По этому, как мне кажется, реализовать это дело можно намного проще и быстрее...
Жду Ваших комментариев...
XVR
Судя по листигну компилятор не в курсе, что существует команда MULS. Вы уверены, что указали компилятору правильный тип процессора? Если да, то вы уверенны, что этот процессор поддерживает MULS?
Xeon
Тип процессора правильный, точно... Использую XMega32 A4... По даташиту данные микроконтроллеры поддерживают команду MULS. Может на самом деле есть где нибудь у компилятора настройка по этому поводу. Сейчас посмотрю и отпишусь.

Ни чего не нашел!!!
XVR
Вообще код странный. Там ни одного перехода нет, и умножения нет. Что то он у вас сильно развернул
А, так у вас int8_t cosVal = -3; Компилятор подставил вместо cosVal константу и развернул умножение на константу -3 в набор сложений и сдвигов. Это будет быстрее, чем MULS. Верните обратно переменную
Сергей Борщ
QUOTE (MaxiMuz @ Mar 30 2012, 10:24) *
Чем отличается обьявление:
Ничем. Они эквивалентны.

QUOTE (Xeon @ Mar 30 2012, 11:13) *
В пдфке по второй ссылке, говориться что в asm-вставку операнды можно передавать по имени
Да, можно. Смотрите:
CODE
    uint8_t Tmp;
    bpm_driver::state * pTmp;

    asm volatile (
//        pTmp = get_update_src();
        "PUSH %[tmp]                \r\n"
        "IN %[tmp], %[sreg]         \r\n"
        "PUSH %[tmp]                \r\n"
        "PUSH %A[ptr]               \r\n"
        "PUSH %B[ptr]               \r\n"
        "LDS  %A[ptr], %[state]     \r\n"
        "LDS  %B[ptr], %[state]+1     \r\n"
        :[ptr]"=z"(pTmp), [tmp]"=r"(Tmp)
        :[sreg] "I" (_SFR_IO_ADDR(SREG))
        ,[state]"m"(bpm_driver::pState)
     );


QUOTE (Xeon @ Mar 30 2012, 11:13) *
У меня почему то это не вышло, писал следующее:
По симптомам "у меня не вышло" можно поставить лишь один диагноз: "значит что-то делали неправильно". Объясните, что вы хотели сделать и что получили?

QUOTE (Xeon @ Mar 30 2012, 11:13) *
Вот кусок коды на С который хочу заставить работать быстрей:
CODE
    FsummRe[FLookignForFreq] += (int32_t) (Values[CurVal]*cosVal);
В этой строке приведение типа либо абсолютно лишнее либо используется не там, где нужно. Как объявлено Values?

Выложите всю функцию со всеми объявлениями в виде одного файла, который можно компилить отдельно и экспериментировать.
QUOTE (Xeon @ Mar 30 2012, 12:02) *
Тип процессора правильный, точно... Использую XMega32 A4... По даташиту данные микроконтроллеры поддерживают команду MULS. Может на самом деле есть где нибудь у компилятора настройка по этому поводу.
С хмегами не работал и не собираюсь, но, возможно, компилятор не очень свежий и еще не умеет гененрить MULS.
MaxiMuz
если всеже будет обьявлять регистровые переменные как "register uint16_t Tmp asm("r16");" или "register uint16_t Tmp __asm__(("r16"))" пишите перед этим квалификатор volatile , меньше ошибок с исчезновением операций будет.
Сергей Борщ
QUOTE (MaxiMuz @ Mar 30 2012, 15:17) *
пишите перед этим квалификатор volatile , меньше ошибок с исчезновением операций будет.
Вы в который раз топчетесь по одним и тем же граблям (уже однажды разбирали в одной из ваших веток, и вы сами смотрели листинг и убеждались). НЕ РАБОТАЕТ в gcc volatile register. В сообщении 30 той ветки было решение.
MaxiMuz
Цитата(Сергей Борщ @ Mar 30 2012, 17:17) *
НЕ РАБОТАЕТ в gcc volatile register. В сообщении 30 той ветки было решение.

как раз volatile решил проблему
Xeon
Привожу исходники: Нажмите для просмотра прикрепленного файла
Немного опишу: первоночальная цель - нужно найти некоторые частоты используя преобразование Фурье, всякие вызовы подобные _DebugMes, _DebugByte и др. - это макросы для вывода в консоль... В ADC.c я делаю 255 измерений и после каждого измерения срабатывает прерывание от DMA и внем я вычисляю вещественную и мнимую части. Почему DMA - просто когда я пробовал без него получалось, что в присудствее задержки распространения АЦП при частоте 250КГц АЦП делал 50000 выборок в секунду, при 500КГц 100000 выборок в секунду, а мне надо мерить сигнал в 100КГц, значит по Найквисту/Котельникову частота выборок в два раза больше, т.е. 200 килосемплов/секунда. Сделав через DMA получилось, что от задержке распространения избавился (может я не прав, но сигнал который дискретизую очень похож на то, что вижу на осциле). К тому же DMA позволило производить измерения, когда на АЦП подаю 1МГц, без DMA не работало. В файлике FTransform.c собственно само преобразование, вобщем там на мой взгляд все довольно тривиально... Если вдруг будет, что не понятно распишу всё подробней...
XVR
А вы все таки откомпилируйте реальный код и посмотрите, что вам сделал компилятор. В вашем asm листинге, который вы приводили, к собственно умножению относилось только 6 комманд. Остальное к индексации 2х массивов

И уровень оптимизаций поставьте побольше: -O3 например


Кстати, таблицы у вас весьма специфичные sm.gif Они действительно состоят только из 0 и -127? Если да, то вам стоит задуматься об изменении алгоритма - там вообще умножения не нужны
Xeon
Попробую... Да одна такая таблица получается) это для 64 гармоники при частоте дискретизации 250килосемплов/секунду (реальная частота 63500Гц), но надо ещё +8 частот смотреть... поэтому менять думаю особо невариантsm.gif
alexeyv
Перед тем как оптимизировать на асме, разберитесь сначала с оптимизацией ни СИ. Во-первых, при оптимизации на асме можно сделать очепятку, которую потом будете долго и упорно искать. Во-вторых, оптимизация на Си выполняется гораздо быстрее. В-третьих, при хорошо изученном поведении своего компилятора, отимизированный код на Си будет всего на 5...15% больше своего собрата на асме, зато скока сэкономленого времени?!
И если только оптимизация на Си не помогает, то необходимо залезать в ассемблер.
Но и при ассемблерной оптимизации есть куча подводных камней.

Примеры:
1. Использование указателей вместо индексации массивов
Код
    int8_t * val = &Values[0]; // в не тела цикла
    ..................................
    // в цикле:
    FsummRe[FLookignForFreq] += (int32_t) (*val) * cosVal;
    FsummIm[FLookignForFreq] += (int32_t) (*val) * sinVal;
    ..................................
    val++;

Это сокращает код на одно вычисление адреса Values[CurVal]. FsummIm и FsummRe, FsinTable и FcosTable также можно привести к использованию указателей

2. Оптимизация циклов.
Цикл i=N;do{.....}while(i--); в большинстве случаев более оптимизировано компилируется, чем цикл for(i = 0; i < N; i++){........};

3. При целочисленной арифметике, деление заменяется на умножение и сдвиг.
4. Объявление наиболее используемых локальных переменных как register (не вовсех компиляторах)
5. и т.д. и т.п..........................



Xeon
Цитата(alexeyv @ Apr 2 2012, 07:56) *


Спасибо! Весьма полезные советы!!!
XVR
Цитата(Xeon @ Apr 2 2012, 11:46) *
Спасибо! Весьма полезные советы!!!
На достаточном уровне оптимизации (-О3 достаточно) gcc это все сделает сам. (Хотя в конверсии массивов в указатели может и запутаться - у вас там весьма накрученно массивы индексируются)

alexeyv
Цитата(XVR @ Apr 2 2012, 14:30) *
На достаточном уровне оптимизации (-О3 достаточно) gcc это все сделает сам. (Хотя в конверсии массивов в указатели может и запутаться - у вас там весьма накрученно массивы индексируются)

1. Поверьте мне - GCC НЕ ВСЕ ЭТО сделает сам, в нем заложены только типичные алгоритмы, которые впрочем постоянно расширяются и периодически обновляются.
2. Мои высказывания относятся ко всем компиляторам, а не только к GCC. И если писать сразу отпимизировано, то не надо будет думать, что "я это делать не буду - это компилятор САМ прооптимизирует", а потом локти себе кусать. Это путь к деградации, если думать что за тебя все сделает компилятор/оптимизатор.
3. -О3 конечно хорошо, но когда идет отладка, то -О3 отключаешь, и как раз в этот момент не хватает - то оперативки, то флеша, то быстродействия......... (если конечно при проектировании не заложили запас, а все сделали в притык, как часто бывает)
=GM=
Цитата(XVR @ Apr 2 2012, 07:30) *
... у вас там весьма накрученно массивы индексируются

Да там индексы не нужны вообще. Например, здесь

FsummRe[FreqNum] += Values[i]*cosVal;

дважды по 256 раз вычисляется адрес по индексу FreqNum, но этот индекс - константа в подпрограмме.

Надо бы поставить A=A+Values[i]*cosVal;

в теле цикла, а после цикла сделать обратное присвоение FsummRe[FreqNum]=А
Xeon
Возник следующий вопрос - как в на асме прочитать байтик из памяти программ? Покопавшись в просторах интернета нашёл следующее:
CODE

ldi ZH, hi8(pm(F64_cos))
ldi ZL, lo8(pm(F64_cos))
lpm r18, Z


F64_cos - таблица значений косинуса. Но чёт это не работает. Код в AVR stidio 4 помнется выглядит примерно так:
CODE

ldi ZH, high(F64_cos*2)
ldi ZL, low(F64_cos*2)
lpm r18, Z

Т.е. в AVR stidio 4 нужно делать сдвиг в лево, что равно умножению на 2. Почитав доки на avr-gcc нашёл что макрос pm надо использовать если нужен доступ к памяти программ, но он равносилен деления на 2 или, что тоже самое, сдвигу на 1 в право. На этом моменте у меня сразу возникло не понимание принципа доступа к памяти программ на ассемблере в avr-gcc.
Может кто в курсе как делается правильно?

Вот весь код на асме, который написал:
CODE

#include "../Headers/asm_ftransform.h"
#include <avr/io.h>

.extern FsummRe // == r9, r8, r7, r6
.extern FsummIM // == r13, r12, r11, r10
.extern CurVal // == r5
.extern Flags
.extern FsinTable
.extern FcosTable
.extern F64_cos

.global DMA_CH0_vect
DMA_CH0_vect:

push r0
push r1
push ZL
push ZH
push r17
push r18
push r19

/* load Sin and Cos */
// calc offset
ldi r17, 0x35
//mul r5, r17 // r1:r0 - offset

// sin addres
ldi ZH, hi8(pm(F64_cos))
ldi ZL, lo8(pm(F64_cos))
//add r16, r0
//adc r17, r1
lpm r18, Z
sts _SFR_IO_ADDR(USARTE0_DATA), r18

// DMA.CH0.CTRLB |= 48; // clear interrupt flags
ldi r26, 0x30
sts _SFR_IO_ADDR(DMA_CH0_CTRLB), r26
// stop ADC
clr r17
sts _SFR_IO_ADDR(ADCA_CTRLA), r17

ldi r17, 2
sts Flags, r17

pop r19
pop r18
pop r17
pop ZH
pop ZL
pop r1
pop r0

reti


Нашел в чём проблема, мой косяк, F64_cos была в ОЗУ. И собственно чтоб прочитать из памяти программ нужно:
ldi ZH, hi8(F64_cos)
ldi ZL, lo8(F64_cos)
lpm r18, Z
без всяких макросов pm().
И новый вопрос: я правильно понимаю, макрос pm() используется для вызова подпрограмм, например:
ldi ZH, hi8(pm(s_func))
ldi ZL, lo8(pm(s_func))
call Z
так?
alexeyv
1. pm не используется для вызова подпрограм. Для вызова подпрограмм используются команды CALL/ICALL/EICALL/RCALL.
pm() - program memory, макрос просто умножает параметр на 2.
Дело в том, что ОЗУ у AVR адресуется байтово, а flash-память словарно, и адреса всех объектов, лежащих во flash-памяти являются словарными. Поэтому что бы из словарного адреса получить байтовый (для доступа к любому байту flash-памяти), необходимо умножить на 2, или сдвинуть на 1 влево.

Доступ к flash-памяти:
Код
ldi ZH, high(Table_1<<1)             ; Initialize Z pointer
ldi ZL, low(Table_1<<1)
lpm r16, Z+                                  ; Load constant from program Low-adress + increment Z
lpm r16, Z                                  ; Load constant from program High-adress


2. call Z - нет такой команды, есть icall.
И насколько я помню при загрузке ZH:ZL для команды icall, сдвигать адрес не надо.
Косвенный вызов функции:
Код
ldi ZH, high(func)             ; Initialize Z pointer
ldi ZL, low(func)
icall                                  ; Load constant from program

fox2trot
Вообще то, при постановке вопроса следовало бы уточнить, а какая собственно оптимизация вам нужна ? По скорости или по объему ? Подходы к обоим видам совершенно разные, кроме как совета писать на асме. Без этого бессмысленно что либо советовать.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.