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 & 0x80) {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…

AVR GCC optimization
Čia originalus softas.

AVR GCC optimization
Čia mano.

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ų.]

14 replies on “Biški C optimizacijos”

  1. Sitaip dar greiciau turetu veikti:

    uint8_t mask = 0x80;
    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);

  2. Č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…

  3. Algio variantas elegantiškesnis, bet…

    algio var

    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ė).

  4. Savel, o kokie LST gabaliukai gaunasi kiekvienu atveju, ir kuri gcc versija, kad taip nekokybiskai kompiliuoja?

  5. gcc: vr-gcc (GCC) 4.8.2 20131010

    Mano:

    110:ss1306.c ****
    111:ss1306.c **** for(unsigned char bit=0;bit<8;bit++)
    112:ss1306.c **** {
    113:ss1306.c **** L(PORTC, SS1306_OLED_CLK);
    39 .loc 1 113 0
    40 000e AB98 cbi 0x15,3
    114:ss1306.c **** if(data & 0x80) {H(PORTC,SS1306_OLED_DAT);} else { L(PORTC, SS1306_OLED_DAT); }
    41 .loc 1 114 0
    42 0010 67FF sbrs r22,7
    43 0012 00C0 rjmp .L4
    44 .loc 1 114 0 is_stmt 0 discriminator 1
    45 0014 AC9A sbi 0x15,4
    46 0016 00C0 rjmp .L5
    47 .L4:
    48 .loc 1 114 0 discriminator 2
    49 0018 AC98 cbi 0x15,4
    50 .L5:
    115:ss1306.c **** data = data < < 1; 51 .loc 1 115 0 is_stmt 1 52 001a 660F lsl r22 53 .LVL3: 116:ss1306.c **** H(PORTC, SS1306_OLED_CLK); 54 .loc 1 116 0 55 001c AB9A sbi 0x15,3 56 .LVL4: 57 001e 8150 subi r24,lo8(-(-1)) 58 .LVL5: 111:ss1306.c **** { 59 .loc 1 111 0 60 0020 01F4 brne .L7 61 .LBE12: 117:ss1306.c **** } 118:ss1306.c **** 119:ss1306.c ****

    Algio:

    120:ss1306.c ****
    121:ss1306.c **** unsigned char mask = 0x80;
    38 .loc 1 121 0
    39 0010 20E8 ldi r18,lo8(-128)
    40 .LVL3:
    41 .L7:
    122:ss1306.c **** do {
    123:ss1306.c **** L(PORTC, SS1306_OLED_CLK);
    42 .loc 1 123 0
    43 0012 AB98 cbi 0x15,3
    124:ss1306.c **** if (data & mask) H(PORTC, SS1306_OLED_DAT);
    44 .loc 1 124 0
    45 0014 322F mov r19,r18
    46 0016 3623 and r19,r22
    47 0018 01F0 breq .L4
    48 .loc 1 124 0 is_stmt 0 discriminator 1
    49 001a AC9A sbi 0x15,4
    50 001c 00C0 rjmp .L5
    51 .L4:
    125:ss1306.c **** else L(PORTC, SS1306_OLED_DAT);
    52 .loc 1 125 0 is_stmt 1
    53 001e AC98 cbi 0x15,4
    54 .L5:
    126:ss1306.c **** mask = mask >> 1;
    55 .loc 1 126 0
    56 0020 2695 lsr r18
    57 .LVL4:
    127:ss1306.c **** H(PORTC, SS1306_OLED_CLK);
    58 .loc 1 127 0
    59 0022 AB9A sbi 0x15,3
    60 0024 0197 sbiw r24,1
    128:ss1306.c **** } while (mask != 0);
    61 .loc 1 128 0
    62 0026 0097 sbiw r24,0
    63 0028 01F4 brne .L7
    129:ss1306.c ****
    130:ss1306.c **** // galiukas

  6. 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.

  7. O jeigu šitaip:

    for (unsigned char bit = 0; bit < 8; bit++) {
    PORTC &= ~(1<<SS1306_OLED_CLK); // clear
    PORTC ^= (-(data & 0x80) ^ PORTC) & (1 << SS1306_OLED_DAT); // set n-th bit to (data & 0x80)
    PORTC |= (1<<SS1306_OLED_CLK); // ready
    data <<= 1;
    }

  8. Korekcija ankstesniam kodui (praleistas r-shift 7):
    for (unsigned char bit = 0; bit < 8; bit++) {
    PORTC &= ~(1<<SS1306_OLED_CLK);
    PORTC ^= (-((data & 0x80) >> 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.

  9. 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.

    osci SPI
    hardwarė. +0 baitų.

    osci SPI
    mano. +50 baitų.

    osci SPI
    Tomo. +58 baitai.

  10. Tomui:

    nelabai gerai naudoti taip, nes portui daromas read modify write, ir jei ten prikabinta kokia hw funkcija, gali nutikti nenuspėjami dalykai.

  11. 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.

  12. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *