Pojďme programovat elektroniku: Jak to, že je průměrná webová stránka stejně velká jako Doom?

  • Arduino vás vrátí ke kořenům
  • Začnete si vážit každého bajtu RAM
  • Pojďme si připomenout, jak přemýšleli všichni vývojáři kdysi dávno

Pokaždé, když narazím na YouTube na nějakou tu počítačovou reklamu z dřevních dob IT, fascinuje mě nejen představa, že ti starší z nás takové počítače skutečně používali a připadalo jim to naprosto normální, ale zároveň to, že i tyto počítače ze 70. a 80. let v podstatě plnily stejné úlohy jako dnes.

Když IBM na sklonku letních prázdnin 1981 vyrukovalo se svým PC 5150, o jeho výpočetní výkon se staral procesor Intel 8088 s taktem 4,77 MHz a operační systém DOS měl k dispozici podle výbavy 16 až 256 kB RAM.

Ačkoliv je z dnešního úhlu pohledu taková výbavička jen stěží představitelná, IBM PC se ve své době stal senzací, brzy jej začali kopírovat ostatní výrobci, a vznikla tak unikátní platforma IBM PC Compatible, která ve své základní koncepci dodnes vládne světu, ačkoliv IBM už dávno žádné osobní počítače nevyrábí.

Éra, kdy ke všemu stačilo pár kilobajtů paměti

To jsem však trošku odbočil. Pointa mé úvahy spočívá v tom, že i na takovém starém a tisíckrát pomalejším počítači naši předchůdci psali první e-maily, hráli hry, psali textové dokumenty, počítali v tabulkových procesorech, vedli účetnictví a tak dále.

Tehdejším programům tedy muselo stačit opravdu jen pár bajtů paměti, aby splnily podobný úkol, ke kterému ty dnešní potřebují mnohdy až stovky megabajtů RAM.

Vývojáři té doby počítali doslova každý bit, zatímco ti dnešní mnohdy nasázejí i do primitivní webové stránky několik javascriptových knihoven. Průměrná velikost webové stránky se tak v posledních letech vyšplhala na zhruba 2,5 MB.

2,5 MB? To dnes samozřejmě nic není, ale jak loni na jaře poznamenal analytik Ronan Cremlin, před dvaceti lety by to stačilo na instalační program legendárního Doomu.

Arduino vás vrátí ke kořenům

Ohromné plýtvání prostředky si uvědomí každý bastlíř, kterému poštou dorazí v pěnové obálce první Arduino s pár kilobajty paměti, která musí stačit k tomu, aby se na drobném mikropočítači umístěném v polystyrenové krabičce třeba kdesi na zahradě spustil meteorologický program, každou minutu přečetl údaje z všemožných senzorů a numerická data nakonec odeslal skrze rádiový vysílač do základny ve vašem obývacím pokoji.

Klepněte pro větší obrázek
Jednoduchý obvod s řídícím čipem ATmega328P, externím 16MHz taktovacím krystalem, 433MHz vysílačem HC-12 a teploměrem DS18B20. Čip ATmega328P, který tvoří srdce i populárních arduin, má k dispozici jen 32kB flashovou paměť pro kód samotného programu, 2 kB SRAM a 1 kB persistentní EEPROM. Vývojář se tedy tak trošku vrací ke kořenům.

Jelikož chceme, aby takový vysílač pracoval co nejkratší dobu, protože si během vysílání řekne o pořádný kus elektrické energie, pomůžeme mu zkrácením zprávy, kterou má odeslat, jak to jen jde.

Ale pěkně popořadě. Lidé odkojení programováním pro web, by nejspíše takovou zprávu odesílali v textové podobě – v CSV, XML anebo dnes ve velmi populárním JSONu.

Klepněte pro větší obrázek Klepněte pro větší obrázek
A ještě jedna ukázka dvojice meteorologických mikropočítačů. Vlevo venkovní sonda s tlakoměrem, vlhkoměrem, teploměrem a luxmetrem o jejíž chod se stará oblíbený čip ESP8266 s podporou Wi-Fi. Vpravo pak domácí přijímací stanice, která páruje venkovní údaje s těmi vnitřními a vše zasílá na webový server. O její chod se stará mikropočítač Particle.io Photon, který má k dispozici čip z řady STM32 (ARM Cortex M3) a Wi-Fi vysílač od Broadcomu.

20 až 100+ bajtů dlouhá zpráva

Dejme tomu, že naše sonda na zahradě právě změřila tyto údaje, které má skrze vysílač odeslat do základny:

  • Teplota: -6,15 °C
  • Vlhkost: 78 %
  • Tlak: 999,45 hPa
  • Intenzita osvětlení: 2 254 lx

