logo elektroda
logo elektroda
X
logo elektroda
Adblock/uBlockOrigin/AdGuard mogą powodować znikanie niektórych postów z powodu nowej reguły.

Funkcja "memcpy", a optymalizacja kodu

HIOB 04 Wrz 2008 16:28 2769 3
  • #1 5505728
    HIOB
    Poziom 17  
    Witam,

    Mam takie pytanie, czy któryś z kolegów zastanawiał się nad sensem stosowania funkcji "memcpy" do kopiowania tablic typu "char" w pamięci programu?

    Napisałem taki program:

    
    #include	<string.h>
    #include	<avr/io.h>
    
    #define		MAC0_CELU_ARP		0
    #define		MAC0_ZRODLA_ARP		50
    
    unsigned char bufor[100];
    
    
    int main(void)
    {
    	bufor[MAC0_CELU_ARP + 31] = bufor[MAC0_ZRODLA_ARP + 31];
    
    	//start
    	bufor[MAC0_CELU_ARP + 0] = bufor[MAC0_ZRODLA_ARP + 0];
    	bufor[MAC0_CELU_ARP + 1] = bufor[MAC0_ZRODLA_ARP + 1];
    	bufor[MAC0_CELU_ARP + 2] = bufor[MAC0_ZRODLA_ARP + 2];
    	bufor[MAC0_CELU_ARP + 3] = bufor[MAC0_ZRODLA_ARP + 3];
    	bufor[MAC0_CELU_ARP + 4] = bufor[MAC0_ZRODLA_ARP + 4];
    	bufor[MAC0_CELU_ARP + 5] = bufor[MAC0_ZRODLA_ARP + 5];
    	bufor[MAC0_CELU_ARP + 6] = bufor[MAC0_ZRODLA_ARP + 6];
    	bufor[MAC0_CELU_ARP + 7] = bufor[MAC0_ZRODLA_ARP + 7];
    	bufor[MAC0_CELU_ARP + 8] = bufor[MAC0_ZRODLA_ARP + 8];
    	bufor[MAC0_CELU_ARP + 9] = bufor[MAC0_ZRODLA_ARP + 9];
    	bufor[MAC0_CELU_ARP + 10] = bufor[MAC0_ZRODLA_ARP + 10];
    	bufor[MAC0_CELU_ARP + 11] = bufor[MAC0_ZRODLA_ARP + 11];
    	bufor[MAC0_CELU_ARP + 12] = bufor[MAC0_ZRODLA_ARP + 12];
    	bufor[MAC0_CELU_ARP + 13] = bufor[MAC0_ZRODLA_ARP + 13];
    	bufor[MAC0_CELU_ARP + 14] = bufor[MAC0_ZRODLA_ARP + 14];
    	bufor[MAC0_CELU_ARP + 15] = bufor[MAC0_ZRODLA_ARP + 15];
    	bufor[MAC0_CELU_ARP + 16] = bufor[MAC0_ZRODLA_ARP + 16];
    	bufor[MAC0_CELU_ARP + 17] = bufor[MAC0_ZRODLA_ARP + 17];
    	bufor[MAC0_CELU_ARP + 18] = bufor[MAC0_ZRODLA_ARP + 18];
    	bufor[MAC0_CELU_ARP + 19] = bufor[MAC0_ZRODLA_ARP + 19];
    	//stop
    
    	bufor[MAC0_CELU_ARP + 30] = bufor[MAC0_ZRODLA_ARP + 30];
    	
    	for(;;);
    	
    	return (0);
    }
    


    Fragment programu znajdujący się pomiędzy komentarzami "start - stop" wykonuje się w 80 taktach zegara.

    Zastępując teraz to "żmudne kopiowanie" funkcją "memcpy":

    
    #include	<string.h>
    #include	<avr/io.h>
    
    #define		MAC0_CELU_ARP		0
    #define		MAC0_ZRODLA_ARP		50
    
    unsigned char bufor[100];
    
    
    int main(void)
    {
    	bufor[MAC0_CELU_ARP + 31] = bufor[MAC0_ZRODLA_ARP + 31];
    
    	//start
    	memcpy(&bufor[MAC0_CELU_ARP], &bufor[MAC0_ZRODLA_ARP], 20);
    	//stop
    
    	bufor[MAC0_CELU_ARP + 30] = bufor[MAC0_ZRODLA_ARP + 30];
    	
    	for(;;);
    	
    	return (0);
    }
    


    ... zauważymy, że ten sam fragment kodu wykonuje się aż 145 taktów zegara - dwa razy dłużej!

    Oczywiście zmieniając typ tablicy na np. "int" otrzymujemy już porządany efekt:
    160 taktów dla "żmudnego kopiowania"
    144 taktów dla "memcpy"

    Zatem czy takie postępowanie jest zabiegiem czysto kosmetycznym (dla tablic typu char)? Można temu jakoś zaradzić?

    Pozdrawiam,
    Hiob.
  • Pomocny post
    #2 5505780
    BoskiDialer
    Poziom 34  
    Zmieniając typ na int w oryginalnym kodzie przenosisz 40 bajtów (20*2) na każdy po 4 cykle (2 wczytanie, 2 zapis) = 160 cykli. To jest minimalny czas, poniżej którego nie da się zejść. Jeśli memcpy działa szybciej, znaczy nie kopiuje wszystkich 40 bajtów (tylko 20 bajtów - jeśli nie zmieniasz trzeciego argumentu w wywołaniu funkcji), sam kod to wtedy 20 iteracji po 7 cykli (4 na przeniesienie bajtu, minimum 3 na kontrolę iteracji, co daje 140 cykli + (-1) + zarządzanie rejestrami etc)... Wykorzystując memcpy tracisz cenne cykle procesora przez to, że pojawia się kontrola iteracji, zyskujesz natomiast na rozmiarze kodu (zamiast 4 bajtów kodu programu na każdy bajt komórki pamięci masz około 4 instrukcji, czyli 8 bajtów + przygotowanie parametrów. Zawsze można napisać specjalizowaną funkcję do przenoszenia bloków pamięci, która będzie na jedną iterację przenosić np 8 bajtów, dzięki czemu narzuta na kontrolę iteracji jest mniejsza. Mogę podrzucić kod - asm(zoptymalizowany kod) + .h jeśli chcesz.
  • #3 5505821
    HIOB
    Poziom 17  
    rozumiem... czyli najoptymalniejszym rozwiązaniem dla tablic typu "char" jest "żmudne kopiowanie" (kosztem niewielkiej ilości pamięci programu).

    Co do pliku, to bardzo chętnie rzuce na niego okiem.
  • Pomocny post
    #4 5505857
    BoskiDialer
    Poziom 34  
    Plik .s (asm) + nagłówek do niego. Są tam 4 funkcje, które są do siebie bardzo podobne (w kodzie): move (do przenoszenia bloku pamięci, uwzględniony jest przypadek nachodzenia na siebie obszarów), clear (do czyszczenia), pioread i piowrite (przenoszenie pomiędzy blokiem a rejestrem pio) - wszystkie działają na blokach po 8 bajtów (co nie oznacza, że liczba bajtów musi być wielokrotnością 8 - bajty ponad wielokrotność też zostaną przeniesione). Funkcja move w trybie wstecznym (początkowy adres docelowy znajduje się wewnątrz bloku źródłowego) pracuje w blokach po 4 bajty.
    --- edycja
    w kodzie jest nieszkodliwy błąd spowodowany tym, że po zmianie nazw (pliki wyciągnięte z innego projektu) nie zmieniłem nazwy stałej do wkompilowywania warunkowego.
    Załączniki:
REKLAMA