1 Востаннє редагувалося Volodya96 (24.01.2019 16:17:49)

Тема: Бібліотека C на AVR під gcc-avr

Доброго часу всім. Запитую, можливо комусь в майбутньому така тема може бути теж цкава.
Я пишу на звичайному C, хоча я, загалом, розбалуваний всілякими C++ на десктопному ПЗ (в Qt наприклад). Тому хотілось би щось типу класів, чи хоча б простору імен для функцій, щоб бібліотека назвами своїх функцій не заважала іншій. Звичайно ж можна і просто робити назви функцій типу LibraryName_FuncName(), але це якось виглядає кострубато, зате працює без ніяких проблем :D. Гуглячи дане питання наткнувся на StackO… кхм… на одному відомому сайті на досить гарне рішення. Використання статичної структури зі статичними посиланнями на функції.

Я подумав. А чи не зберігається така структура у SRAM і чи не буде МК затрачувати час на виклик функції через посилання яке лежить в SRAM. Я написав простий тестовий main.c

Прихований текст
#include <avr/io.h>
#include <util/delay.h>

uint8_t i = 0;

struct testType {
    void (*func1)();
    void (*func2)();
};

void func1() {
    i = 1;
    _delay_ms(1000);
}

void func2() {
    i = 2;
    _delay_ms(2000);
}

const struct testType Test = {
    .func1 = func1,
    .func2 = func2
};

int main() {
    Test.func1();
    Test.func2();

    while(1);
}

Згенерувавши файл .asm через avr-objdump я отримав магію:

