Pytanie o optymalność kodu

0

Moje pytanie dotyczy wydajności zamieszczonej tu funkcji.
Kod napisałem tak z palca bez zastanowienia nad wydajnością,istotne jest aby był w całości w pascal'u.
Czy te funkcje są maksymalnie szybkie,czy da się jeszcze jakoś przyspieszyć ich działanie?
W pętli programu odwołania do nichi zachodzą setki tysięcy razy dlatego szybkość wykonywania jest tu bardzo istotna.

 
////////////////////////////////////////////////////////////////////////////////
//Funkcja pozostawia stan tylko tych bitow parametru Dana
//które w parametrze Maska sa zamaskowane binarnie jedynkami
//dodatkowo wszystkie uzyskane tak bity sa szeregowane kolejno do prawj.
function PozostawBity(Dana,Maska: byte):byte; overload;
var
i: integer;
begin
     Result:=0;
     for i:=0 to 7 do
          if Boolean(Maska and Byte(1 shl i)) then
               Result:=Result+(Dana and (1 shl i));
end;

function PozostawBity(Dana,Maska: word):word; overload;
var
i: integer;
begin
     Result:=0;
     for i:=0 to 7 do
          if Boolean(Maska and Word(1 shl i)) then
               Result:=Result+(Dana and (1 shl i));
end;

function PozostawBity(Dana,Maska: longword):longword; overload;
var
i: integer;
begin
     Result:=0;
     for i:=0 to 7 do
          if Boolean(Maska and LongWord(1 shl i)) then
               Result:=Result+(Dana and (1 shl i));
end;
////////////////////////////////////////////////////////////////////////////////
0

A czy te funkcje nie robią po prostu:

Result := Dane and Maska

?

1
Patryk27 napisał(a):

A czy te funkcje nie robią po prostu:

Result := Dane and Maska

?
Z tego co widać właśnie tak robią.

Nie rozumiem tylko tego:

tester_68k napisał(a):

dodatkowo wszystkie uzyskane tak bity sa szeregowane kolejno do prawj.
Przecież w podanym kodzie nie ma nic innego oprócz nakładania maski bitowej.

0

Optymalny kod podał Patryk27

0

Faktycznie nie działa zgodnie z tym co zakładałem zaraz poprawie

0

Teraz działa ale do wydajnej szybkości funkcjonowania chyba temu daleko.

(Sender as TButton).Caption:=IntToStr(PozostawBity($FF,128+1)); //Daje winik 3 bo z parametru Maska mamy dwa bity i są one ustawione w zmiennej "Dana" a potem poszeregowane kolejno do prawej.

////////////////////////////////////////////////////////////////////////////////
//Funkcja pozostawia stan tylko tych bitow parametru Dana
//które w parametrze Maska sa zamaskowane binarnie jedynkami
//dodatkowo wszystkie uzyskane tak bity sa szeregowane kolejno do prawj. 
function PozostawBity(Dana,Maska: byte):byte; overload;
var
i: integer;
begin
     Result:=0;
     for i:=7 downto 0 do
          if Boolean(Maska and Byte(1 shl i)) then
               if Boolean(Dana and Byte(1 shl i)) then
                    Result:=Result+(Result+1 shl 0)
               else
                    Result:=Result+(Result shl 0);
end;

function PozostawBity(Dana,Maska: word):word; overload;
var
i: integer;
begin
     Result:=0;
     for i:=7 downto 0 do
          if Boolean(Maska and Word(1 shl i)) then
               if Boolean(Dana and Word(1 shl i)) then
                    Result:=Result+(Result+1 shl 0)
               else
                    Result:=Result+(Result shl 0);
end;

function PozostawBity(Dana,Maska: longword):longword; overload;
var
i: integer;
begin
     Result:=0;
     for i:=7 downto 0 do
          if Boolean(Maska and LongWord(1 shl i)) then
               if Boolean(Dana and LongWord(1 shl i)) then
                    Result:=Result+(Result+1 shl 0)
               else
                    Result:=Result+(Result shl 0);
end;

////////////////////////////////////////////////////////////////////////////////

 
0

Zgodnie z tym opisem:

//Funkcja pozostawia stan tylko tych bitow parametru Dana
//które w parametrze Maska sa zamaskowane binarnie jedynkami
//dodatkowo wszystkie uzyskane tak bity sa szeregowane kolejno do prawj. 

Ma to być zwykłe and z przestawieniem bitów, tak?

Wyniki są takie:

Twoja funkcja przy `Dana=255` oraz `Maska=128+1`:
3 :: 00000011

Binarne `and`:
129 :: 10000001

Może opisz dokładniej co chcesz uzyskać jako wynik?

0

Teraz działa ale do wydajnej szybkości funkcjonowania chyba temu daleko.

Skoro według ciebie jest za wolno to pewnie po prostu szukasz spowolnienia nie tam gdzie trzeba.

(Sender as TButton).Caption:=IntToStr(PozostawBity($FF,128+1));

Po pierwsze to procedura powinna być PrzysunBity a and jeszcze na operandach powinieneś wykonać.
Zapewne więcej czasu albo przynajmniej porównywalną ilość zajmuje IntToStr.
Jak chcesz to pisz sobie tą procedurę jako wstawkę assemblerową, już szybciej się nie da, ale nie sądzę żeby to dało przyśpieszenie wielkie.

2

Jeszcze bardziej wydajna wersja, ale nie sądzę żeby to w tym leżała przyczyna spowolnienia:

function ZrobToCoChce(a,b:longword):longword; assembler;inline;
asm
  and a,b
  xor ecx,ecx
  @loop:
  cmp a,0
  je @end
  shr a,1
  jnc @loop
  sal ecx,1
  inc ecx
  jmp @loop
  @end:
  mov result,ecx
end;

Może pokaż więcej kodu to zlokalizujemy spowalniacz.

1

Wynik = (1 << pop_cnt(Dana ^ Maska)) - 1;
gdzie ^ to xor, a << to shl.

Wbudowany POP_CNT miał być już od pojawienia się AMD64, ale chyba coś nie wyszło: http://blogger.popcnt.org/2007/09/magic-popcount-popcnt-command.html

W każdym razie najprostszym sposobem na emulację pop_cnt jest chyba http://www-graphics.stanford.edu/~seander/bithacks.html#CountBitsSetKernighan population count Kernighana.

Zmodyfikowany kod, który korzysta z kodu Kernighana do przesunięcia bitów w prawo:

unsigned int in;
unsigned int out;
for (out = 0; in; out+=out+1)
{
  in &= in - 1; // clear the least significant bit set
}

Wynik w out

Ewentualnie jest jeszcze: http://www-graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel

0

Jutro zrobię testy wydajności i postaram się umieścić przykład.

Dzięki wszystkim za pomoc.

0

Przyznam ,że nie rozumiem działania pop_cnt() ma zliczać liczbę zapalonych bitów ale
tak jak opisałem gdy parametr "Maska" ma ustawiony bit to odpowiednik tego bitu obojętnie
czy w stanie "0"czy "1" musi pojawić się na wyniku w poszeregowanym ciągu. A ten kod raczej tego nie zapewnia:

(Sender as TButton).Caption:=IntToStr(1 shl (pop_cnt(($F0) xor (128+1)) - 1));

0

Dobra, żeby nie było wątpliwości cały kod:

unsigned int in = dana ^ maska;
unsigned int out;
for (out = 0; in; out+=out+1)
{
  in &= in - 1; // clear the least significant bit set
}

Hmm, w sumie przejrzałem ten twój drugi kod i on tam wcale nie xoruje nic, ani nie zbiera jedynek do kupy. W twoim kodzie maska służy do wycinania bitów. Zdecyduj się wreszcie o co ci chodzi.

Edit:
Spróbuję klepnąć z palca twoją ostatnią prośbę:

#include <stdio.h> 
 
int main(void) { 
  // dane
  unsigned int maska = 5;
  unsigned int dana = 6;
 
 
  // kod
  unsigned int a = 1;
  unsigned int wynik = 0;
  while (maska != 0) {
    unsigned int bit = maska & -maska;
    wynik += (bit & dana) == 0 ? 0 : a;
    a += a;
    maska &= maska - 1;
  }   
 
  printf("%u\n", wynik); 
  return 0; 
} 

http://ideone.com/06mq7

Zdaje się działać. W zasadzie będzie lepszy tylko jeśli maska będzie rzadka. Pojedyncza iteracja będzie wolniejsza (możliwe że i tak niewiele wolniejsza na nowych prockach) niż w twojej wersji. Za to iteracji będzie tylko tyle ile jest zapalonych bitów w masce, a w twojej wersji iteracji jest tyle ile wynosi szerokość słowa w bitach.

