kategoria: Asembler
[#1] Czyszczenie bloku pamięci w Asemblerze
optymalizuję sobie różne procedurki w AmigaE i do przyspieszenia jednej pętli chciałbym użyć asemblera, którego w E można bezpośrednio wstawiać.
Pętla jest bardzo prosta, jej zadaniem jest ustawić każdy bajt w buforze pamięci na $FF.

Nie za bardzo wiem jak to ugryźć żeby było jak najszybciej, może jakaś podpowiedź speców od asemblera?

kod E jest mniej więcej taki:

bufSize:=$6400
    buf:=AllocMem(bufSize, MEMF_ANY)
    (...)		
	->ustawiamy cały bufor na $FFFF...
	FOR i:=0 TO bufSize-1 
	      buf[i] := $FF
	ENDFOR


Można ilość przebiegów zmniejszyć 4 razy, zapisując długie słowo $FFFFFFFF, ale chciałbym żeby sam FOR był w asemblerze - jak to najszybciej zrobić?


edit: A moze szybciej byłoby przy AllocMem ustawić flagę MEMF_CLEAR (wyzeruje bufor), a w pętli asemblerowej zrobić negację na pamięci, zamiast move.l $FFFFFFFF?

Ostatnia aktualizacja: 18.12.2019 19:38:36 przez vojo
[#2] Re: Czyszczenie bloku pamięci w Asemblerze

@vojo, post #1

Jeżeli rozmiar bufora w bajtach (bufSize) jest podzielny przez 4 możesz użyć szybkiego czyszczenia za pomocą MOVE.L. Warto również wstawić tę liczbę #$FFFFFFFF raz do rejestru.

Poniżej przykład. Uwaga: W tej wersji bufor nie może być większy niż 65532 bajty! Jeśli chcesz większy - zmień warunek pętli korzystając z licznika 32-bitowego.

MOVEQ #-1,D0 -> Ustawia rejestr D0 na wartość $FFFFFFFF

MOVE.W #bufSize,D1
LSR.W #2,D1
SUBQ.W #1,D1 -> Licznik dla instrukcji DBF

loop:
MOVE.L D0,(A0)+
DBF D1,loop

Z uwagi na to, że procesor MC68000 nie ma pamięci cache procedura iteracji powinna zawierać więcej instrukcji MOVE.L D0,(A0)+, gdyż każdy skok jest bardzo cenny.

Od MC68020+, który ma pamięć cache na instrukcje - nie jest to wymagane.

Wspominam o tym, bo chyba chcesz by to działało dla MC68000.

Poza tym, jeśli to jest pamięć CHIP, możesz też użyć Blittera do wyczyszczenia pamięci na $FFFFFFFF.

Od wersji V36 systemu funkcja BltClear() to robi. Ustawiasz bit 2 we Flags oraz wartość $FFFF w starszym słowie Flags.

http://amigadev.elowar.com/read/ADCD_2.1/Includes_and_Autodocs_3._guide/node02B4.html

Pozdrawiam.

Ostatnia aktualizacja: 18.12.2019 19:57:19 przez Hexmage960
[#3] Re: Czyszczenie bloku pamięci w Asemblerze

@Hexmage960, post #2

Dzięki, przetestuję :)

Rozmiar zmiennych jest OK, maksymalna wielkość bufora to $ffff.
Blitter odpada, bo buf może być w FAST.

No i rozumiem że adres bufora do A0 przed rozpoczęciem petli.

piwo
[#4] Re: Czyszczenie bloku pamięci w Asemblerze

@vojo, post #3

No i rozumiem że adres bufora do A0 przed rozpoczęciem petli.

Zgadza się, zapomniałem o tym wspomnieć.
[#5] Re: Czyszczenie bloku pamięci w Asemblerze

@vojo, post #1

Oczywiście to nie ma związku z dywagacjami o assemblerze ale czemu nie użyjesz flagi MEMF_CLEAR?
[#6] Re: Czyszczenie bloku pamięci w Asemblerze

@michal_zukowski, post #5


czemu nie użyjesz flagi MEMF_CLEAR?


Bo potrzebuję bloku pamięci z domyślnie ustawionym bitami, a nie z wyzerowanymi.
A na to flagi niestety nie ma.
[#7] Re: Czyszczenie bloku pamięci w Asemblerze

@Hexmage960, post #2

W tej wersji bufor nie może być większy niż 65532 bajty!

Czy w przykladzie ktory podales bufor nie moze byc max 4*65536?
[#8] Re: Czyszczenie bloku pamięci w Asemblerze

@Phibrizzo, post #7

Nie, bo uzywa move.w #Buffsize,D1, lsr.w #2,D1 i subq.w. #1,d1. Ogolnie lepiej takie rzeczy robic na longwordach, duzo bardziej czytelnie jest, a roznica w szybkosci dzialania niewielka na 68000, bo to jest poza petla.
[#9] Re: Czyszczenie bloku pamięci w Asemblerze

@Don_Adan, post #8

@Phibrizzo

Dokładnie tak jak pisze Don Adan.

Użyłem: MOVE.W #bufSize,D1, czyli operację o rozmiarze WORDa. Bufor zatem może mieć max. #$fffc bajty.

Prostym rozwiązaniem jest MOVE.L zamiast MOVE.W w tym miejscu oraz LSR.L zamiast LSR.W, ale trzeba nadal mieć na uwadze rozmiar bufora.

Przy okazji ujawnił się mały błąd w moim kodzie. Powinno być raczej MOVE.W bufSize,D1, bo bufSize to jest zmienna, a nie stała. Ciekawe czy kompilator Amiga E zwróci błąd.

Zastanawiam się też czy wykorzystanie rejestrów będzie OK. Z tego co czytałem, to E potrzebuje tylko rejestru A4 do swoich prywatnych celów. Tutaj wykorzystuję tylko D0, D1 i A0.

Ostatnia aktualizacja: 18.12.2019 23:19:41 przez Hexmage960
[#10] Re: Czyszczenie bloku pamięci w Asemblerze

@Hexmage960, post #9

Tak, ale jest jeszcze problem jesli bufor ma wartosc od 0 do 3 w tym kodzie, wtedy crash jest raczej pewny. Czyli minimalny bufor tez musi byc znany, i moze byc minimum 4.
[#11] Re: Czyszczenie bloku pamięci w Asemblerze

@Don_Adan, post #10

Zgoda, dzięki za spostrzeżenie. Procedura wymaga, by rozmiar bufora był podzielny przez 4 oraz to by nie był równy 0, bo wykonuje pętlę przynajmniej raz.

Żeby wprowadzić zabezpieczenie do tego kodu pewnie starczy dodać jeden BRA.S przed pętlą. DBF z argumentem 0 w rejestrze D1 nie wykona pętli ani razu.

Nie odejmujemy wówczas 1 od D1!

Tutaj cały kod z zabezpieczeniem przed rozmiarem równym 0:

MOVEQ #-1,D0
MOVE.W bufSize,D1
LSR.W #2,D1
MOVEA.L buf,A0

BRA.S .next
.loop:
MOVE.L D0,(A0)+
.next:
DBF D1,.loop

Póki bufor ma rozmiar do #$fffc (lub do $3fffc w wersji Phibrizzo z MOVE.L i LSR.L) pętlę z DBF w tej postaci można stosować.
[#12] Re: Czyszczenie bloku pamięci w Asemblerze

@Hexmage960, post #9

Nic nie stoi na przeszkodzie aby samodzielnie odłożyć na stos używane rejestry.

movem.l d0/d1/a0,-(a7)
movem.l (a7)+, d0/d1/a0
1
[#13] Re: Czyszczenie bloku pamięci w Asemblerze

@Hexmage960, post #11

Ta pętelka jest w Asemblerze ok 8 razy szybsza od FOR... ENDFOR w AmigaE, dzięki :)
[#14] Re: Czyszczenie bloku pamięci w Asemblerze

@vojo, post #13

Tak tylko z ciekawości... Masz Ty w tym E wskaźniki? Jak tak to weź spróbuj zrobić dokładnie to samo co ten kod asemblerowy czyli:

- zrób wskaźnik na tablicę ULONG i przypisz do niej adres tablicy bajtów
- przeiteruj przez rozmiar / 4
- każdemu elementowi nadaj wartość 0xFFFFFFFF czy tam -1

Może się okazać że też będzie te 8 razy szybciej a przy okazji 5x czytelniej ;)
[#15] Re: Czyszczenie bloku pamięci w Asemblerze

@vojo, post #1

Pochwal się co piszesz w E ?

Ostatnia aktualizacja: 26.12.2019 17:34:45 przez Sventevith
[#16] Re: Czyszczenie bloku pamięci w Asemblerze

@Sventevith, post #15

Cały czas to samo, ale rodzi się w głowie nowy pomysł :)
[#17] Re: Czyszczenie bloku pamięci w Asemblerze

@vojo, post #16

O super aplikacja i nie wymaga kicka 3.x. Fajnie że ktoś w E piszę. Obiecuje sobie kiedyś się E pobawić.
Używasz edytor z podświetlaniem składni ?

Ostatnia aktualizacja: 26.12.2019 18:07:59 przez Sventevith
[#18] Re: Czyszczenie bloku pamięci w Asemblerze

@vojo, post #16

Coś w ten deseń.
clear: ;a0=buffer, d0=size
	movem.l	d2-d7/a2-a6,-(sp)
	move.l	a0,a6
	add.l	d0,a6
	move.l	#123-1,d0

	moveq	#-1,d1
	moveq	#-1,d2
	moveq	#-1,d3
	moveq	#-1,d4
	moveq	#-1,d5
	moveq	#-1,d6
	moveq	#-1,d7
	move.l	d1,a0
	move.l	d2,a1
	move.l	d3,a2
	move.l	d4,a3
	move.l	d5,a4
	move.l	d6,a5

	movem.l	d1-d3,-(a6)
.loop	movem.l	d1-a5,-(a6)
	movem.l	d1-a5,-(a6)
	movem.l	d1-a5,-(a6)
	movem.l	d1-a5,-(a6)
	dbf	d0,.loop	;123*(52+52+52+52)+16

	movem.l	(sp)+,d2-d7/a2-a6
	rts
1
[#19] Re: Czyszczenie bloku pamięci w Asemblerze

@teh_KaiN, post #14

@Teh_KaiN
Hej, uważam że ten krótki kod w asemblerze nie jest nieczytelny.

Amiga E umożliwia wklejanie bezpośrednio instrukcji w asemblerze, więc dlaczego z tego nie skorzystać?

- każdemu elementowi nadaj wartość 0xFFFFFFFF czy tam -1

Instrukcja MOVEQ (Move Quick) służy do szybkiego umieszczania 8-bitowej danej ze znakiem w rejestrze danych (od -128 do 127). Dlatego piszemy -1, zamiast $FF ponieważ znak zostaje przekopiowany do wszystkich starszych bitów (innymi słowy znak liczby zostaje rozszerzony).

Czyli liczby dodatnie od $00 do $7F (od 0 do 127) zostaną zapisane w ten sam sposób, zaś liczby ujemne od $80 do $FF (od -128 do -1) zostaną zapisane jako $FFFFFF80 - $FFFFFFFF.

Zapis -1 daje zatem jasność co to tego jaka liczba znajdzie się w rejestrze.

Zauważ też, że MOVE.L D0,(A0)+ jest sporo szybsze niż MOVE.L #$FFFFFFFF,(A0)+.

Przykładowo dla procesora MC68020 adresowanie rejestrem danych jest całkowicie darmowe we wszystkich przypadkach, zaś adresowanie wartością natychmiastową o rozmiarze długiego słowa #<data>.L w najlepszym wypadku zajmie 1 cykl procesora. W zwyczajnym przypadku, gdy instrukcja jest w pamięci Cache procesora zajmuje 4 cykle procesora, a w najgorszym przypadku mamy 5 cykli.

Asembler może być przydatny w wielu newralgicznych miejscach, gdzie jawne określenie instrukcji i trybu adresowania może poprawić szybkość kodu.

Ostatnia aktualizacja: 28.12.2019 17:45:39 przez Hexmage960
[#20] Re: Czyszczenie bloku pamięci w Asemblerze

@Hexmage960, post #19

Hej,
Czy pod 060, można jakoś jeszcze szybciej wyczyścić bufor?
Ma może Ktoś przykład kodu?

W moim przypadku są to bufory typu byte, mają być czyszczone "zerami", ale moga miec różna wielkość, np:
320x240, 640x400, ale zawsze podzielne przez 2 lub 4..

Dzięki
[#21] Re: Czyszczenie bloku pamięci w Asemblerze

@mateusz_s, post #20

Tak, podobno mozna. Uzywasz instrukcji move16 z 68040/68060.
Ale zeby ona dzialala jak nalezy to przeznaczenie powinno znajdowac sie na adresie podzielnym przez 16. Czyli zwykle dodajesz cnop 0,16 przed nazwa buffora o ile jest statyczny, albo alokujesz 16 bajtow (chyba nawet 8 bajtow wystarczy) wiecej dla bufora dynamicznego i wyliczasz/ustawiasz go. Po prostu czyscisz rejestry danych za pomoca moveq #0,Dx, a rejestry adresowe za pomoca sub.l Ax,Ax. I robisz petle czyszczaca o ile pamietam to chyba jakas w takim stylu:
Clean
move16 D0-D3,(A0)
lea 16(A0),A0
dbf D7,Clean

Ale tutaj sa lepsi eksperci od 68040/68060, ktorzy kodowali dema, ja tylko kiedys sprawdzalem skladnie tej instrukcji.

Ostatnia aktualizacja: 02.06.2021 18:26:44 przez Don_Adan
[#22] Re: Czyszczenie bloku pamięci w Asemblerze

@Don_Adan, post #21

Prawie dobrze;)

move16 nie ma trybu Dn,mem (ma bardzo okrojoną listę wspieranych trybów adresowania), tak więc trzeba wszystko zrobić nieco bardziej dookoła. Trzeba się też nieco bardziej postarać, bo move16 działa kompletnie z pominięciem cache, tak więc dane źródłowe muszą być "gorące".

Zastanawiałem się czy wrzucać kod, ale co tam. Z perspektywy czasu widać że jest nieoptymalny. Pomiędzy move16 powinny być instrukcje dekrementacji oraz inkrementacji pętli (move16 jest pOEP-only, więc jest miejsce na instrukcję pOEP/sOEP), ale tutaj i tak mamy do czynienia z operacją która jest ograniczona przepustowością pamięci, więc pewnie nie ma znaczenia...

; clears memory 'box'. width has to be a multiple of 16
;asm void    memsetboxaligned16(REG(   a0  ,  UBYTE   *mem) ,
;					   REG(   d0 ,   UBYTE   value),
;					   REG(   d1 ,   int     stride),
;					   REG(   d2 ,   int     width),
;					   REG(   d3 ,   int     height) );
;

_memsetboxaligned16:

	movem.l	d0/d1/d2/d3-d4/a0/a1/d5, -(sp)

	muls.l	#$01010101, d0
	sub.l	d2, d1	; how many bytes to skip after each line

	lea		clearvalue16, a1
	add.l	#15, a1
	move.l	a1, d4
	and.l	#$fffffff0, d4
	move.l	d4, a1
	move.l	d0, (a1)
	move.l	d0, 4(a1)
	move.l	d0, 8(a1)
	move.l	d0, 12(a1)
	move.l	d0, 16(a1)
	move.l	d0, 20(a1)
	move.l	d0, 24(a1)
	move.l	d0, 28(a1)
	move.l	d0, 32(a1)

	lsr.l	#5, d2
	move.l	#32, d5

.loopy:
	tst.l	0(a1)	; make sure it is in the cache!
	tst.l	16(a1)
	move.l	d2, d4
.loopx:
	move16	(a1)+, (a0)+
	move16	(a1)+, (a0)+
	sub.l	d5, a1
	subq	#1, d4
	bgt.b	.loopx

	add.l	d1, a0	; skip to next line
	subq	#1, d3
	bgt.b	.loopy

	movem.l	(sp)+, d0/d1/d2/a0/a1/d3-d4/d5
	rts

clearvalue16:  ds.l	  96
[#23] Re: Czyszczenie bloku pamięci w Asemblerze

@kiero, post #22

Jeszcze taka dygresja. Kiedyś robiło się też kopiowanie/ustawianie pamięci na FPU, przy użyciu wskaźnika na 'double'. Teoretycznie 2x większy rejestr i łatwiej zaprogramować to w C. Nie pamiętam tylko czy kiedykolwiek robiłem testy takiego rozwiązania:)
[#24] Re: Czyszczenie bloku pamięci w Asemblerze

@kiero, post #22

Oo dzięki.. spróbuje to przekleić potem,

Robię w C i czyszczenie za pomocą memset
Sporo obniża wydajność i chciałem przetestować
Coś co może będzie bardziej wydajne
[#25] Re: Czyszczenie bloku pamięci w Asemblerze

@mateusz_s, post #24

To ja tylko dodam, że jeżeli czyścisz pamięć CHIP, dosyć szybko można to zrobić Blitterem. Jako, że Blitter jest asynchronicznym koprocesorem, procesor główny CPU jest wówczas zwolniony z tego zadania i może zajmować się czymś innym (np. logiką gry). Bardzo się to przydaje.

Zastanawiam się w kontekście wcześniejszych dyskusji, czy można zrobić szybszy WritePixelArray() do pamięci graficznej korzystający z Blittera na karcie, bo rozumiem, że czyścisz pamięć graficzną (albo dane, które chcesz do tej pamięci przesłać)?

Ostatnia aktualizacja: 02.06.2021 19:52:49 przez Hexmage960
[#26] Re: Czyszczenie bloku pamięci w Asemblerze

@Hexmage960, post #25

tak, znaczy bufor w fast ramie po prostu czyszcze,

to bufor dynamiczny, ale taki pomocniczy gdzie przechowuje pewne stany tak/nie
wiec jeszcze się zastanawiam czy ewentualnie można spróbować zrezygnować z czyszczenia w ogóle
i za pomocą jakiś wyrażeń logicznych operować na inny liczbach w co drugim przebiegu

Ostatnia aktualizacja: 02.06.2021 20:22:22 przez mateusz_s
[#27] Re: Czyszczenie bloku pamięci w Asemblerze

@mateusz_s, post #26

Tak, moj blad. Tutaj jest opis instrukcji.

link

Uzywana w FastATA, RoadShow i CopyMemQuick.

link

Mozesz sie spytac SpeedGeeka jak najlepiej jej uzyc, zeby najszybciej czyscila pamiec. On na pewno testowal.
[#28] Re: Czyszczenie bloku pamięci w Asemblerze

@kiero, post #22

kurczę..
nie wiem czy taki test na WinUAE moze być miarodajny,
ale testowałem kilka asm kodów i każdy był sporo wolniejszy od memset

np. dla bufora 320x240 w petli 10 tys.
- memset = 1.7 s (najszybciej)
- kod od @kiero = 4.4s
- oparty o 16 linijek move.l #0,(a0)+ (i petla do tego) = ok. 3.9 s
- oparty o clr.l (a0)+ w pętli = ok 7 s.

no to moze nie da się szybciej..
[#29] Re: Czyszczenie bloku pamięci w Asemblerze

@mateusz_s, post #28

Mozna zadac pytanie czy musisz ja czyscic.
[#30] Re: Czyszczenie bloku pamięci w Asemblerze

@mateusz_s, post #28

EDIT: widzę, że już na górze o tym rozwiązaniu wspomniano :)

Pamiętam, że kiedyś do czyszczenia ekranu używało się MOVEM (chyba).
Idea polegała na tym, że czyściło się wszystkie rejestry i jedną instrukcją można było wyczyścić 4*(8+6) bajtów pamięci.

Coś w tym stylu:
MOVEM D0-D7/A0-A5,(A6)+

Oczywiście warto mieć wielokrotność tego rozkazu (w zależności czy jest cache, czy nie).

Memset może już mieć tego rodzaju optymalizacje zaimplementowane.


Ostatnia aktualizacja: 03.06.2021 08:14:52 przez drsky
Na stronie www.PPA.pl, podobnie jak na wielu innych stronach internetowych, wykorzystywane są tzw. cookies (ciasteczka). Służą ona m.in. do tego, aby zalogować się na swoje konto, czy brać udział w ankietach. Ze względu na nowe regulacje prawne jesteśmy zobowiązani do poinformowania Cię o tym w wyraźniejszy niż dotychczas sposób. Dalsze korzystanie z naszej strony bez zmiany ustawień przeglądarki internetowej będzie oznaczać, że zgadzasz się na ich wykorzystywanie.
OK, rozumiem