Pokud bychom data odesílali v textové podobě, mohla by mít naše zpráva podle formátu třeba následující podobu:

CSV

-6.15;78;999.45;2254 

JSON

{"teplota":-6.15,"vlhkost":78,"tlak":999.45,"svetlo":2254}

XML

<zprava><teplota>-6.15</teplota><vlhkost>78</vlhkost><tlak>999.45</tlak><svetlo>2254</svetlo></zprava>

Nejkratší podoba ve formátu CSV (hodnoty oddělené středníkem) zabírá 20 bajtů, zpráva v JSON 58 bajtů a v XML dokonce 102 bajtů.

Na klasickém webu bychom nad pár bajty samozřejmě mávli rukou – viz ona dvoumegbajtová velikost průměrné webové stránky, ale ve světě arduin jde opravdu o každý bajt a nějaký modelový rádiový vysílač by musel pracovat mnohem déle, než by data skutečně odeslal. S větším balíkem dat zároveň u slaboučkých vysílačů roste i pravděpodobnost, že se na první pokus neodešle zpráva korektně, a tak opakovaný přenos může zabrat delší čas.

Kdyby se doba vysílání prodloužila byť jen o jedinou sekundu, znamenalo by to při přenosu každou minutu 1 440 sekund navíc každý den! To odpovídá 24 minutám, během kterých vysílač odebírá z baterie proud třeba 100 mA.

Teď zkrátíme zprávu na 11 bajtů

Dost bylo textových řetězců. Teď budeme posílat stejnou meteorologickou zprávu jako řadů bajtů původních datových proměnných.

Teplota (4 B): Teplota vzduchu je číslo s desetinou čárkou, a tak ji uložíme do 32bitového datového typu float. Jeden bajt má 8 bitů, takže teplota zabere 4 bajty.

Vlhkost (1 B): Vlhkost vzduchu se pohybuje v rozmezí 0 až 100 %, takže použijeme 8bitový datový typ uint8_t, který může nabývat hodnot 0 až 255, takže vlhkost zabere 1 bajt.

Tlak (4 B): Tlak vzduchu je opět číslo s desetinou čárkou, znovu tedy použijeme 32bitový typ float, takže i tlak zabere 4 bajty.

Intenzita světla (2 B): Intenzitu světla změříme v luxech. Drtivá většina laciných senzorů měří v rozmezí 0 až cca 55 000 luxů (pokud senzor namíříme do jasného Slunce na obloze), pro uložení této informace tedy bude stačit 16bitový datový typ uint16_t, který může nabývat hodnot 0 až 65 535, takže intenzita světa zabere 2 bajty.

Teď to pojďme sečíst: 4 + 1 + 4 + 2 = 11 bajtů. To je mnohem méně než u textové podoby, kde každý znak zabírá celý jeden bajt.

Namísto textové zprávy si nyní vytvořím 11 bajtů dlouhé pole, do kterého to vše uložím. V případě jazyka pro Arduino, respektive C, ze kterého vychází, by to mohlo vypadat takto:

float teplota = -6.15; // 32bitova teplota
uint8_t vlhkost = 78; // 8bitova vlhkost
float tlak = 999.45; // 32bitovy tlak
uint16_t svetlo = 2254; // 16bitove svetlo

uint8_t zprava[11]; // 11bajtove pole pro nasi zpravu

// Zkopiruj 4 bajty teploty na pozici 0 v poli
memcpy(&zprava[0], &teplota, 4); 
// Zkopiruj 1 bajt vlhkosti na pozici 4 v poli
memcpy(&zprava[4], &vlhkost, 1);
// Zkopiruj 4 bajty tlaku na pozici 5 v poli
memcpy(&zprava[5], &tlak, 4);
// Zkopiruj 2 bajty svetla na pozici 9 v poli
memcpy(&zprava[9], &svetlo, 2);

// hypoteticka funkce, ktera odesle zpravu skrze vysilac
if(odesliZpravu(zprava, sizeof(zprava))){
 Serial.println("Hura, odeslal jsem zpravu!");
}
else{
 Serial.prinln("Zpravu se nepodarilo odeslat. Muze za to Kalousek!");
}

Tak a teď už je v našem jedenáctibajtovém poli zprava naprosto vše a můžeme jej odeslat. Zpráva je o polovinu kratší než textová podoba v CSV, takže její vytvoření méně zatíží jednoduchý mikropočítač a stejně tak vysílač ji odešle mnohem rychleji.