Jeśli nadal będzie za wolne to pobaw się przełącznikami kompilatora, może jak mu coś ciekawego podasz to wygeneruje szybszy kod.

0

Maska jest tylko do wycinania bitów a reszta kodu układa te powycinane bity kolejno, chodzi mi o to aby kod jaki napisałem działał znacznie szybciej od obecnej wersji.

0

Możesz jeszcze sprawdzić czy maska nie jest liczbą typu 2^N - 1, wtedy wyciąganie bitów jest banalnie proste i szybkie. Kod z dodatkiem:

#include <stdio.h> 
 
int main(void) { 
  // dane
  unsigned int maska = 5;
  unsigned int dana = 6;
 
 
  // kod
  unsigned int wynik = 0;
  if ((maska & (maska + 1)) == 0) {
    wynik = maska & dana;
  } else {
    unsigned int a = 1;
    while (maska != 0) {
      unsigned int bit = maska & -maska;
      wynik += (bit & dana) == 0 ? 0 : a;
      a += a;
      maska &= maska - 1;
    }   
  }
 
  printf("%u\n", wynik); 
  return 0; 
}

http://ideone.com/ryfTw

Oczywiście musisz sam sprawdzić czy dla twoich danych to da przyspieszenie, bo jeśli maski typu 2^N - 1 w twoim kodzie są rzadkie, to to da lekkie spowolnienie.

1

Maska jest tylko do wycinania bitów a reszta kodu układa te powycinane bity kolejno, chodzi mi o to aby kod jaki napisałem działał znacznie szybciej od obecnej wersji.

A czy patrzyłeś mój kod assemblerowy? Nie dość że się człowiek napracował to jeszcze go w dupie mają. Thx

0

kod miał być w całości w pascalu, przynajmniej tak autor podał w pierwszym poście

A co to przeszkadza wstawka assemblerowa? Można zrobić tablicę z opkodami, ustawić jej flagę wykonania i jest wszystko w pascalu, tylko po co kłamać że to pascal?
I tak btw. dlatemu dajesz kod w C? :P

0

No tak i kod będzie równie przenośny.

Z C już łatwo przepisać do Pascala, ja już dawno w Pascalach nie pisywałem, a myślę, że autor na tyle dobrze zna C żeby sobie sam mógł przepisać.

2

No tak i kod będzie równie przenośny.

A gdzie przeniesiesz kod z Delphi? Do Kylixa niby? on też x86. Jak się domyślam to autor nie pisze w FPC.

Kod bez asma (sprawdzany pod FPC 2.6.0 +Lazarus):

const
NieUzywamAsma:array[0..18] of byte=($31,$c9,
$83,$F8,0,$79,$9,$d1,$e8,$73,$f7,$d1,$e1,$41,$eb,$f2,$89,$c8,$c3);

type
  TNotAsm=procedure;register;nostackframe;
Function CallMeMaybe(a,b:longword):longword;register;
begin
  TNotAsm(pointer(@NieUzywamAsma[0]))();
end;

Z C już łatwo przepisać do Pascala, ja już dawno w Pascalach nie pisywałem, a myślę, że autor na tyle dobrze zna C żeby sobie sam mógł przepisać.

Nie wydaje mi się żeby twój kod dawał duży przyrost prędkości, zresztą, mój też nie mimo że jest mocno zoptymalizowany. Na 99% problem leży w innym spowolnieniu.

0
-123oho napisał(a):

Jeszcze bardziej wydajna wersja, ale nie sądzę żeby to w tym leżała przyczyna spowolnienia:

function ZrobToCoChce(a,b:longword):longword; assembler;inline;
asm
  and a,b
  xor ecx,ecx
  @loop:
  cmp a,0
  je @end
  shr a,1
  jnc @loop
  sal ecx,1
  inc ecx
  jmp @loop
  @end:
  mov result,ecx
end;

Może pokaż więcej kodu to zlokalizujemy spowalniacz.

Ten kod nie spełnia wymagań autora. Zamiast wycinać bity, przesuwa jedynki do prawej z iloczynu logicznego maski i danych.

Czemu zamiast:

jnc @loop
sal ecx,1
inc ecx

nie zrobisz po prostu:

jnc @loop
adc ecx,ecx

?

Poza tym twój kod wykona tyle kroków ile wynosi pozycja najwyższego bitu w (maska xor dane), a moja modyfikacja Kernighana wykonuje tyle iteracji ile wynosi liczba bitów w wyniku, czyli czasami różnica może być spora.

