26 Ostatnio edytowany przez piotrv (2006-02-04 12:59:30)

tebe - nie mówiłbyś tak, gdybyś widział ten mój algorytm na FP->ASCII...
za małą głowę mam, żeby to od razu robić w ASM.
natomiast nie wykluczam, że później znajdzie się ktoś kto będzie miał na tyle dużo samozaparcia, aby to przerobić. Operacje są dość proste - ale jest ich cholernie dużo (jak na ten temat), poza tym pewnie samo C robi spory narzut w wydajności i pamięciożerności. Na razie chodzi mi o to, żeby móc normalnie coś zrobić w tym C - bez float-a się nie obędzie.

aktualna (nie-)wydajność (ANTIC on, emulator ON):

fadd     -  370.0 ops/s (FC - 4880)
fsub     -  370.0 ops/s (FC - 4650)
fmult    -  119.0 ops/s (FC -  380)
fdiv     -   75.1 ops/s (FC -  256)
flog2    -   60.2 ops/s
flog10   -   31.5 ops/s
flogn    -   21.4 ops/s
fsqrt    -   13.5 ops/s
fcbrt    -    3.8 ops/s
fexp     -    1.7 ops/s // (!)

fmult2   - 1666.0 ops/s (FC -  380?) ( z = a * 2 )
fmult2ip - 5000.0 ops/s (FC -  380?) ( a = a * 2 )
fmult10  -  200.0 ops/s (FC -  380?) ( z = a * 10 )
fmult10ip-  208.0 ops/s (FC -  380?) ( a = a * 10 )
fdiv2    - 1666.0 ops/s (FC -  256?) ( z = a / 2 )
fdiv2ip  - 6000.0 ops/s (FC -  256?) ( a = a / 2 )

-----
2006-01-17 Wyniki poprawione z 185 na 333 dla +-
2006-01-18 Dodane fdiv
2006-01-25 Optymalizacja i poprawiony benchmark (przedtem fałszywe wyniki dla mult, div)
2006-01-31 Więcej funkcji, fexp -musi- być poprawiony
2006-02-04 Przyspieszone mult, add, sub, sqrt, cbrt

I'm not so bad, once you get to know me.

27 Ostatnio edytowany przez drac030 (2006-01-16 03:29:55)

Dlaczego nie możesz zmieścić zakresu E-38/E+38 na ośmiu bitach wykładnika? Atari zakres E-98/E+98 zmieściło na pięciu.

KMK
? HEX$(6670358)

28

Hihi, tu jest właśnie cały trick. Atari przechowuje wszystko w BCD, ja binarnie. W związku z tym mogę jako tako liczyć w C - patrz mnożenie - niby C a porównywalne z ASM. Jak się to przerobi na ASM, powinno być ze dwa razy szybsze. Na pewno da się coś jeszcze wycisnąć w C - ostatnia przeróbka fmul dała 3x przyspieszenie.

Nie wiem jak były liczone wyniki FASTCHIP na Atariki - jakby ktoś mógł zweryfikować czy z ANTICiem czy bez to byłoby super.

Mój format to float - czyli pojedyncza precyzja. Ale jeśli będzie to C to łatwo będzie dodać obsługę double (dwa razy większa mantysa + większa cecha). Aktualnie nie robie double, bo będzie to po prostu na pewno wolniejsze.

Dlaczego 38? bo 2^128 =ok. E+-38

Wersja double na PC ma zakres ok. E+-308

I'm not so bad, once you get to know me.

29 Ostatnio edytowany przez drac030 (2006-01-16 11:07:43)

Jeśli robisz to binarnie to bądź tak miły i zachowaj kolejność lo/hi.

Wyniki FASTCHIP-a były liczone napisanym do tego programem w asemblerze i przy włączonym obrazie (w GR.0).

PS. A właśnie, to też jest wada C - nie daje dos?ępu do obliczeń dziesiętnych.

KMK
? HEX$(6670358)

30 Ostatnio edytowany przez piotrv (2006-01-16 11:55:48)

