ako naprogramovat

David Obdrzalek David.Obdrzalek na mff.cuni.cz
Neděle Duben 14 23:34:04 CEST 2024


Já bych ještě s dovolením podotknul, že používat String v situaci, kdy se do něj 
inkrementálně neco strká, je dost neefektivní jak paměťově, tak výkonově, protože 
tempString += inChar; znamená, že se v zásadě udělá nová alokace na velikost součtu 
délek (v tomhle přípaně jen o 1 větší) a okopíruje se tam všechno plus ten nový 
znak. A tak pořád dokola.
Plus je potřeba připočítat management okolo vlastní existence instance String.

Pokud vím, jak je zpráva dlouhá, tak je šetrnější deklarovat rovnou buffer potřebné 
délky a do něj to postupně ukládat. Nebo zprávu dokonce zpracovávat za letu tak jak 
přicházejí jednotlivé znaky a nepotřebuju ani buffer na celou zprávu.

Ono sice né že by to byl ve spojení se seriovou linkou nějaký výkonnostní problém 
(seriová komunikace je obvykle výrazně pomalejší, než jak běží mikrokontroler, takže 
se mezitím ta realokace v pohodě stihne). Také obvykle string bude krátký, takže 
nejspíš ani nějaké potíže, že by se to do paměti nevešlo dvakrát nebo došlo k nějaké 
fatální vnitřní fragmentaci paměti, asi taky nenastanou. Ale myslím, že ani tehdy se 
prostě nemá plýtvat :-)

Na druhou stranu souhlasím, že pokud se jedná o nějakou jednorázovku nebo něco fakt 
triviálního, kde i těch 32 kB programové paměti Arduino Uno je řádově víc, než 
potřebuju, a 16MHz hodiny to ženou tak rychle, že se to stejně vlastně pořád jen 
točí dokola v loop, aniž by to dělalo něco rozumného, a navíc ani nepotřebuju nijak 
šetřit energií, tak se zohledněním ceny času programátora může být prostě 
efektivnější se s tím nebabrat a namastit to tam víceméně jakkoli... (ovšem když se 
to namastí ne plýtvavě, tak to asi vadit nebude, žejo)

D.O.




PS: Pro srovnání:
A)
volatile String retezec;
void loop() {
  while(Serial.available())
  {
    char novyZnak;
    novyZnak = Serial.read();
    retezec += novyZnak;
  }
}

B)
const unsigned maxDelka=100;
volatile char retezec[maxDelka];
uint8_t delka;
void loop() {
  while(Serial.available())
  {
    char novyZnak;
    novyZnak = Serial.read();
    if(delka<maxDelka-1) {
      retezec[delka++] = novyZnak;
      retezec[delka] = 0;
    }
  }
}
(volatile je v obou případech jen aby se to fakt uložilo, jinak by to přinejmenším v 
tom druhém případě teď bez dalšího použití pole retezec překladač vyoptimalizoval)

Na mé instalaci se pro Arduino Uno A) přeloží do 2826 B programové paměti + 202 B 
gbálních proměnných, B) do 1388 B + 285 B. V B) jsem dal 100 jen tak od oka, neznaje 
max délku té věty v ASCII, tam by samozřejmě mělo být něco rozumnějšího. A) má proti 
B) větší program o cca kilo a půl. Na proměnných to je sice na první pohled naopak, 
B) teď potřebuje o 83 B víc místa pro globální proměnné, ale za běhu už pak nebude 
potřebovat téměř žádné lokální nebo dynamické alokování, zatímco varianta A) 
potřebuje vždy naalokovat dvakrát tolik místa, než je délka věty, kterou přijímá. 
Protože jak globální, tak lokální a dynamicky naalokované proměnné jsou ale ve 
stejné paměti, je potřeba porovnávat součet, takže B) bude proti A) úspornější i z 
tohoto pohledu.
(běhovou analýzu, jak přesně moc ten heap roste dělat nebudu) 
 