Nie dość, że lamer jesteś z asemblera to jeszcze niepotrzebnie się produkujesz, bo autor ma inne wymagania.

Przerobiłem ten kod asmowy na AT&T bo takiego oczekuje ideone: http://ideone.com/GArgY

1

Nie dość, że lamer jesteś z asemblera

Tak, bo nie chce mi się szukać bardziej optymalnych rozwiązań i tracę parę(naście) taktów maszynowych? No rzeczywiście, lamer ze mnie straszny. Chyba nie rozróżniasz wyrazów lamer od np. newbie albo non-pr0. Musisz być z siebie dumny że napisałeś lepszy kod i jedziesz po innych że są lamerami. Pogratulować.

to jeszcze niepotrzebnie się produkujesz, bo autor ma inne wymagania.

Ten kod nie spełnia wymagań autora. Zamiast wycinać bity, przesuwa jedynki do prawej z iloczynu logicznego maski i danych.

Tak zrozumiałem i tak napisałem.

Kierowałem się tym:

Daje winik 3 bo z parametru Maska mamy dwa bity i są one ustawione w zmiennej "Dana" a potem poszeregowane kolejno do prawej.

Wynik był poprawny więc stwierdziłem że jest dobrze.

Poza tym twój kod wykona tyle kroków ile wynosi pozycja najwyższego bitu w (maska xor dane), a moja modyfikacja Kernighana wykonuje tyle iteracji ile wynosi liczba bitów w wyniku, czyli czasami różnica może być spora.

No to zaoszczędziłeś paręnaście taktów maszynowych. Wybrałem algorytm oczywisty, nie chciało mi się szukać lepszych, bo rzeczywiście, dla współczesnych procesorów dużą różnicą będzie taki 32 powtórzeniowy loop, gdy inne części kodu są dużo gorszej jakości. Poprawiłeś wydajność kodu o 0%.

Czemu zamiast [...]nie zrobisz po prostu [...] ?

Bo nie chciało mi się myśleć nad bardziej optymalnymi rozwiązaniami. Domyślałem się że ktoś wymyśli coś lepszego, zazwyczaj są lepsze podejścia niż oczywiste.

0

Skoro nie zależy ci na wydajności to po co piszesz cokolwiek w asemblerze i jeszcze żalisz się że autor tego nie chce użyć mimo iż zastrzegł, że nie chce takich rozwiązań?

1

Skoro nie zależy ci na wydajności to po co piszesz cokolwiek w asemblerze

Zależy mi (ale to nie jest jedyne zastosowanie assemblera). Domyślałem się że są lepsze rozwiązania, ale to uznałem już za wystarczająco szybkie. Zresztą nie wierze że problem leży w wydajności tego kodu. I opublikowałem je po to bo może ktoś je jeszcze ulepszy.

żalisz się że autor tego nie chce użyć mimo iż zastrzegł, że nie chce takich rozwiązań

To niech autor poda powód dla którego nie chce takich rozwiązań bo moim zdaniem jest to bez sensu. Autorzy tematów to zazwyczaj osoby które same nie wiedzą czego chcą.

Jeszcze mały PS. do twojego kodu na ideone:

Przerobiłem ten kod asmowy na AT&T bo takiego oczekuje ideone: http://ideone.com/GArgY

{$ASMMODE INTEL}, lamer pozdrawia.

0

{$ASMMODE INTEL}

Brawo mistrzu FreePascala.

Akurat przepisanie kodu do asma wcale nie gwarantuje przyspieszenia, a może przynieść nawet spowolnienie. Zależy czy sprytniejszy jest programista asemblera czy kompilator.

Poza tym, gdyby autor chciał użyć lekko przerobionego algorytmu do innego celu, to nie znając asemblera nie mógłby skorzystać z kodu asemblerowego.

1

Akurat przepisanie kodu do asma wcale nie gwarantuje przyspieszenia, a może przynieść nawet spowolnienie.

Nie gwarantuje, ale nawet tak lamerski programista assemblera jak ja jest w stanie napisać porównywalny kod w asmie jak w 'zwykłym' kompilatorze. Zresztą, małe znaczenie ma przepisanie 4 liniowej procedury na asma, nawet tak pr0-haxorsko jak ty to zrobiłeś.
Dużo większe przyśpieszenie osiąga się lepszym algorytmem, który i tak można zakodzić w języku wysokiego poziomu i być szybszym od asma.