Aktualnie nie rozdrabniam mantysy niżej niż unsigned int, czyli o ile nie stosujesz 32-bitowego procesora to nie będzie problemu.

Co do wydajności, to mam nadzieje, że bardziej skomplikowane operacje wykażą przewagę formatu binarnego. Trochę dziwne, że dodawanie jest tyle razy wolniejsze - ale to pewnie narzut języka to powoduje.

----
Zresztą nawet gdyby się uprzeć i z niewiadomych mi powodów chcieć zrobić mantysę:

man-16-lo | man-16-hi

zamiast aktualnej:

man-16-hi | man-16-lo

to jest to zmiana polegająca na wymianie dwóch elementów struktury - więc jakies 5s pracy wprawnego programisty.

I'm not so bad, once you get to know me.

31 Ostatnio edytowany przez drac030 (2006-01-16 16:29:01)

Mi idzie (czyli "mnie się rozchodzi") o to, żeby bajty w 16-bitowym słowie były w kolejności low/high, bo to pozwoli na ewentualną przyszłą obróbkę 16-bitowymi rozkazami 65C816. Niestety natywny format Atari jest od starszego do młodszego, dzięki czemu jego wynalazca jest juz przeze mnie obsobaczony do piątego pokolenia wstecz. :>

KMK
? HEX$(6670358)

32

Nie można walczyć z naturą...

Tzn. że 6502 wykorzystuje inny porządek niż 65C816?
Ja na poziomie słowa używam tego co jest w cc65 - więc pewnie natywnego dla 6502.

I'm not so bad, once you get to know me.

33

FP atarowskie wykorzystuje inny porządek niż naturalny dla 6502 (mantysa jest zapisywana od najstarszego bajtu do najmłodszego).

KMK
? HEX$(6670358)

34

drac030 napisał/a:

FP atarowskie wykorzystuje inny porządek niż naturalny dla 6502 (mantysa jest zapisywana od najstarszego bajtu do najmłodszego).

Mantysa jest w BCD, a 6502 ma operacje BCD tylko 8-bitowe, więc ciężko mówić o jakimś naturalnym porządku bajtów. >;->

piotrv: jeśli chodzi o wykładnik, to przecież może on być wykładnikiem 4 , 16 czy wręcz 256 a nie 2 i od razu będzie większy zakres liczb. Jeśli wybierzesz wykładnik 256 to operacje w rodzaju dodawania powinny dostać kopa (wydajnościowego), nawet jak dołożysz bajt na mantysę żeby nie tracić na dokładności.

https://www.youtube.com/watch?v=jofNR_WkoCE

35

Ale 65C816 ma 16-bitowe (operacje BCD) i stąd wiadomo, jaki jest ten "naturalny porządek".

KMK
? HEX$(6670358)

36

Większość algorytmów, które znalazłem operuje na zakresie E+/-38. Myślę, że marudzicie.
Do każdej operacji zapisuje algorytm. Myślę, że pierwsza wersja będzie tak jak jest, w następnych będzie można dodać wersję double lub oprzeć się o standardowe formaty float-a.

Co do znaku w bicie a nie w bajcie, to jest to bardzo rozsądne zapotrzebowanie jeśli chodzi o zajętość pamięci, ale wymaga pakowania / rozpakowywania float-a przed i po użyciu, przez co na pewno zwolni całą bibliotekę. Myślę, że na razie poprzestanę na dodaniu funkcji fpack, funpack pakujących do formatu bez extra bajtu znakowego - do zapisania na dysku lub w pamięci.

I'm not so bad, once you get to know me.

37

Myślę, że tak bardzo nie zwolni. Wydzielenie znaku z cechy to kilka cykli.

KMK
? HEX$(6670358)

38

