Biški C optimizacijos
Jau ne pirmą kartą randu svetimam softe vieną dalykėlį. Įdedu čia du fragmentus- originalų ir mano rašyta:
Originalus:
for(int8_t bit = 7; bit >= 0; bit–)
{
L(PORTC, SS1306_OLED_CLK);
if((1 < < bit) & data) { H(PORTC, SS1306_OLED_DAT); }
else { L(PORTC, SS1306_OLED_DAT); }
H(PORTC, SS1306_OLED_CLK);
}
Mano:
for(unsigned char bit=0;bit<8;bit++)
{
L(PORTC, SS1306_OLED_CLK);
if(data & 0×80) {H(PORTC,SS1306_OLED_DAT);} else { L(PORTC, SS1306_OLED_DAT); }
data = data < < 1;
H(PORTC, SS1306_OLED_CLK);
}
H() ir L() procedūros tai ne mano rašytos ir jos neturi įtakos. Pagrindinis skirtumas yra pakeisti “(1<<bit)" į “(data=data1<<1)". Atrodo paprastas pakeitimas, bet pagalvokim koks bereikalingas darbas stumti tuos bitus per visą ciklą… 7+6+5+4+3+2+1 bitų pastumimai originaliam softe ir 7 pastumimai mano softe. Skirtumas gerai matosi oscilografo ekrane…
Grubiai šnekant, su tuo pačiu MCU greitis padidėjo nuo 22μs iki 9μs vienam perduotam baitui, du su biškiu karto (ir 16 baitų mažesnis softas). O čia gi grafinis ekraniukas!
Ir kaip sakiau, jau ne pirmas kartas randu šitą klaidelę svetimam softe, kai daroma duomenu serializacija programiškai.
Aišku vienas loginis niuansas, mano algoritmas sunaikina kintamąjį data. Tačiau jį galima ir atsiminti, bet dažniausiai jis jau nereikalingas.
Kodėl tokios klaidos? Manau todėl, kad mąstoma šabloniškai- tikrinam duomenų bitus su “maske" ir pagal rezultatą išsiunčiam. Čia labai žmogiška, tačiau reikia galvoti plačiau- kam generuoti “maskę", jei galima taip pat stumdyti pačius duomenis. O jei tai būtų ciklinis stumimas ROL/ROR, tai netgi duomenys nesusigadintu- galima “apsukti" visą baitą ir vėl viskas bus kaip pradžioje.
[dar galimi kiti variantai, kai “maskę" galima irgi “sukti" ir ciklo sąlygas tikrinti pagal tai. Kodas gausis dar geresnis. Laukiam skaitytojų versijų.]
March 22nd, 2016 at 3:30 pm
Sitaip dar greiciau turetu veikti:
uint8_t mask = 0×80;
do {
L(PORTC, SS1306_OLED_CLK);
if (data & mask) H(PORTC, SS1306_OLED_DAT);
else L(PORTC, SS1306_OLED_DAT);
mask = mask >> 1;
H(PORTC, SS1306_OLED_CLK);
} while (mask != 0);
March 22nd, 2016 at 3:53 pm
Čia bėda su elementariom žiniom, kaip veikia bitukų pastūmimo operacija ir kad jos našumas yra ne O(1), o O(n).
Praėjo tie laikai, kai tikri vyrai rašydavosi device draiverius patys. O liūdniausia, kad net ir „geri“ gamintojų pavyzdžiai va su tokiais kreivais bitshiftinimais pateikiami…
March 22nd, 2016 at 6:53 pm
Algio variantas elegantiškesnis, bet…
Kodėl taip gavosi, reikia klausti gcc…
Ir kodas palyginus su mano pailgėjo 8 baitais…
Laukiam kitų variantų, netgi hardkoro (primenu tai AVR, asembleris welkomė).
March 22nd, 2016 at 7:03 pm
Savel, o kokie LST gabaliukai gaunasi kiekvienu atveju, ir kuri gcc versija, kad taip nekokybiskai kompiliuoja?
March 22nd, 2016 at 7:06 pm
gcc: vr-gcc (GCC) 4.8.2 20131010
Mano:
Algio:
March 22nd, 2016 at 7:35 pm
Ir kam tam c++ kompileriui do {} while cikle prireike paslepto dar vieno int16_t kintamojo?
Nors tavo atveju didelis + kompileriui buvo fiksuoto data bito tikrinimas, kai mano atveju jam teko daryti bereikalinga kopijavima.
March 23rd, 2016 at 11:34 pm
Palyginimui hardwarinis variantas su tuom pačiu MCU (@12MHz):
March 24th, 2016 at 4:55 am
O jeigu šitaip:
for (unsigned char bit = 0; bit < 8; bit++) {
PORTC &= ~(1<<SS1306_OLED_CLK); // clear
PORTC ^= (-(data & 0×80) ^ PORTC) & (1 << SS1306_OLED_DAT); // set n-th bit to (data & 0×80)
PORTC |= (1<<SS1306_OLED_CLK); // ready
data <<= 1;
}
March 30th, 2016 at 6:59 pm
Korekcija ankstesniam kodui (praleistas r-shift 7):
for (unsigned char bit = 0; bit < 8; bit++) {
PORTC &= ~(1<<SS1306_OLED_CLK);
PORTC ^= (-((data & 0×80) >> 7) ^ PORTC) & (1 << SS1306_OLED_DAT);
PORTC |= (1<<SS1306_OLED_CLK);
data <<= 1;
}
Už HW variantą ne greičiau, tačiau atsisakom if-branch'o.
March 30th, 2016 at 11:01 pm
Kadangi biški pasikeitė kitos softo dalys (išmečiau bereikalingus pradinius nustatymus ir bereikalingus perjungimas, kad ir priverstini CLK LOW), tai perdarau paveiksliukus. Ir dar geriau įžeminau, todėl vaizdelis geresnis. Ir kad tikslesnis matavimas, tai matuojam nuo pirmo LH iki paskutinio HL.
hardwarė. +0 baitų.
mano. +50 baitų.
Tomo. +58 baitai.
April 3rd, 2016 at 9:08 pm
Tomui:
nelabai gerai naudoti taip, nes portui daromas read modify write, ir jei ten prikabinta kokia hw funkcija, gali nutikti nenuspėjami dalykai.
April 3rd, 2016 at 9:53 pm
Aš kiek suprantu, gcc sukompiliuoja taip, kad nenaudojama procedūra read-modify-write, o naudojama asemblerio komanda SBI (Sets a specified bit in an I/O register.). Čia kaip ir “tradicinis palikimas" suderinamumui su kitais procesoriais, niekur nedaroma PORTn |= x baito lygyje.
April 5th, 2016 at 11:15 am
XOR be read-modify-write nepadarysi
April 7th, 2018 at 11:49 am
Dar vienas pastebejimas. Tos pauzės su poslinkiu labai aktualu AVR tipo procesoriams. ARM procesorius turi hardwarinį slinktuvą ir bet koks bitų poslinkis atliekamas per vieną taktą. Kiek pamenu Z80 ir 6502 to nemoka. Kitų procesorių tai giliai nekapsčiau.