Zależy czy sprytniejszy jest programista asemblera czy kompilator.

Kompilator nie jest sprytny, kompilator jest głupi. Kompilator korzysta z rozwiązań przygotowanych przez innych programistów, którzy nie optymalizowali kodu dla twojego problemu. Dlatego nawet tak lamerski programista assemblera jak ja jest w stanie uzyskać bardzo porównywalny kod, który często będzie szybszy. Zresztą, walka o parę taktów maszynowych to głupota. Lepiej jest podawać kompilatorowi taki kod żeby był on w stanie wygenerować optymalny kod maszynowy, programista zaoszczędza czas przepisania na assembly i uzyskuje porównywalny kod maszynowy. Sztuką nie jest przepisanie wszystkiego na asma, ale konstruowanie szybkiego kodu wysokiego poziomu.

Brawo mistrzu FreePascala.

Nie mistrzu, raczej chodzi o to że każdy w czymś jest lepszy a w czymś gorszy, nie trzeba go od razu wyzywać od lamerów, bo jego kod nie jest najlepszy. IMO nikt nie wie wszystkiego, te forum jest żeby się uczyć od siebie i wspierać, nie żeby oczekiwać od każdego że wszystko umie i wszystko wie.

Poza tym, gdyby autor chciał użyć lekko przerobionego algorytmu do innego celu, to nie znając asemblera nie mógłby skorzystać z kodu asemblerowego.

To najwyższy czas nauczyć się assemblera, bo jednak przy debuggowaniu to podstawa, a często się w nim różne triki robi. Moim zdaniem nie znając assemblera nie jest się w stanie produkować szybkiego kodu wysokiego poziomu.

1

Kompilator nie jest sprytny, kompilator jest głupi. Kompilator korzysta z rozwiązań przygotowanych przez innych programistów, którzy nie optymalizowali kodu dla twojego problemu. Dlatego nawet tak lamerski programista assemblera jak ja jest w stanie uzyskać bardzo porównywalny kod, który często będzie szybszy. Zresztą, walka o parę taktów maszynowych to głupota. Lepiej jest podawać kompilatorowi taki kod żeby był on w stanie wygenerować optymalny kod maszynowy, programista zaoszczędza czas przepisania na assembly i uzyskuje porównywalny kod maszynowy. Sztuką nie jest przepisanie wszystkiego na asma, ale konstruowanie szybkiego kodu wysokiego poziomu.

I tu się mylisz. Przede wszystkim mózg programisty ma dość niewielką moc przerobową w porównaniu do CPU. CPU może wykonywać miliardy operacji na sekundę, więc może wypróbować sobie mnóstwo różnych optymalizacji i wybrać najlepszą. Może dokonać optymalizacji na dużą skalę, tzn nie tylko coś w rodzaju "peephole optimization" ale może optymalizować całe grafy wywołań funkcji. Kompilatory używają algorytmów kolorowania grafów dla optymalnego wykorzystania rejestrów procesora, itp itd

Jeśli dalej twierdzisz że optymalizacje przeprowadzane przez dobrej klasy kompilator będą zwykle słabsze od przeciętnego asemblerowca to odpal sobie jakiś algorytm rozwiązywania sudoku czy sztuczną inteligencję do gry w szachy i spróbuj się z tym zmierzyć. Powodzenia. Jeśli komputer będzie w stanie lepiej grać w szachy niż człowiek to czemu ma nie być w stanie lepiej optymalizować?

Moim zdaniem nie znając assemblera nie jest się w stanie produkować szybkiego kodu wysokiego poziomu.