Прихований текст
int main() {
    Test.func1();
  da:    0e 94 53 00     call    0xa6    ; 0xa6 <func1>
    Test.func2();
  de:    0e 94 60 00     call    0xc0    ; 0xc0 <func2>
  e2:    ff cf           rjmp    .-2          ; 0xe2 <main+0x8>

"Перемога" подумалось мені. Себто, функція викликається напряму за своєю адресою. Я зрадів, і пішов писати бібліотеку.
Написав я за звичною структурою lib.c, lib.h і підключаю тільки lib.h.

lib.h

Прихований текст
#ifndef LIB_H
#define LIB_H

#include <avr/io.h>
#include <util/delay.h>

struct testType {
    void (*func1)();
    void (*func2)();
};

extern const struct testType Test;

#endif //LIB_H

lib.c

Прихований текст
#include <avr/io.h>
#include <util/delay.h>

#include "lib.h"

uint8_t i;    

void func1() {
    i = 1;
    _delay_ms(1000);
}

void func2() {
    i = 2;
    _delay_ms(2000);
}

const struct testType Test = {
    .func1 = func1,
    .func2 = func2
};

main.c

Прихований текст
#include <avr/io.h>
#include <util/delay.h>

#include "lib.h"

int main() {
    Test.func1();
    Test.func2();

    while(1);
}

І отримав неприємну штуковину:

Прихований текст
int main() {
    Test.func1();
  da:    e0 91 00 01     lds    r30, 0x0100    ; 0x800100 <Test>
  de:    f0 91 01 01     lds    r31, 0x0101    ; 0x800101 <Test+0x1>
  e2:    09 95           icall
    Test.func2();
  e4:    e0 91 02 01     lds    r30, 0x0102    ; 0x800102 <Test+0x2>
  e8:    f0 91 03 01     lds    r31, 0x0103    ; 0x800103 <Test+0x3>
  ec:    09 95           icall
  ee:    ff cf           rjmp    .-2          ; 0xee <main+0x14>

Тепер за адресою структури в SRAM (ну там загалом масив адрес, але то вже доточності) завантажується адреса функції до регістру двобайтового Z, а потім виконується icall, а це +2 такти на виклик однієї функції. Неприємна новина. Бачу такий вихід: оголосити назви функцій в lib.h кострубатого вигляду lib_func1, а у самій структурі назви посилань на них залишити як є, гарними, але мені чомусь таке видається кострубатим.

Глянув ASM та HEX редактором, то, здається, до SRAM структура все ж записується, хоч і в подальшому не використовується, тому, мабуть, доведеться відмовитись від такого рішення :(.
Запитую, щоб отримати поради щодо краса коду, бо те як його зробити працездатним і з економією ресурсів, я знаю :D, але тут виходить або краса коду, або оптимізація. Авжеж виграє оптимізація, але як при цьому код хоч трохи зробити гарнішим. Дякую за увагу.

Як можна домогтися краси коду бібліотеки C не жертвуючи ресурсами МК?

Подякували: ReAl1

2 Востаннє редагувалося reverse2500 (24.01.2019 21:17:54)

Re: Бібліотека C на AVR під gcc-avr

а потім виконується icall, а це +2 такти на виклик однієї функції. Неприємна новина.

все через структуру і правильно,в структурі під переміну виділяється пам'ять чи адрес

Як можна домогтися краси коду бібліотеки C не жертвуючи ресурсами МК?

макроси+ассемблер
процедурич чи як ще називають підпрограми

Подякували: Volodya961

3 Востаннє редагувалося ReAl (28.01.2019 10:20:44)

Re: Бібліотека C на AVR під gcc-avr

Volodya96 написав:

Доброго часу всім. Запитую, можливо комусь в майбутньому така тема може бути теж цкава.
Я пишу на звичайному C, хоча я, загалом, розбалуваний всілякими C++ на десктопному ПЗ (в Qt наприклад). Тому хотілось би щось типу класів, чи хоча б простору імен для функцій, щоб бібліотека назвами своїх функцій не заважала іншій.

Забігаючи наперед — для AVR цілком можна писати на C++, якщо не надто розганятися. При акуратному використанні шаблонів і код не роздувається де не треба, і швидкість непогана де треба (за рахунок якраз роздування коду, але зробити це заради швидкості шаблонами може бути зручніше, ніж С-шними макросами, див, наприклад, цю тему.

А якщо писати без класів, у звичайному процедурному стилі, то і простори імен будуть, і зайвих витрат пам'яті не буде, буде «все як на С», тільки буде LibraryName::FuncName() замість LibraryName_FuncName() чи LibraryName.FuncName() у варіанті зі статичними структурами :D (і що красивіше — на мою думку лише питання смаку до течій AsciiArt, а не «краси коду», хіба ото у варіанті C++ можна using namespace сказати для вибору конкретної бібліотеки).

Volodya96 написав:

А чи не зберігається така структура у SRAM і чи не буде МК затрачувати час на виклик функції через посилання яке лежить в SRAM.

Згенерувавши файл .asm через avr-objdump я отримав магію:
"Перемога" подумалось мені. Себто, функція викликається напряму за своєю адресою.

Тут пощастило, бо компілятор бачив все, бачив, що структури константні і зоптимізував роботу з константами.

Volodya96 написав:

І отримав неприємну штуковину:
Тепер за адресою структури в SRAM (ну там загалом масив адрес, але то вже доточності) завантажується адреса функції до регістру двобайтового Z, а потім виконується icall,

А тут компілятор не міг побачити вміст цих константних структур, тому згенерував повний код.

Volodya96 написав:

Як можна домогтися краси коду бібліотеки C не жертвуючи ресурсами МК?

Жертва ресурсів не така вже й велика. Якщо функції складні, то в них кілька push на початку і кілька pop в кінці зжеруть набагато більше часу, ніж той непрямий виклик через icall.
Повертаючись до С++ — виклик віртуальної функції буде ще трохи дорожчий, ніж той виклик зі структури, бо таблиця віртуальних функцій класу лежить окремо, спільна для всіх екземплярів класу.
А якщо функції не віртуальні, то у порівнянні з С практично нічого не додається. Так, перед викликом у регістрову пару буде завантажуватися вказівник на структуру даних екземпляра класу, але це навіть добре, бо у самій функції будуть звертання до полів структури зміщенням. ldd r16, Y+4, наприклад, що коротше, ніж lds r16, struct_addr+4, яке часто у таких випадках генерує компілятор, навіть якщо у коді звертання по вказівнику(!). А щоб форсувати у С-ному коді завантаження вказівника і звертання по ньому, навіть придумали макрос RELOAD.

Тепер розворот, і повертаємося до С з константними структурами у різних файлах.
Є така штука LTO (link-time optimization). Компліятор тоді у об'єктні файли кладе не готовий код, а свої внутрішні структури його опису. А лінкер тоді вже бачить всю програму як одне ціле і робить наскрізну оптимізацію. Чудово вигризає все, що може. Прошивки в діапазоні розміру 4-40 кілобайт стають на 5-7% коротшими. Бачить, що дрібні функції з іншого файлу можна вставити по місцю як inline і оптимізує їх у кожному місці окремо. Для C++ теж корисно.
Але — якщо десь було забуте volatile, то варіант з функціями в інших файлах може працювати, бо виклик зовнішньої функції спрацьовує як бар'єр пам'яті, а при LTO вони вже не «зовнішні» у цьому розумінні, і без volatile щось може «уоптимізуватися вусмерть»

Подякували: Volodya96, leofun012

4

Re: Бібліотека C на AVR під gcc-avr

ReAl написав:

А якщо писати без класів, у звичайному процедурному стилі, то і простори імен будуть, і зайвих витрат пам'яті не буде, буде «все як на С», тільки замість LibraryName_FuncName() чи LibraryName.FuncName() у варіанті зі статичними структурами буде LibraryName::FuncName() :D (і що красивіше — на мою думку лише питання смаку до течій AsciiArt, а не «краси коду», хіба ото у варіанті C++ можна using namespace сказати для вибору конкретної бібліотеки).

Мабуть перейду тоді до C++. Іноді мені все ж на C не вистачало всіляких речей типу: однозв'язних (чи будь-якзв'язних) списків, namespace-ів (особливо), іноді навіть класів. Так це дає величезні можливості для створення ще більш універсальних бібліотек.
Єдине що мене лякає, так це все ж ще менший контроль за SRAM (особливо з однозв'язними списками) і велика спокуса використовувати повноцінні класи :D Бо я вже перероблюючи проект на C++, задумуюся над створенням класів *PARDON*

Щодо смаку до течій AsciiArt. Я просто думав, може є які інші методи, про які я не знав, от наприклад про статичні структури я б не здогадався, але ж то красивий варіант. Гарніше виглядає ніж через нижній пробіл, але іноді без -flto компілятор робить їх не статичним. Так, я бачив що іноді у функції компілятор додає безліч push, pop на початку й кінці, але все ж додати ще до того 2 такти на вивантаження адреси функції із SRAM, та й невиправдане використання SRAM для кожної бібліотеки, це надлишок. Краще вже C++, вже перевірив. Компілятор таких дурниць із namespaces не робить. Так у програмістів я бачив є декілька розбіжностей бачення коду, як от де ставити фігурну дужку чи в якому стилі називати змінні, чи навіть TAB vs SPACE (на початку, через великий вплив Pascal-я, я користувався пробілами, потім вже переходив плавно до табів, бо бачу що у деяких IDE з пробілами біда, тож перейшов до табів (зараз думаю, що дарма того раніше не зробив)), але ж є різні міні-правила(рекомендації), типу от як не використовувати goto :) От і подумав, може і тут є яке міні-правило.

reverse2500 написав:

макроси+ассемблер

Дякую за таку підказку. Передивився код згенерований компілятором під asm, знайшов функцію в якій воно намалювало цикл, хоча він там і не потрібен насправді, тому переписав її на макрос. Тож воно тепер вставляє в код лише одну команду, замість виклику функції з циклом. Макрос — сила! :D