Detailnější pohled na přidání 1 písmene do Stringu: V případě A) se to retezec += 
novyZnak; přeloží zavoláním metody retezce concat, která uvnitř vytvoří z toho 
jednoho znaku céčkový řetězec a zavolá concat stávajícího Stringu s ním:
unsigned char String::concat(char c)
{
	char buf[2];
	buf[0] = c;
	buf[1] = 0;
	return concat(s.buffer, s.len);
}

Tehnle druhý concat si vyžádá rezervaci délky bufferu na požadovanou novou délku a 
strcpy pak na konec původního přikopíruje nový:
unsigned char String::concat(const char *cstr, unsigned int length)
{
	unsigned int newlen = len + length;
	if (!cstr) return 0;
	if (length == 0) return 1;
	if (!reserve(newlen)) return 0;
	strcpy(buffer + len, cstr);
	len = newlen;
	return 1;
}

Metoda reserve zavolá v případě zvětšování metodu changeBuffer:
unsigned char String::reserve(unsigned int size)
{
	if (buffer && capacity >= size) return 1;
	if (changeBuffer(size)) {
		if (len == 0) buffer[0] = 0;
		return 1;
	}
	return 0;
}

Metoda changeBuffer zavolá realloc:
unsigned char String::changeBuffer(unsigned int maxStrLen)
{
	char *newbuffer = (char *)realloc(buffer, maxStrLen + 1);
	if (newbuffer) {
		buffer = newbuffer;
		capacity = maxStrLen;
		return 1;
	}
	return 0;
}
Tady připouštím, že je možné, že realloc uvnitř zjistí, že za tím řetězcem je ještě 
volno, takže není potřeba přesouvat. To nevím, jak je pro avr implementované, ale i 
kdyby, tak za tím řetězcem volno principiálně být nemusí a tak to čert bude nutit 
přesouvat pořád znovu a znovu.

On 12 Apr 2024 at 18:19, Daniel Valuch wrote:

> zdravim osadenstvo,
> 
> programovat viem len velmi zakladne, preto by som mal na piatok 
> algoritmicku... Dokoncujem ziskavanie presneho casu z GNSS modulu o 
> ktorych sme tu uz nejaky cas rozpravali.
> 
> Ten generuje dva typy vystupu po seriovej linke. Klasicky ascii, ktory 
> je ukonceny \n znakom a parsovanie tohoto stringu je trivialne (robim v 
> arduino ide). Prijimaju sa data, tie sa pridavaju do stringu a ked pride 
> \n tak sa to rozobere a urobi co treba
> 
> void serialEvent() {
>    while (Serial.available()) {
>      // get the new byte:
>      char inChar = (char)Serial.read();
>      if (inChar == '\n') {
>          // sprava/retazec je hotovy, rozparsuj a urob co treba
>        tempString = "";
>      }
>      // keep receiving until \n arrives
>      else {
>        // add it to the inputString and keep receiving:
>        tempString += inChar;
>      }
> 
> Druhy vystup je ale binarny, prichadzaju pakety, ktore maju strukturu 
> definovanu, ale paket zacina opacne.
> 
> 0000  B5 62 06 8A 18 00 00 05 00 00 01 00 76 10 01 05
> 0010  00 53 10 01 7E 01 91 20 00 7F 01 91 20 01 00 B6
> 
> Prve dva bajty na zaciatku B5 62 vzdy oznacuju zaciatok paketu
> 
> dalsie dva 06 8A oznacuju o aky paket ide
> 
> nasledujuce dva 18 00 dlzku kolko byteov spravy nasleduje (v tomto 
> pripade 24)
> 
> potom je samotna sprava
> 
> a na konci 00 B6 su dva bajty checksum
> 
> a s tymto si neviem poradit, ako to zapisat aby to pocuvalo kedy pride 
> B5 62, pockalo ako dlha bude sprava a potom zapisalo do nejakeho stringu 
> data.
> 
> Ako na to?
> 
> dakujem,
> 
> b.
> 
> 





Další informace o konferenci Hw-list