Zajrzałem do kodu asm generowanego z CC65. Koszmar...
Nigdy nie myślałem, że 3 rejestry mogą aż tak ograniczać kompilator (większość kodu to move'y z/do pamięci...).

Ostatnie testy prędkości znacznie odbiegają od tego co powyżej.
(z pamięci)
fmult     - 37 ops/s
fmult10 - 200 ops/s
fmult2   - 1700 ops/s
fmult2ip -  5000 ops/s (być może więcej, koniec skali w benchmarku)

fmult2ip -> mnożenie * 2, argument i wynik w tym samym miejscu
fmult2 w stosunku do fmult2ip wykonuje 3 przesłania pamięciowe więcej - a jaki efekt...

Sama zmiana zmiennych z lokalnych / argumentowych na globalne zmniejszyła kod miejscami o połowe i przyspieszyła np. procedurę fmult dwukrotnie.

Mam już ze 20 funkcji, ale nadal pracuje nad przyspieszaniem tych podstawowych.

I'm not so bad, once you get to know me.

39

Ilość move'ów jeszcze nie przesądza o tym, że kod jest zły. Może wklej coś, to ocenimy.

KMK
? HEX$(6670358)

40 Ostatnio edytowany przez piotrv (2006-01-25 23:02:36)

Są tam same LDA, JSR, LDY, LDA (xx),Y, JSR... Ale spox, wieczorem wkleje fmult - tak jako ciekawostkę...

------
Poniżej kod fdiv (dzielenie zmiennoprzecinkowe). Jest to na razie rekordzista w zwalnianiu - szczytowa prędkość to 46.7 ops/s Od razu mówię, że nie uważam to za coś finalnego...

Zaktualizowałem też wyniki we wcześniejszej tabeli.

Co do CC65 to nie jest tak źle jak myślałem - po zgrubnej analizie kodu asm zauważyłem, że kompilator ten robi parę optymalizacji - np. skraca warunki, jeśli po sprawdzeniu części wiadomo, że cały warunek już będzie fałszywy, to dalej nie liczy. Niestety jak widać od cholery tu JSRów, więc to takie średnio miłe.

; ---------------------------------------------------------------
; void __near__ fdiv (struct __float*, struct __float*, struct __float*)
; ---------------------------------------------------------------

.segment    "CODE"

.proc    _fdiv: near

.segment    "CODE"

    ldy     #$03
    jsr     ldaxysp
    ldy     #$03
    jsr     ldaxidx
    sta     _dw_z1
    stx     _dw_z1+1
    ldy     #$03
    jsr     ldaxysp
    ldy     #$05
    jsr     ldaxidx
    sta     _dw_z1+2
    stx     _dw_z1+2+1
    jsr     ldax0sp
    ldy     #$03
    jsr     ldaxidx
    sta     _dw_bm
    stx     _dw_bm+1
    jsr     ldax0sp
    ldy     #$05
    jsr     ldaxidx
    sta     _dw_bm+2
    stx     _dw_bm+2+1
    ldy     #$03
    jsr     ldaxysp
    ldy     #$00
    sta     ptr1
    stx     ptr1+1
    lda     (ptr1),y
    jsr     pusha0
    ldy     #$03
    jsr     ldaxysp
    ldy     #$00
    sta     ptr1
    stx     ptr1+1
    ldx     #$00
    lda     (ptr1),y
    ldy     #$80
    jsr     decaxy
    jsr     tossubax
    sta     _r0exp
    lda     _r0exp
    jsr     pusha0
    ldy     #$05
    jsr     ldaxysp
    ldy     #$00
    sta     ptr1
    stx     ptr1+1
    ldx     #$00
    lda     (ptr1),y
    jsr     tosicmp
    bcc     L0533
    beq     L0533
    ldy     #$07
    jsr     pushwysp
    lda     #$FD
    jsr     pusha
    jsr     _fsetspec
    jmp     incsp6
L0533:    lda     _dw_bm
    ora     _dw_bm+1
    jne     L0539
    lda     _dw_bm+2
    ora     _dw_bm+2+1
    jne     L0539
    jsr     ldax0sp
    ldy     #$00
    sta     ptr1
    stx     ptr1+1
    lda     (ptr1),y
    bne     L053F
    ldy     #$03
    jsr     ldaxysp
    ldy     #$03
    jsr     ldaxidx
    cpx     #$00
    bne     L0541
    cmp     #$00
    bne     L0541
    ldy     #$03
    jsr     ldaxysp
    ldy     #$05
    jsr     ldaxidx
    cpx     #$00
    bne     L0541
    cmp     #$00
    bne     L0541
    ldy     #$03
    jsr     ldaxysp
    ldy     #$00
    sta     ptr1
    stx     ptr1+1
    lda     (ptr1),y
    bne     L0541
    ldy     #$07
    jsr     pushwysp
    lda     #$FE
    jsr     pusha
    jsr     _fsetspec
    jmp     incsp6
L0541:    ldy     #$07
    jsr     pushwysp
    lda     #$FF
    jsr     pusha
    jsr     _fsetspec
    ldy     #$07
    jsr     pushwysp
    ldy     #$05
    jsr     ldaxysp
    ldy     #$01
    jsr     ldaidx
    jsr     staspidx
    jmp     incsp6
L053F:    jsr     pushw0sp
    lda     #$FF
    jsr     pusha
    jsr     _fisspec
    tax
    beq     L0551
    ldy     #$05
    jsr     pushwysp
    lda     #$FF
    jsr     pusha
    jsr     _fisspec
    tax
    beq     L0551
    ldy     #$07
    jsr     pushwysp
    lda     #$FE
    jsr     pusha
    jsr     _fsetspec
    jmp     incsp6
L0551:    ldy     #$07
    jsr     pushwysp
    ldy     #$05
    jsr     pushwysp
    jsr     _fcopy
    jmp     incsp6
L0539:    ldy     #$03
    jsr     ldaxysp
    ldy     #$00
    sta     ptr1
    stx     ptr1+1
    lda     (ptr1),y
    beq     L0562
    lda     _dw_z1
    ora     _dw_z1+1
    bne     L0561
    lda     _dw_z1+2
    ora     _dw_z1+2+1
    bne     L0561
L0562:    ldy     #$07
    jsr     pushwysp
    ldy     #$09
    jsr     pushwysp
    ldx     #$00
    txa
    ldy     #$04
    jsr     staxspidx
    ldy     #$02
    jsr     staxspidx
    ldy     #$05
    jsr     ldaxysp
    sta     sreg
    stx     sreg+1
    lda     #$00
    tay
    sta     (sreg),y
    ldy     #$05
    jsr     ldaxysp
    sta     sreg
    stx     sreg+1
    lda     #$01
    tay
    jmp     L0A13
L0561:    tya
    sta     _dw_z+2
    sta     _dw_z+2+1
    sta     _dw_z
    sta     _dw_z+1
    lda     #$20
L0A21:    sta     _loop_cnt
    lda     _loop_cnt
    jeq     L0577
    lda     _loop_cnt
    cmp     #$11
    bcs     L0A26
    lda     _dw_z+2+1
    and     #$80
    beq     L057E
    lda     _dw_z
    ldx     _dw_z+1
    jsr     shlax1
    ora     #$01
    jmp     L0A1D
L057E:    lda     _dw_z
    ldx     _dw_z+1
    jsr     shlax1
L0A1D:    sta     _dw_z
    stx     _dw_z+1
L0A26:    lda     _dw_z+2
    ldx     _dw_z+2+1
    jsr     shlax1
    sta     _dw_z+2
    stx     _dw_z+2+1
    lda     _dw_z1
    ldx     _dw_z1+1
    jsr     pushax
    lda     _dw_bm
    ldx     _dw_bm+1
    jsr     tosicmp
    bcs     L058C
    ldx     #$FF
    jmp     L05A5
L058C:    lda     _dw_z1
    ldx     _dw_z1+1
    jsr     pushax
    lda     _dw_bm
    ldx     _dw_bm+1
    jsr     tosicmp
    bcc     L0592
    bne     L059E
L0592:    lda     _dw_z1+2
    ldx     _dw_z1+2+1
    jsr     pushax
    lda     _dw_bm+2
    ldx     _dw_bm+2+1
    jsr     tosicmp
    bcs     L0598
    ldx     #$FF
    jmp     L05A5
L0598:    lda     _dw_z1+2
    ldx     _dw_z1+2+1
    jsr     pushax
    lda     _dw_bm+2
    ldx     _dw_bm+2+1
    jsr     tosicmp
L059E:    ldx     #$00
L05A5:    txa
    bpl     L0588
    lda     _dw_z+2
    ldx     _dw_z+2+1
    jmp     L0A1E
L0588:    lda     _dw_z1+2
    ldx     _dw_z1+2+1
    jsr     pushax
    lda     _dw_bm+2
    ldx     _dw_bm+2+1
    jsr     tosicmp
    bcs     L05A9
    ldx     #$FF
    txa
    jsr     pushaFF
    lda     _dw_bm+2
    ldx     _dw_bm+2+1
    jsr     tossubax
    clc
    adc     _dw_z1+2
    pha
    txa
    adc     _dw_z1+2+1
    tax
    pla
    jsr     incax1
    sta     _dw_z1+2
    stx     _dw_z1+2+1
    lda     _dw_z1
    ldx     _dw_z1+1
    jsr     pushax
    lda     _dw_bm
    ldx     _dw_bm+1
    jsr     tossubax
    jsr     decax1
    jmp     L0A1F
L05A9:    lda     _dw_z1+2
    ldx     _dw_z1+2+1
    jsr     pushax
    lda     _dw_bm+2
    ldx     _dw_bm+2+1
    jsr     tossubax
    sta     _dw_z1+2
    stx     _dw_z1+2+1
    lda     _dw_z1
    ldx     _dw_z1+1
    jsr     pushax
    lda     _dw_bm
    ldx     _dw_bm+1
    jsr     tossubax
L0A1F:    sta     _dw_z1
    stx     _dw_z1+1
    lda     _dw_z+2
    ldx     _dw_z+2+1
    ora     #$01
L0A1E:    sta     _dw_z+2
    stx     _dw_z+2+1
    lda     _loop_cnt
    cmp     #$11
    bcs     L05B6
    lda     _dw_bm+2
    ldx     _dw_bm+2+1
    jsr     shrax1
    sta     _dw_bm+2
    stx     _dw_bm+2+1
    jmp     L05BA
L05B6:    lda     _dw_bm
    and     #$01
    beq     L05BB
    lda     _dw_bm+2
    ldx     _dw_bm+2+1
    jsr     shrax1
    pha
    txa
    ora     #$80
    tax
    pla
    jmp     L0A20
L05BB:    lda     _dw_bm+2
    ldx     _dw_bm+2+1
    jsr     shrax1
L0A20:    sta     _dw_bm+2
    stx     _dw_bm+2+1
    lda     _dw_bm
    ldx     _dw_bm+1
    jsr     shrax1
    sta     _dw_bm
    stx     _dw_bm+1
L05BA:    lda     _loop_cnt
    sec
    sbc     #$01
    jmp     L0A21
L0577:    lda     _dw_z
    ora     _dw_z+1
    bne     L05CB
    lda     _dw_z+2
    ora     _dw_z+2+1
    beq     L05CC
L05CB:    lda     _dw_z+1
    and     #$80
    tax
    lda     #$00
    jsr     bnegax
    beq     L05CC
    lda     _r0exp
    beq     L05CC
    lda     _dw_z+2+1
    and     #$80
    beq     L05D2
    lda     _dw_z
    ldx     _dw_z+1
    jsr     shlax1
    ora     #$01
    jmp     L0A22
L05D2:    lda     _dw_z
    ldx     _dw_z+1
    jsr     shlax1
L0A22:    sta     _dw_z
    stx     _dw_z+1
    lda     _dw_z+2
    ldx     _dw_z+2+1
    jsr     shlax1
    sta     _dw_z+2
    stx     _dw_z+2+1
    lda     _r0exp
    sec
    sbc     #$01
    sta     _r0exp
    jmp     L05CB
L05CC:    ldy     #$05
    jsr     ldaxysp
    sta     ptr1
    stx     ptr1+1
    lda     _dw_z
    ldx     _dw_z+1
    ldy     #$02
    sta     (ptr1),y
    iny
    txa
    sta     (ptr1),y
    ldy     #$05
    jsr     ldaxysp
    sta     ptr1
    stx     ptr1+1
    lda     _dw_z+2
    ldx     _dw_z+2+1
    ldy     #$04
    sta     (ptr1),y
    iny
    txa
    sta     (ptr1),y
    ldy     #$03
    jsr     ldaxysp
    ldy     #$01
    jsr     ldaidx
    jsr     pushax
    ldy     #$03
    jsr     ldaxysp
    ldy     #$01
    jsr     ldaidx
    jsr     tosicmp
    beq     L05E1
    ldy     #$05
    jsr     ldaxysp
    sta     sreg
    stx     sreg+1
    lda     #$00
    jmp     L0A23
L05E1:    ldy     #$05
    jsr     ldaxysp
    sta     sreg
    stx     sreg+1
    lda     #$01
L0A23:    ldy     #$01
    sta     (sreg),y
    lda     _dw_z
    ora     _dw_z+1
    bne     L05E8
    lda     _dw_z+2
    ora     _dw_z+2+1
    beq     L0A1B
L05E8:    lda     _r0exp
L0A1B:    sta     _zexp
    ldy     #$05
    jsr     ldaxysp
    sta     sreg
    stx     sreg+1
    lda     _zexp
    ldy     #$00
L0A13:    sta     (sreg),y
    jmp     incsp6

.endproc
I'm not so bad, once you get to know me.

41

Tja, zastapienie samych jsrow makrami przyspieszy sporo...

(nie wiem jak dlugie sa te prock, ktore se to wywoluje, ale moze warto)

42

Bardziej by się przydał kod w C - asma zainteresowani mogą sobie wygenerować.
"Inlajnowanie" procedur runtime-owych raczej niewiele pomoże - myślę, że problem stanowi nieefektywny kod w C.

W C można przekazywać structy przez wartość, co w przypadku małych structów których nie zamierzamy modyfikować jest zdecydowanie bardziej efektywne (niezależnie od procka i kompilatora). Nie wiem tylko, czy CC65 to obsługuje. Sugerowałem użycie 32-bitowych floatów, bo wtedy można zrobić:
typedef long int float;
typedef float double;
i można przekazywać floaty "normalnie".

https://www.youtube.com/watch?v=jofNR_WkoCE

43 Ostatnio edytowany przez piotrv (2006-01-26 20:33:30)

(po weryfikacji long-a przeedytowane)

W cc65 structy muszą być przekazywane przez pointer.

Rzeczywiście, long w cc65 jest 32-bitowy. Jeśli nawet nie będę przerabiał całego float-a, to na pewno przyda się ta informacja do przechowywania mantysy. Dzięki!

Argumenty funkcji w C są takie:

fdiv(_float result*, _float *a, _float *b);

result = a / b

Źródła udostępnie jak skończę ftoa (->ASCII), co wymaga flog10, co wymaga flog2...
(nie chce -się- zniechęcać).

I'm not so bad, once you get to know me.

44 Ostatnio edytowany przez piotrv (2006-01-26 22:47:06)

(Przeredagowane po poważniejszych testach)

Wyniki przeróbki mantysy:

Przerobiłem:

typedef struct __dword {
  word word1;
  word word2;
} _dword;

na:

typedef unsigned long _dword;

Po przeróbce - przy tym samym algorytmie, fdiv działa tak:

było:
  fdiv - 46.7 ops/s  (FC -  256)
jest:
  fdiv - 58.8 ops/s (FC -  256)

Niestety nie jest tak różowo, jak mi się początkowo wydawało... No ale i tak lepiej. No i kod znacznie się uprościł.

I'm not so bad, once you get to know me.

45

Na miesiąc wyjeżdzam na wakacje, dlatego spakowałem to co już mam i wywiesiłem dla wszystkich zainteresowanych na stronie:

http://republika.pl/piotrek_home/Atari/fmath65.html

Pozdro

I'm not so bad, once you get to know me.