Znam bardzo dobrze asemblera x86, zajmowałem się optymalizacją programów w asemblerze (część moich produkcji jest na asembler.republika.pl czy http://www.maximumcompression.com/TarsaLZP.zip ), disasemblowałem programy pisane w C++ czy Deplhi i coś tam wiem o optymalizacjach. Czytałem manuale Agnera Foga, łamałem crackmesy a nawet parę normalnych programów, itp itd Czytałem wywody o optymalizacji, ZTCP to były chyba wywody Paula Hsieha, w jednym z jego artykułów pokazywał, że kompilatory takie jak Intelowski kompilator C++ są generalnie trudne do pokonania.

Inny problem ze wstawkami asemblerowymi jest taki, że kompilatorowi może być ciężej optymalizować taki kod, przez co inlinig i wieloetapowe optymalizacje zadziałają gorzej.

0

I tu się mylisz. Przede wszystkim mózg programisty ma dość niewielką moc przerobową w porównaniu do CPU. CPU może wykonywać miliardy operacji na sekundę, więc może wypróbować sobie mnóstwo różnych optymalizacji i wybrać najlepszą.

Tja, tylko zajęło by to dużo czasu. Przeceniasz optymalizację, jakby robiła ona coś magicznego, tak że nie liczy się kod wejściowy.

Może dokonać optymalizacji na dużą skalę, tzn nie tylko coś w rodzaju "peephole optimization" ale może optymalizować całe grafy wywołań funkcji.

Chyba mówimy o wstawkach assembly? Zresztą, widziałem dużo kodu wyprodukowanego przez VS i uwierz mi, wcale magii nie ma.

Kompilatory używają algorytmów kolorowania grafów dla optymalnego wykorzystania rejestrów procesora, itp itd

daje to przyrost w przypadku dużych kodów, zgoda. Natomiast my mówimy o wstawkach w asmie które odpowiadają za kluczową funkcjonalność.

Jeśli dalej twierdzisz że optymalizacje przeprowadzane przez dobrej klasy kompilator będą zwykle słabsze od przeciętnego asemblerowca to odpal sobie jakiś algorytm rozwiązywania sudoku czy sztuczną inteligencję do gry w szachy i spróbuj się z tym zmierzyć. Powodzenia. Jeśli komputer będzie w stanie lepiej grać w szachy niż człowiek to czemu ma nie być w stanie lepiej optymalizować?

Sudoku i szachy to proste problemy matematyczne. Program komputerowy jest bardziej skompilowany bo kompilator nie wie co dany program ma robić. Nie zmieni on algorytmu na lepszy etc. Nie jest on w stanie również przewidzieć która część wyniku nas interesuje. Natomiast może przekształcać instrukcje na równoważne, ale szybsze.

Czytałem wywody o optymalizacji, ZTCP to były chyba wywody Paula Hsieha, w jednym z jego artykułów pokazywał, że kompilatory takie jak Intelowski kompilator C++ są generalnie trudne do pokonania.

Dobrze, dajmy kod jakiegoś newbie twoim błogosławionym kompilatorom, i porównajmy z moim albo twoim kodem który realizuje to samo. I?

Również czytałem dużo kodu assembly, głównie produkowanego przez FPC i VS, i o dziwo często dziwiłem się niepotrzebnym instrukcjom asma.

Inny problem ze wstawkami asemblerowymi jest taki, że kompilatorowi może być ciężej optymalizować taki kod, przez co inlinig i wieloetapowe optymalizacje zadziałają gorzej.

Bo kompilator nie wie co chcemy osiągnąć, a wszelkie udawanie że wie, wychodzi mu tylko na poziomie najbliższym maszynowemu, który składa się z ciągu operacji matematycznych etc.

Bardzo prosty test na optymalizację: Napisz sobie program, który zeruje tablicę dwuwymiarową, oczywiście zrób to podwójnym forem. Jeżeli napiszesz oczywisty for to o dziwo VS wyprodukuje kod który za pomocą jednej procedury wyczyści cały region pamięci. Dobry optymalizator. Teraz odwróć kolejność zmiennych z obu forów, VS już wyprodukuje kod normalnych forów. Taki dobry ten optymalizator?

Generalnie ja nie mówię że optymalizatory są tępe jak pień, ale wydaje mi się że przeceniasz ich możliwość. Na papierze wszystko wygląda super, to takie magiczne. Natomiast prawda jest taka że kod powinno się pisać pod kompilatory tak żeby ich optymalizatory mogły zrobić optymalny kod. Nie ma możliwości żeby z nieoptymalnego kodu źródłowego wyszedł optymalny kod maszynowy.
Optymalizator na pewno wykonuje wystarczającą robotę jeżeli chodzi o normalny kod, jeżeli kod jest dobry to kod maszynowy również będzie dobry. Natomiast nie zrobi on niczego co by wykraczało poza sferę skróć instrukcje maszynowe do prostszej/szybszej postaci. Gdy się pisze kod w asmie, znając ograniczenia można dostosować algorytm do procesora. Optymalizator nie jest tego świadomy, jeżeli ma 12 zmiennych to przecież nie wsadzi ich wszystkich do rejestrów bo zabraknie miejsca. Natomiast gdy będzie się pisać w asmie to można wykombinować taki algorytm który wpakuje wszystko do rejestrów.

0

Ani VS ani FPC nie są znane z szybkości kodu wykonywalnego. Za to Intel C++ czy nowe GCC już raczej tak.

Lata temu, kiedy procesory i kompilatory były proste, pisanie kodu w asmie dawało dużego kopa. Dzisiaj kompilatory mają mnóstwo heurystyk (nie twierdzę, że wszystkie popularne kompilatory, ale Intelowskie czy GCC raczej tak. Procesory są dzisiaj superskalarne, potrafią wykonać wiele instrukcji naraz, ale przewidzenie które instrukcje zostaną wykonane naraz jest trudne. Czasem opłaca się wklepać więcej instrukcji jeżeli dzięki temu przerwiemy łańcuch zależności i procesor będzie mógł wykonać wiele instrukcji naraz. Tak więc te niepotrzebne instrukcje być może były właśnie po to, aby superskalarne procesory mogły sobie wykonywać instrukcje całymi grupami naraz (choć wątpię, żeby VS czy FPC stosowały takie sztuczki; ale Intel C++ już jak najbardziej mógłby). Najkrótszy kod wcale nie musi być najszybszy. Po szczegóły możesz zajrzeć np do wspomnianych przeze mnie tutoriali Agnera Foga.

Dzisiaj do długotrwałych obliczeń nadal wykorzystuje się Fortrana. Jak wynika z mikrobenchmarków na http://shootout.alioth.debian.org/u64q/which-programming-languages-are-fastest.php Intelowski Fortran potrafi być zauważalnie bardziej wydajny niż C z GCC.

Asemblera wprost używa się za to przy optymalizacji pod instrukcje wektorowe, gdyż z tym kompilatory miewają jeszcze problemy. Kod asemblerowy raczej nie jest mniej czytelny, a może i jest nawet bardziej niż kod w C/C++/Fortran/cokolwiek wypchany intrinsicsami.

0

Witam
Byłem zajęty kilkoma innymi problemami …. Dziś wracam na forum i widzę, że w temacie optymizacji wybuchła prawdziwa burza. Opisując problem zaznaczyłem wyraźnie podstawowy aspekt: „istotne jest aby kod był w całości w pascal'u.” Dlaczego w Paskalu.. bo znalazłem prosty kompilator dający kod wynikowy pod procki 68HC11,Z80,8052,AT8515 to chyba wyjaśnia bezcelowość wstawki asemblerowej. Zwykle przy takim projekcie decyduje czas powstania i wydajność działania więc nie ma tu miejsca na dzieło sztuki programowania w assemblerze ani na interpreter BASIC’a ,który zwykle ma te funkcje w swoich zasobach. Napisałem tak jak potrafiłem wydajnościowo niezbyt ,ale zaobserwowałem ciekawe zjawisko w przypadku procka 68HC11 jego rozkazy asemblerowe mają najwięcej cykli zegarowych… przez co powinien wykonywać ten sam kod programu najwolniej od reszty i tak jest faktycznie ale tylko kiedy sprawdzam czas w pętli o wielkości 50 000 gdy rozmiar pętli wzrośnie do 500 000 wywołań układ 68HC11 wygrywa czasowo. Dysproporcje są szokujące dlatego zamieszczam je jako ciekawostkę dla zainteresowanych zagadnieniami optymizacji:
-pomiary czasu wykonałem analizatorem stanów logicznych. Punktem wyzwolenia była zmiana stanu logicznego portu I/O przed i po zakończeniu cyklu na tych samych danych bitowych . Taktowanie 16 Mhz

Mikrokontroler rozmiar pętli 50 000 rozmiar pętli 500 000
68HC11 33.248 ms 219.471 ms
89S52 26.792 ms 563.568 ms
8515 15.122 ms 207.369 ms
Szkoda ,że nie mam procka z mojego pierwszego komputera, wyniki dla staruszka Z80 pewnie dały by porównanie jak wielkie zmiany zaszły w optymizacji samego CPU ( o ile wytrzymał by 16 Mhz !!!!)

Pozdrawiam.

0

@tester_68k
I to są wyniki dla twojego oryginalnego kodu czy mojej propozycji?

1 użytkowników online, w tym zalogowanych: 0, gości: 1