Pomocí funkce memcpy pro pohyb s jednotlivými bajty pak můžeme pole opět rozsekat na jednotlivé části obráceným postupem:

float teplota;
uint8_t vlhkost;
float tlak;
uint16_t svetlo;

// Zkopiruj do promenne teplota 4 bajty z pozice 0 v poli zprava
memcpy(&teplota, &zprava[0], 4);
// Zkopiruj do promenne vlhkost 1 bajt z pozice 4 v poli zprava
memcpy(&vlhkost, &zprava[4], 1);
// Zkopiruj do promenne tlak 4 bajty z pozice 5 v poli zprava
memcpy(&tlak, &zprava[5], 4);
// Zkopiruj do promenne svetlo 2 bajty z pozice 9 v poli zprava
memcpy(&svetlo, &zprava[9], 2);

Zkrátíme zprávu ještě víc na 7 bajtů

Jedenáct bajtů je ale pořád hodně. Tak třeba ta teplota. Opravdu potřebujeme pro uložení čísla s desetinou čárkou celé čtyři bajty? No, pokud by nám šlo o přesnost třeba na čtyři desetinná místa, tak ano, ale u teploty nám bude bohatě stačit přesnost na dvě desetinná místa.

Původní 32bitové číslo s desetinou čárkou bychom tedy mohli jednoduchým přepočtem uložit do 16bitového celého čísla se znaménkem:

int16_t kratsiTeplota = teplota * 100;

Původní teplotu jsem tedy znásobil stem, takže z hodnoty -6,15 se rázem stalo -615. Datový typ int16_t pojme hodnoty v rozsahu -32 768 až 32 767, takže při této přesnosti do něj můžeme uložit mezní hodnoty -327,68 °C až 327,67 °C, což by mělo stačit až do chvíle, než začne mikropočítač hořet.

Podobným způsobem bychom mohli převést na 16bitové celé číslo i tlak vzduchu. Pouhé znásobení stem ale nestačí, tlaku 995,45 hPa by totiž odpovídalo číslo 99 545 a to se nám do datového typu uint16_t bez znaménka už nevleze, protože hraniční hodnoty jsou 0 až 65 535.

Běžný tlak vzduchu se v našich podmínkách nicméně pohybuje v relativně malém rozmezí několika desítek hektopascalů. Nejprve tedy od tlaku odečteme třeba 900 a teprve pak jej znásobíme:

 uint16_t kratsiTak = (tlak - 900) * 100;

Pokud bychom zaměřili tlak 980,45 hPa, přepočítáme jej tedy na 8 045.
No a pokud by poskočil na 1 015,87 hPa, po přepočtu získáme 11 587.

V obou případech se hravě vlezeme do rozsahu 16bitového čísla.

Suma sumárum, drobným přepočtem jsme ušetřili další 4 bajty a naše zpráva tak rázem zabírá pouze 7 bajtů. Skoro třikrát méně než v případě textového CSV.

Textová zpráva v CSV (20 bajtů):
2d 36 2e 31 35 3b 37 38 3b 39 39 39 2e 34 35 3b 32 32 35 34

Binární zpráva (11 bajtů):
cd cc c4 c0 4e cd dc 79 44 ce 08

Zkrácená binární zpráva (7 bajtů)
99 fd 4e d9 26 ce 08

Pojďme si pohrát s jednotlivými bity a ušetříme několik dalších celých bajtů

No dobrá, ale co kdybychom v té naši kompresi pokračovali dál. Co kdybychom teplotu uložili do jediného bajtu? Šlo by to? Ale jistě, ale za určitou cenu.

Jeden bajt má osm bitů a může nabývat hodnoty 0 až 255.

Číslo 1 bychom tedy mohli ve dvojkové soustavě jednotlivých bitů vyjádřit jako:

00000001

Zatímco 255 jako:

11111111

Čím vyšší číslo, tím vice jednotlivých bitů tedy potřebuje a bajt se postupně zprava zaplňuje. K uložení čísla 256 bychom potřebovali další bajt, a tak už potřebujeme 16bitovou proměnnou, která se právě z dvou bajtů skládá.

Pojďme ale trošku níže. Nás bude zajímat číslo 63, protože zabírá šest bitů:

00111111

Dva bity zcela vlevo zůstávají neobsazené. Do dvou bitů přitom mohu uložit rozsah 0 až 3, protože platí:

  • 0 = 00
  • 1 = 01
  • 2 = 10
  • 3 = 11

Do jednoho bajtu tedy mohu uložit dvě celočíselné hodnoty. Ta první bude mít rozsah 0 až 63 a ta druhá 0 až 3.

