kategorie: ANSI C, Asembler
[#1] VBCC i przykre zaskoczenie
W ramach porównywania kompilatorów na klasyka, oraz porównania ręcznej zabawy w asembler, zrobiłem prosty sprawdzian. Napisałem w asmie i w C prostą funkcję, StrSize(), która po prostu podaje długość łańcucha tekstowego wraz z kończącym bajtem 0. Czyli innymi słowy ile bajtów zajmuje łańcuch. Trywialne. Adres łańcucha przekazywany jest w rejestrze A0. W asmie napisałem to tak:

public      _StrSize

_StrSize    MOVEA.L a0,a1
.1          TST.B   (a0)+
            BNE.S   .1
            SUBA.L  a1,a0
            MOVE.L  a0,d0
            RTS

Teraz postanowiłem przepisać to wiernie w C:

ULONG StrSize(REG("a0") CONST UBYTE *s)
{
    CONST UBYTE *p;

    p = s;
    while (*s++);
    return (s - p);
}

Skompilowałem to kompilatorem GCC 2.95.3 z opcjami -O2 -fomit-frame-pointer. Ta druga jest po to, żeby kompilator nie tworzył zbędnego wskaźnika na ramkę stosu w rejestrze A5. Zdisasemblowałem funkcję:

_StrSize    MOVE.L  a0,d0
.1          TST.B   (a0)+
            BNE.S   .1
            SUBA.L  d0,a0
            MOVE.L  a0,d0
            RTS

W zasadzie to samo, chociaż warto zauważyć, że ja użyłem trzy rejestry, kompilator tylko dwa. Co prawda i tak wszystkie są „scratch register”, więc to w sumie wszystko jedno.

Teraz zatem nakarmiłem kodem kompilator VBCC, z opcją -O2 (opcja -O3 daje ten sam kod). VBCC domyślnie nie używa A5 jako wskaźnika ramki stosu, tylko w razie potrzeby odwołuje się do zmiennych lokalnych przez SP. Disasemblacja i WTF, zobaczcie sami:

_StrSize    MOVEA.L  a0,a1
            MOVE.L   a1,d1
            MOVEA.L  a1,a0
            ADDQ.L   #1,a1
            TST.B    (a0)
            BEQ.S    .2
.1          MOVEA.L  a1,a0
            ADDQ.L   #1,a1
            TST.B    (a0)
            BNE.S    .1
.2          MOVE.L   a1,d0
            SUB.L    d1,d0
            RTS

Nie można powiedzieć, że to nie działa. Tylko primo kod jest znacznie dłuższy, secundo, w zasadniczej pętli są cztery instrukcje zamiast dwóch. Być może to mimo wszystko jest szybsze (nie robiłem jeszcze benchmarków), ale jakoś mi się nie wydaje. Dziwna żonglerka rejestrami (po jaką cholerę najpierw kopiować A0 do A1, żeby za chwilę kopiować A1 do A0?), wystawienie pierwszego przebiegu pętli poza nią... A to przecież jest kod dla prostej M68000 bez żadnych superscalarów, bez cache nawet.

Jutro zrobię jakiś konkretny benchmark (na 68020, bo taki procesor mam w Amidze), ale jeżeli ten kod nie będzie szybszy, albo wręcz będzie wolniejszy, to moje ostatnio lepsze zdanie o VBCC znów się pogorszy. Bo tutaj VBCC konkuruje z kompilatorem sprzed, zdaje się, 24 lat... Ktoś ma jakieś ciekawe uwagi?

Ostatnia aktualizacja: 25.09.2017 22:58:00 przez Krashan
[#2] Re: VBCC i przykre zaskoczenie

@Krashan, post #1

Interesujący test.

Z ciekawości zobaczyłem jak radzi sobie z tym przykładem kompilator, z którego korzystam najczęściej, czyli DICE.

Podaję wyniki dla DICE.

Lekko zmodyfikowałem Twoją procedurę testową. Różni się tym, że nie zlicza zera, kończącego ciąg znaków.

Powiem to bez ogródek. Twoja wersja po prostu produkowała kod leciutko gorszej jakości w DICE. Wykorzystywała dodatkowy rejestr.

ULONG StrSize(register __A0 UBYTE *s)
{
	UBYTE *p = s;

	while (*s)
		s++;
	return(s - p);
}

Oto rezultat po zapodaniu parametrów -s (żeby nie asemblował) oraz -d0 (żeby wyłączyć debugging).

DICE w tym przypadku tworzy niepotrzebnie pustą ramkę stosu oraz wykonuje niepotrzebny skok na końcu funkcji (szablonowe działanie kompilatora), ale sam kod pętli jest OK.

section text,code

	ds.l 0
	procstart
	ds.w	0
	xdef	@StrSize
@StrSize:
	movem.l	l7,-(sp)
	link	A5,#-l4
	move.l	A0,A1
	bra	l2
l1
	addq.l	#$01,A0
l2
	tst.b	(A0)
	bne	l1
l3
	move.l	A0,D0
	sub.l	A1,D0
	bra	l5
l5
	unlk	A5
	rts
l7	reg	
l6	equ	8
l4	equ	0
	procend
	END

Jak widać pętla jest prawie taka sama co Asm/GCC, ale DICE nie zastosował adresowania z postinkrementacją.

Na tym przykładzie widać, że DICE jest całkiem niezłym kompilatorem, skoro produkuje kod tej jakości.

Z ciekawostek to w dokumentacji do DCC jest napisane, że asembler DASM dodatkowo optymalizuje wynik kompilacji (czyli powyższy kod jest jeszcze optymalizowany przed zamianą na kod maszynowy).

Ostatnia aktualizacja: 26.09.2017 00:18:02 przez Hexmage960
[#3] Re: VBCC i przykre zaskoczenie

@Krashan, post #1

Ja sprawdziłem bez opcji pod VBCC (vc str.c -S) i wygląda to tak
_StrSize
	movem.l	l11,-(a7)
	move.l	(4+l13,a7),a1
	move.l	a1,a2
l8
l9
	move.l	a1,a0
	addq.l	#1,a1
	tst.b	(a0)
	bne	l8
l10
	move.l	a1,d0
	sub.l	a2,d0
l6
l11	reg	a2
	movem.l	(a7)+,a2
l13	equ	4
	rts

Co tu dużo mówić, szału ni ma.
[#4] Re: VBCC i przykre zaskoczenie

@asman, post #3

Hej!

Sprawdziłem jeszcze tą procedurę pod Maxon C++.
Wyszło niemal dokładnie jak GCC. Pętla jest z postinkrementacją.

No i Panowie, wychodzi, że nie tylko wielki GCC jest bardzo dobrym kompilatorem.

Taki kompilator jak MaxonC++ ma też rację bytu.

A MaxonC++ skompilował ten kod w kilka sekund na mojej Amidze.

; Maxon C++ Compiler
; Work:Test.c



	SECTION ":0",CODE


	XDEF	_StrSize
_StrSize
L3	EQU	4
L4	EQU	$400
	move.l	a2,-(a7)
	move.l	a0,a2
L1
L2
	tst.b	(a0)+
	bne.b	L2
	move.l	a0,d0
	sub.l	a2,d0
	move.l	(a7)+,a2
	rts

	XDEF	_main
_main
L5	EQU	0
L6	EQU	0
	moveq	#0,d0
	rts

	END
[#5] Re: VBCC i przykre zaskoczenie

@Hexmage960, post #4

Wyszło niemal dokładnie jak GCC.
No nie bardzo. Zauważ, że z jakiegoś powodu Maxon do przechowania początkowej wartości wskaźnika na string użył rejestru A2. Ponieważ funkcje mają obowiązek go przechować, to musiał go zrzucić na stos i potem przywrócić. Dwa dostępy do pamięci ekstra. Niby niewiele, ale przy intensywnym używaniu tej funkcji może już mieć znaczenie. Tym bardziej, że nie ma żadnego racjonalnego powodu, żeby nie użyć do przechowania wskaźnika rejestru A1, D0 czy D1.

A tak z innej beczki, dla mnie ma znaczenie kwestia własności intelektualnej przy używaniu kompilatora. Status prawny takich kompilatorów jak Storm, Maxon, SAS czy Dice jest dla mnie niejasny. Nie są one – z tego co mi wiadomo – formalnie uznane za abandonware. Z jednej strony to niby dzielenie włosa na czworo, z drugiej jednak, gdy używam kompilatora do tworzenia oprogramowania, które sprzedaję, nie może być żadnych wątpliwości. I również z tego powodu GCC uważam za najlepszy kompilator na klasyka (zresztą także na systemy NG...).
[#6] Re: VBCC i przykre zaskoczenie

@Krashan, post #5

Oczywiście mam oryginał MaxonC++. A DICE został uwolniony z kodem źródłowym.

GCC pewno jest najlepszy, ale chciałem wskazać, że nie jedyny podobnej klasy. Według mnie po prostu warto używać również MaxonC++.

Rzeczywiście powinien to wrzucić do zmiennej A1. Być może używa A1 w jakimś innym celu. Przyznasz jednak, że pętla jest najważniejsza bo wpływa najbardziej na koszt wykonania procedury.

Po dodaniu w parametrach register __A1 *p nie używa już A2.

; Maxon C++ Compiler
; Work:Test.c



	SECTION ":0",CODE


	XDEF	_StrSize
_StrSize
L3	EQU	0
L4	EQU	0
	move.l	a0,a1
L1
L2
	tst.b	(a0)+
	bne.b	L2
	move.l	a0,d0
	sub.l	a1,d0
	rts

	XDEF	_main
_main
L5	EQU	0
L6	EQU	0
	moveq	#0,d0
	rts

	END


Ostatnia aktualizacja: 27.09.2017 08:37:33 przez Hexmage960
[#7] Re: VBCC i przykre zaskoczenie

@Hexmage960, post #6

Tak dla formalności wklejam kod wygenerowany przez Maxon C++ z jednym argumentem przekazywanym przez A2.

MaxonC++ ustępuje GCC w przypadku tej procedury w kwestii "zgadywania", że argument funkcji posłuży do obliczenia wyniku, dzięki czemu jest on w rejestrze D0 zawczasu.

Zresztą myślę, że wykorzystanie czy to dwóch, czy trzech rejestrów w procedurze to sprawa drugorzędna.

Chodzi o sam efekt, czyli produkowanie wystarczająco efektywnego kodu w asemblerze. W tym przypadku chodzi o pętlę zliczającą znaki.

Maxon C++ produkuje w przypadku tej procedury kod podobnie efektywny.

Być może w kwestii zarządzania rejestrami wewnątrz funkcji działa odrobinę inaczej.

Tak, czy inaczej jak się wyjdzie poza "scratch registers" i tak trzeba użyć stosu.

Zresztą ciekawe jak by się spisywały te kompilatory w przypadku innych procedur.

Kod źródłowy:
#include <exec/types.h>

ULONG StrSize(register __a2 UBYTE *s)
{     
   const UBYTE *p;

   p = s;
   while (*s++)
      ;

   return(s - p);
}

main()
{
   return 0;
}


Kod skompilowany:
; Maxon C++ Compiler
; Work:Test.c



	SECTION ":0",CODE


	XDEF	_StrSize
_StrSize
L3	EQU	0
L4	EQU	0
	move.l	a2,a0
L1
L2
	tst.b	(a2)+
	bne.b	L2
	move.l	a2,d0
	sub.l	a0,d0
	rts

	XDEF	_main
_main
L5	EQU	0
L6	EQU	0
	moveq	#0,d0
	rts

	END


Ostatnia aktualizacja: 27.09.2017 14:44:29 przez Hexmage960
[#8] Re: VBCC i przykre zaskoczenie

@Hexmage960, post #7

panowie oczywiscie wiedza ze sa cross kompilatory cahira i bebba, ten ostatni to gcc.6.3 chyba juz podobno lepsze osiagi niz obecna wersja vbcc. natomiast nie moge tego potwierdzic na kodzie bo nie mam go obecnie zainstalowanego. moglbym najwyzej sprawdzic pod arosowym 6.3 ale wyniki moga byc i pewnie sa inne bo bebbo bardziej optymalizowal pod m68k a arosowy gcc jest chyba raczej generyczna wersja.
[#9] Re: VBCC i przykre zaskoczenie

@Krashan, post #1

Temat VBCC już kiedyś poruszyłem, zobacz sobie co trzeba zrobić z prostą funkcją kopiowania pamięci, aby zmieściła się w 3 instrukcjach, a nie w 9-ciu.

Próbowałem użyć GCC wyciągnietego z pakietu AmiDeVCpp do skompilowania jednego z programów zawierających taką pętlę, wyniki były zadowalające w porównaniu do VBCC jednak skompilowany program wymaga noixemul, po dodaniu parametru -noixemul nie kompilował mi się program, bo nie mógł znaleźć odniesienia do funkcji putc i już nie chciało mi się dochodzić, czemu tak jest.

Ostatnia aktualizacja: 27.09.2017 15:33:34 przez sanjyuubi
[#10] Re: VBCC i przykre zaskoczenie

@sanjyuubi, post #9

Podaję wyniki dla Maxona. Zmodyfikowałem procedurę w ten sposób, że kopiuje długie słowa (nie bajty) oraz, że jako parametr podaje się adres końca obszaru pamięci do skopiowania.

Zatem zrobiłem odmianę CopyMemQuick, która wymaga, by rozmiar obszaru był podzielny przez 4.

void MemCpy(register __a0 ULONG *s, register __a1 ULONG *d, register __a2 ULONG *e)
{
   while (s < e)
      *d++ = *s++;
}

Oto co wyprodukował kompilator:
XDEF	_MemCpy
_MemCpy
L5	EQU	0
L6	EQU	0
	bra.b	L2
L1
	move.l	(a0)+,(a1)+
L2
	cmp.l	a2,a0
	blt.b	L1
	rts
[#11] Re: VBCC i przykre zaskoczenie

@wawrzon, post #8

panowie oczywiscie wiedza ze sa cross kompilatory cahira i bebba, ten ostatni to gcc.6.3
Kroskompilacja mnie nie interesuje.
[#12] Re: VBCC i przykre zaskoczenie

@Krashan, post #11

yyy ale chyba nie stoi nic na przeszkodzie, by taki cross-compiler zbudować kompilatorem 68k i wtedy przestanie być crossem, nie? ;)
[#13] Re: VBCC i przykre zaskoczenie

@Krashan, post #11

a uzasadnisz dlaczego?
nie troluję, jestem ciekaw z racji małego stażu w kodowaniu na Ami...
TIA
[#14] Re: VBCC i przykre zaskoczenie

@Krashan, post #11

Kroskompilacja mnie nie interesuje.


ostatecznie mozna skompilowac prawie caly toolchain pod m68k (tez dziala, o ile sprawdzalem pod uae, przynajmniej gcc i helloworld albo cos w tym stylu zlozonosci). podobnie zreszta zrodla bebbo albo inne post 3.x.x. ale na prawdziwym 020 czy 030 i w odpowiednim odniesieniu do pamieci to moze byc przy najmniej bardzo zmudne i malo przekonywujace.

wiec pozostawmy to jako wskazowke na marginesie. sorki.
[#15] Re: VBCC i przykre zaskoczenie

@retronav, post #13

nie troluję, jestem ciekaw z racji małego stażu w kodowaniu na Ami...


nie chce sie wtracac, ale to chyba sprawa podejscia. wzgledy praktyczne chyba za bardzo nad tym nie przemawiaja, pzatym gdy kodowane i testowane jest srodowisko niskopoziomowe, sterowniki itp, ktore faktycznie moga bys niewystarczajaco emulowane, mialem z tym wielokrotnie doczynienia.
[#16] Re: VBCC i przykre zaskoczenie

@retronav, post #13

a uzasadnisz dlaczego?
Piszę aktualnie rzeczy będące blisko sprzętu i obsługujące sprzęt podłączony do prawdziwej Amigi. Nie są to duże programy, więc kompilują się w miarę szybko nawet na mojej 68020 @ 28 MHz, a nie muszę się bawić w przenoszenie skompliowanego pliku za każdym razem. A poza tym lubię klimat pracy na prawdziwej A1200.
[#17] Re: VBCC i przykre zaskoczenie

@sanjyuubi, post #9

Podaję wyniki dla Maxona i funkcji MemCmp().

Usprawniłem nieco procedurę, tak że rozmiar jest dekrementowany dopiero gdy wiadomo, że jest większy niż 0, poza tym wynik odejmowania jest przechowywany w zmiennej lokalnej diff, przez co nie trzeba liczyć tego samego dwa razy.

Pętla przerywana jest za pomocą break, dzięki czemu jest jeden, wspólny return.

Poza tym w programie załączyłem kod testujący w funkcji main().

GCC rzeczywiście jest oszczędniejszy w używaniu rejestrów, czyści rejestry przed odczytem danych o rozmiarze bajtu tylko raz, jak i stosuje instrukcję CMPM.

GCC jest zatem sprytniejszy, co jednak nie oznacza, że MaxonC jest jakiś słaby. szeroki uśmiech

Sam generowany kod przez Maxona jest bardzo dobry, a i nie trzeba jakoś specjalnie się gimnastykować z językiem C.

Dodatkowo jeśli dla kogoś szybkość kompilowania ma znaczenie, to tutaj Maxon spisuje się bardzo dobrze.

W przypadku tej procedury wrzucenie pamięci do rejestrów jest lepszym rozwiązaniem, bo funkcja ma zwrócić różnicę między tymi operandami.

#include <stdio.h>
#include <exec/types.h>

LONG MemCmp(UBYTE *s1, UBYTE *s2, ULONG size)
{
	LONG diff=0;

	while (size > 0) {
		size--;
		if (diff = *s1++ - *s2++)
			break;
	}
			
	return(diff);
}

main()
{
	static UBYTE tab[]={1, 2, 3, 4}, tab2[]={1, 2, 4, 3};
	LONG wynik;

	wynik = MemCmp(tab, tab2, sizeof(tab));
	printf("%ld\n", wynik);

	return 0;
}


Oto rezultat kompilacji funkcji MemCmp:
XDEF	_MemCmp
_MemCmp
L7	EQU	$14
L8	EQU	$4CC
	movem.l	d2/d3/d6/d7/a2,-(a7)
	move.l	L7+$C(a7),d3
	move.l	L7+$8(a7),a0
	move.l	L7+4(a7),a2
	moveq	#0,d2
	bra.b	L2
L1
	subq.l	#1,d3
	moveq	#0,d7
	move.b	(a2)+,d7
	moveq	#0,d6
	move.b	(a0)+,d6
	sub.l	d6,d7
	move.l	d7,d2
	bne.b	L4
L2
	cmp.l	#0,d3
	bhi.b	L1
L4
	move.l	d2,d0
	movem.l	(a7)+,d2/d3/d6/d7/a2
	rts


Ostatnia aktualizacja: 28.09.2017 06:47:57 przez Hexmage960
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