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…

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

13 Responses to “Biški C optimizacijos”

  1. Algis Says:

    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);

  2. Saulius Says:

    Č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. Administrator Says:

    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. Algis Says:

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

  5. Administrator Says:

    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 0×15,3
    114:ss1306.c **** if(data & 0×80) {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 0×15,4
    46 0016 00C0 rjmp .L5
    47 .L4:
    48 .loc 1 114 0 discriminator 2
    49 0018 AC98 cbi 0×15,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 = 0×80;
    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 0×15,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 0×15,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 0×15,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 0×15,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. Algis Says:

    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. Administrator Says:

    Palyginimui hardwarinis variantas su tuom pačiu MCU (@12MHz):

    AVR SPI OLED

  8. Tomas Says:

    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;
    }

  9. Tomas Says:

    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.

  10. Administrator Says:

    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.

  11. Donatas Says:

    Tomui:

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

  12. Administrator Says:

    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.

  13. Donatas Says:

    XOR be read-modify-write nepadarysi

Leave a Reply

Bot-Check (Jei ne skaičiai spauskit refresh. Tik oranžinius naudoti.)

Unhappy Tikbalang