Dejme tomu, že obvyklá venkovní teplota vzduchu nepřekročí mezní hodnoty -20 až 40 °C. Absolutní rozdíl je tedy 60, čili pro uložení takové hodnoty nám bude bohatě stačit oněch šest bitů. Zbývající dva bity bychom mohli použít pro uložení desetinné části s přesností na ¼ °C.

To znamená, že 0 bude odpovídat desetinné části 0-0,25, 1 pak 0,25-0,5, 2 následně 0,5-0,75 a konečně 3 hodnotám 0,75-1.

Příklad: 16bitovou teplotu 10,36 °C tímto způsobem převedu na 8bitovou teplotu 10,25-10,5 °C = takže třeba na střední hodnotu tohoto rozsahu 10,375 °C.

Hrátky s bitovými operátory

No dobrá, ale jak na to? Abychom mohli pracovat s jednotlivými bity, nabízí každý slušný jazyk bitové operátory. Náš článek jistě není učebnicí základů programování, takže teoretickou omáčku si případně nastudujte jinde a my se podíváme rovnou na praktický příklad.

Dejme tedy tomu, že si opět vytvoříme proměnnou o velikosti jednoho bajtu:

uint8_t teplota = 0;

Ve dvojkové soustavě má proměnná teplota v tuto chvíli hodnotu:

00000000

Skutečná teplota bude třeba pekelných 35,36 °C.

Nejprve tedy uložíme základ 35. Jak už jsem napsal výše, celé číslo musím převést na rozsah 0-63 (6 bitů) a jelikož nejnižší možná teplota bude -20, teplotě 35 °C bude v tomto rozsahu odpovídat hodnota 20 + 35 a tedy 55.

teplota = 55;

Číslo 55 má ve dvojkové soustavě podobu:

00110111

Výborně, zcela vlevo máme pořád ty dva nevyužité bity, které nyní použijeme pro uložení zjednodušené desetinné části.

Jelikož se nám bity zaplňují zprava, uděláme si nejprve místo úplně napravo. Provedeme bitový posun doleva o dvě pozice, k čemuž v C i v Arduinu slouží operátor <<. Takže:

teplota = teplota << 2;

Posunem o dva bity vlevo se nám pořadí přeskládalo tímto způsobem:

00110111
<< 2
11011100

Naše číslo 55 tam tedy stále je, ačkoliv celý bajt má nyní v desítkové soustavě hodnotu 220.

Pro nás je důležité, že nyní máme připravené dva nulové bity zcela vpravo a ty vyplníme hodnotou 0 až 3 pomocí operátoru ^ s naprosto neatraktivním názvem bitový exkluzivní součet. Když takto sečteme bity 1 ^ 0, bude výsledkem bit 1. Když sečteme bity 0 ^ 0, bude výsledkem bit 0. A na závěr, když sečteme bity 1 ^ 1, bude výsledkem opět bit 0. Jednička tedy vznikne jen u rozdílných bitů.

Naše teplota je 35,36 °C. Desetinná část spadá do rozsahu 0,25-0,5 a bude tedy reprezentovaná hodnotou 1, která má v dvojkové soustavě také hodnotu 1. Jelikož naše dva bity zcela vpravo jsou nulové, otiskneme do nich tímto operátorem naši hodnotu (0 ^ 1 = 1):

 11011100
^00000001
 11011101

Heuréka, zaplnili jsme celý bajt, jehož hodnota v desítkové soustavě je nyní 221, nicméně pro nás to jsou čísla 55 (110111) a 1 (01).

Pro přehlednost ještě doplním celý kód v C:

uint8_t teplota = 55;
teplota = teplota << 2;
teplota = teplota ^ 1;

Teplotu 35,36 °C, která původně zabírala 4 bajty (32bitové číslo s desetinnou čárkou), jsme tedy zkomprimovali do jediného bajtu, ve kterém je zakódovaná hodnota 35,25-35,5 °C

Na závěr si ještě ukážeme, jak obě zakódované hodnoty opět získat. Budeme postupovat obráceně. Nejprve tedy získáme číslo 55 a to tak, že posuneme bity o dvě pozice opačným směrem doprava pomocí operátoru >>:

11011101
>> 2
00110111

V Arduinu by to mohlo vypadat takto:

uint8_t zaklad = teplota >> 2;

Proměnná zaklad má tedy nyní hodnotu 55. Desetinnou část získáme pomocí dalšího bitového operátoru & jménem bitový součin. Funguje jednoduše. Pokud má některý z bitů nulovou hodnotu, výsledkem je nula.

Takže platí:

 01010101
&00000011
 00000001

Součin přežil z prvního čísla jen poslední jedničkový bit, protože jednička se vyskytovala v obou číslech. U ostatních bitů byla v jednom z čísel 0, takže nulový byl i výsledek součinu.

Bitový součin lze tedy použít jako masku, která nám smaže bity, které se nám nehodí a ponechá jen ty správné. Nyní ji použiji, abych z původního bajtu smazal vše až na první dva bity vpravo, kde je uložená naše desetinná část:

 11011101
&00000011
 00000001

V Arduinu by mohla nakonec vypadat celá rekonstrukce obou čísel následovně:

uint8_t zaklad = teplota >> 2;
uint8_t desetinnaCast = teplota & 0b00000011;

Všimněte si, že jsem pro větší přehlednost zapsal masku pro bitový součin ve dvojkovém formátu. Čísla ve dvojkové soustavě se uvozují znaky 0b. V masce je tedy krásně patrné, že šest bitů zleva se anuluje a zbudou jen dva bity zcela vpravo.

Tak a je to. V proměnné zaklad je 55, v proměnné desetinnaCast 1 a nyní už mohu dopočítat, že se jedná o teplotu v rozmezí 35,25 °C až 35,5 °C, protože kvůli kompresi do jediného bajtu jsem snížil rozlišení na 0,25 °C.

Klepněte pro větší obrázek
Původní 32bitová teplota a po naší ztrátové kompresi na 8 bitů a rozlišení 0,25 stupně.

Připomínka dob, kdy šlo o každý bit

A to je celé. Dnes jsme si tedy pohráli s čísly až na úroveň jednotlivých bitů a ušetřili tak ohromné množství prostoru. Trošku jsme si tedy připomněli dobu, kdy byli všichni kodéři maximálně nízkoúrovňoví a bojovali o každý bajt paměti na disketě, pevném disku a v operační paměti.

Jen díky tomu mohl být legendární Doom z počátku 90. let stejně rozměrný jako dnešní průměrná webová stránka s hromadou javascriptových knihoven. Myslete na to, až třeba budete některou z nich používat jen kvůli naprosté banalitě.


Všechno, co jsme si dnes vyzkoušeli, můžete otestovat třeba ve webovém kompilátoru a běhovém prostředí Coding Ground.

Témata článku: Pojďme programovat elektroniku, Historie, Programování, Arduino, Programování pro děti, C++, Tlakoměr, Textová podoba, Stránka, Nejkratší doba, Tabulkový procesor, Průměrná teplota, Jednoduchá rekonstrukce, Jednotlivé bity, Malé rozmezí, Ohromné množství, JSON, Průměr, Pre, Origin PC, Textová zpráva, Starý typ, Textové pole, Celý kód, Proměnná

Určitě si přečtěte

Tesla chce změnit nákladní dopravu. Její elektrický náklaďák má ohromující parametry

Tesla chce změnit nákladní dopravu. Její elektrický náklaďák má ohromující parametry

** Tesla představila elektrický kamion ** Má obdivuhodný výkon i dojezd ** Prodávat by se měl už za dva roky

17.  11.  2017 | Vojtěch Malý | 229

Black Friday 2017: Přehled slev na elektroniku a počítače

Black Friday 2017: Přehled slev na elektroniku a počítače

** Začala slevová mánie zvaná Black Friday ** Pozor, ne všechny slevy jsou opravdu výhodné ** Průběžně sledujeme slevové akce v počítačových e-shopech

Včera | David Polesný | 24

Google Mapy mají nový design. Líbí se vám víc než předchozí? Tady je srovnání

Google Mapy mají nový design. Líbí se vám víc než předchozí? Tady je srovnání

** Nový design Google Map přijde na počítače i mobilní telefony. ** Zaměřuje se na zvýraznění konkrétních míst, mapové podklady jsou mnohdy upozaděné. ** Lépe pracuje s chráněnými oblastmi a parky.

20.  11.  2017 | Vladislav Kluska | 29

Bluetoothové patálie: O bezdrátovém přenosu hudby a  problémech s kodeky

Bluetoothové patálie: O bezdrátovém přenosu hudby a problémech s kodeky

** Bezdrátový přenos hudby je budoucnost ** K dosažení nejlepší kvality je ale potřeba, aby telefon i sluchátka podporovala správný kodek ** Záleží také na typu souborů s hudbou

17.  11.  2017 | Jakub Michlovský | 39


Aktuální číslo časopisu Computer

Otestovali jsme 5 HDR 4K televizorů

Jak natáčet video zrcadlovkou

Vytvořte si chytrou domácnost

Radíme s koupí počítačového zdroje