Compare commits
9 Commits
8f4d0e843f
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| b9c81db777 | |||
| bcac1a48fa | |||
| 35853aa4ff | |||
| 3db92e93f4 | |||
| c1afe154bb | |||
| 9a28acb0c9 | |||
| f2242fdb42 | |||
| 6fee8ccd48 | |||
| 0ba84bef75 |
@@ -1,12 +1,14 @@
|
||||
# AK2 - lab 2019+
|
||||
Trochę różnych programów napisanych przy pomocy z rozwiązywaniem zadań od mgr Aleksandry Postawki, mgra Tomasza Serafina, dra Tadeusza Tomczaka (i innych) na zajęciach laboratoryjnych z Architektury komputerów 2, w formie materiałów dydaktycznych – kod szczegółowo opisany w komentarzach.
|
||||
Trochę różnych programów napisanych przy rozwiązywaniu zadań od mgr Aleksandry Postawki, mgra Tomasza Serafina, prof. Tadeusza Tomczaka (i innych) na zajęciach laboratoryjnych z Architektury komputerów 2, Wprowadzenia do wysokowydajnych komputerów oraz Organizacji i architektury komputerów, w formie materiałów dydaktycznych – kod szczegółowo opisany w komentarzach.
|
||||
Repozytorium powstało w 2019 i będzie na bieżąco aktualizowane.
|
||||
|
||||
Przydatne linki:
|
||||
* Intel 64 and IA-32 Architectures Software Developer’s Manual (Combined Volumes) – opis architektury procesorów x86 i spis wszystkich rozkazów asemblera:
|
||||
https://software.intel.com/sites/default/files/managed/39/c5/325462-sdm-vol-1-2abcd-3abcd.pdf
|
||||
https://www.intel.com/content/www/us/en/content-details/671200/intel-64-and-ia-32-architectures-software-developer-s-manual-combined-volumes-1-2a-2b-2c-2d-3a-3b-3c-3d-and-4.html
|
||||
* Linux System Call Table – spis funkcji systemowych Linuxa dla 32- i 64-bitowej wersji x86 w przejrzystej formie:
|
||||
https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md
|
||||
https://www.chromium.org/chromium-os/developer-library/reference/linux-constants/syscalls/
|
||||
* Programming from the Ground Up:
|
||||
https://download-mirror.savannah.gnu.org/releases/pgubook/ProgrammingGroundUp-1-0-booksize.pdf
|
||||
* System V Application Binary Interface AMD64 – opis 64-bitowej konwencji wywołań x86 używanej w systemach z rodziny Linux:
|
||||
https://www.uclibc.org/docs/psABI-x86_64.pdf
|
||||
* System V Application Binary Interface i386 – opis 32-bitowej konwencji wywołań x86 używanej w systemach z rodziny Linux:
|
||||
@@ -23,3 +25,5 @@ https://en.cppreference.com/w/
|
||||
https://en.cppreference.com/w/cpp/io/c/fprintf
|
||||
* Opis funkcji scanf i jej ciągów formatujących:
|
||||
https://en.cppreference.com/w/cpp/io/c/fscanf
|
||||
* Using the GNU Compiler Collection (GCC) – Extended Asm – składnia wstawek asemblerowych w C:
|
||||
https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html
|
||||
@@ -0,0 +1,22 @@
|
||||
all: fibb_32 fibb_64 fibb_32_inline fibb_asm fibb_bin second
|
||||
|
||||
fibb_32: fibb_c.c fibb32.s timestamp32.s
|
||||
gcc -m32 fibb32.s timestamp32.s fibb_c.c -o fibb_32
|
||||
|
||||
fibb_64: fibb_c.c fibb64.s timestamp64.s
|
||||
gcc fibb64.s timestamp64.s fibb_c.c -o fibb_64
|
||||
|
||||
fibb_32_inline: fibb_inline.c fibb32.s
|
||||
gcc -m32 fibb32.s fibb_inline.c -o fibb_32_inline
|
||||
|
||||
fibb_asm: fibb_asm.s fibb64.s
|
||||
gcc fibb64.s fibb_asm.s -o fibb_asm
|
||||
|
||||
fibb_bin: fibb_bin.s fibb64.s
|
||||
gcc -no-pie fibb64.s fibb_bin.s -o fibb_bin
|
||||
|
||||
second: second.c timestamp32.s
|
||||
gcc -m32 timestamp32.s second.c -o second
|
||||
|
||||
clean:
|
||||
rm fibb_32 fibb_64 fibb_32_inline fibb_asm fibb_bin second
|
||||
@@ -0,0 +1,43 @@
|
||||
# Jan Potocki 2020
|
||||
|
||||
.global fibb
|
||||
|
||||
# Segment kodu
|
||||
.text
|
||||
|
||||
fibb:
|
||||
push %ebp
|
||||
mov %esp, %ebp
|
||||
push %ebx
|
||||
|
||||
cmpl $0, 8(%ebp)
|
||||
je zero
|
||||
|
||||
cmpl $1, 8(%ebp)
|
||||
je jeden
|
||||
|
||||
movl 8(%ebp), %edx
|
||||
dec %edx
|
||||
push %edx
|
||||
call fibb
|
||||
pop %edx
|
||||
mov %eax, %ebx
|
||||
|
||||
dec %edx
|
||||
push %edx
|
||||
call fibb
|
||||
add $4, %esp
|
||||
add %ebx, %eax
|
||||
jmp wyjscie
|
||||
|
||||
zero:
|
||||
mov $0, %eax
|
||||
jmp wyjscie
|
||||
|
||||
jeden:
|
||||
mov $1, %eax
|
||||
|
||||
wyjscie:
|
||||
pop %ebx
|
||||
pop %ebp
|
||||
ret
|
||||
@@ -0,0 +1,40 @@
|
||||
# Jan Potocki 2020
|
||||
|
||||
.global fibb
|
||||
|
||||
# Segment kodu
|
||||
.text
|
||||
|
||||
fibb:
|
||||
push %rbp
|
||||
mov %rsp, %rbp
|
||||
push %rbx
|
||||
|
||||
cmp $0, %rdi
|
||||
je zero
|
||||
|
||||
cmp $1, %rdi
|
||||
je jeden
|
||||
|
||||
dec %rdi
|
||||
push %rdi
|
||||
call fibb
|
||||
pop %rdi
|
||||
mov %rax, %rbx
|
||||
|
||||
dec %rdi
|
||||
call fibb
|
||||
add %rbx, %rax
|
||||
jmp wyjscie
|
||||
|
||||
zero:
|
||||
mov $0, %rax
|
||||
jmp wyjscie
|
||||
|
||||
jeden:
|
||||
mov $1, %rax
|
||||
|
||||
wyjscie:
|
||||
pop %rbx
|
||||
pop %rbp
|
||||
ret
|
||||
@@ -0,0 +1,50 @@
|
||||
# Jan Potocki 2023
|
||||
# Wersja przystosowana do zbudowania jako PIE (Position Independent Executable)
|
||||
|
||||
# Definicje numerow funkcji systemowych i ich parametrow
|
||||
SYSEXIT64 = 60
|
||||
|
||||
# Stale okreslajace rozmiar przetwarzanych danych
|
||||
word_length = 8
|
||||
|
||||
.global main
|
||||
|
||||
# Segment niezainicjalizowanych danych
|
||||
.bss
|
||||
element: .space word_length
|
||||
|
||||
# Segment zainicjalizowanych danych
|
||||
.data
|
||||
format_s: .asciz "%lu"
|
||||
format_p: .asciz "%lu\n"
|
||||
|
||||
# Segment kodu
|
||||
.text
|
||||
|
||||
main:
|
||||
# To jest potrzebne zeby printf i scanf nie konczyly sie segfaultem
|
||||
push %rbp
|
||||
mov %rsp, %rbp
|
||||
|
||||
lea format_s(%rip), %rdi # Adresowanie wzgledem rip, wprowadzone w x86-64
|
||||
#mov $format_s, %rdi # ...tu nam wystarczy sam adres
|
||||
lea element(%rip), %rsi
|
||||
#mov $element, %rsi
|
||||
mov $0, %rax # Nie ma argumentow dla scanf() w rejestrach xmm
|
||||
call scanf
|
||||
|
||||
|
||||
lea element(%rip), %r8 # Tutaj trzeba jakis posredni rejestr...
|
||||
movq (%r8), %rdi # ...zeby odwolac sie do wartosci
|
||||
#movq element, %rdi
|
||||
call fibb
|
||||
|
||||
lea format_p(%rip), %rdi
|
||||
#mov $format_p, %rdi
|
||||
mov %rax, %rsi # rax - wartosc zwrocona przez fibb()
|
||||
mov $0, %rax # Nie ma argumentow dla printf() w rejestrach xmm
|
||||
call printf
|
||||
|
||||
mov $SYSEXIT64, %rax
|
||||
mov $0, %rdi
|
||||
syscall
|
||||
@@ -0,0 +1,47 @@
|
||||
# Jan Potocki 2020
|
||||
# Przyklad wywolania:
|
||||
# echo 08 | xxd -p -r | ./fibb_asm | hexdump
|
||||
|
||||
# Definicje numerow funkcji systemowych i ich parametrow
|
||||
SYSEXIT64 = 60
|
||||
SYSREAD = 0
|
||||
SYSWRITE = 1
|
||||
STDIN = 0
|
||||
STDOUT = 1
|
||||
|
||||
# Stale okreslajace rozmiar przetwarzanych danych
|
||||
word_length = 8
|
||||
|
||||
.global main
|
||||
|
||||
# Segment niezainicjalizowanych danych
|
||||
.bss
|
||||
result: .space word_length
|
||||
|
||||
# Segment zainicjalizowanych danych
|
||||
.data
|
||||
term: .zero word_length
|
||||
|
||||
# Segment kodu
|
||||
.text
|
||||
|
||||
main:
|
||||
mov $SYSREAD, %rax
|
||||
mov $STDIN, %rdi
|
||||
mov $term, %rsi
|
||||
mov $word_length, %rdx
|
||||
syscall
|
||||
|
||||
movq term, %rdi
|
||||
call fibb
|
||||
movq %rax, result
|
||||
|
||||
mov $SYSWRITE, %rax
|
||||
mov $STDOUT, %rdi
|
||||
mov $result, %rsi
|
||||
mov $word_length, %rdx
|
||||
syscall
|
||||
|
||||
mov $SYSEXIT64, %rax
|
||||
mov $0, %rdi
|
||||
syscall
|
||||
@@ -0,0 +1,23 @@
|
||||
#include <stdio.h>
|
||||
|
||||
// Jan Potocki 2020
|
||||
|
||||
unsigned long fibb(unsigned long n);
|
||||
unsigned long long timestamp();
|
||||
|
||||
int main()
|
||||
{
|
||||
unsigned long term, result;
|
||||
unsigned long long tstamp1, tstamp2;
|
||||
|
||||
scanf("%lu", &term);
|
||||
|
||||
tstamp1 = timestamp();
|
||||
result = fibb(term);
|
||||
tstamp2 = timestamp();
|
||||
|
||||
printf("Result: %lu\n", result);
|
||||
printf("Cycles: %llu\n", tstamp2-tstamp1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#include <stdio.h>
|
||||
|
||||
// Jan Potocki 2020
|
||||
|
||||
unsigned long fibb(unsigned long n);
|
||||
|
||||
unsigned long long timestamp()
|
||||
{
|
||||
unsigned long long tsc;
|
||||
|
||||
asm volatile(
|
||||
"xor %%eax, %%eax\n"
|
||||
"cpuid\n"
|
||||
"rdtsc"
|
||||
: "=A" (tsc)
|
||||
:
|
||||
: "ebx", "ecx"
|
||||
);
|
||||
|
||||
return tsc;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
unsigned long term, result;
|
||||
unsigned long long tstamp1, tstamp2;
|
||||
|
||||
scanf("%lu", &term);
|
||||
|
||||
tstamp1 = timestamp();
|
||||
result = fibb(term);
|
||||
tstamp2 = timestamp();
|
||||
|
||||
printf("Result: %lu\n", result);
|
||||
printf("Cycles: %llu\n", tstamp2-tstamp1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// Jan Potocki 2020
|
||||
|
||||
unsigned long long timestamp();
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
unsigned long long tstamp1, tstamp2;
|
||||
|
||||
tstamp1 = timestamp();
|
||||
sleep(1);
|
||||
tstamp2 = timestamp();
|
||||
|
||||
printf("Cycles: %llu\n", tstamp2 - tstamp1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
# Jan Potocki 2020
|
||||
|
||||
.global timestamp
|
||||
|
||||
# Segment kodu
|
||||
.text
|
||||
|
||||
timestamp:
|
||||
push %ebx
|
||||
|
||||
xor %eax, %eax
|
||||
cpuid
|
||||
rdtsc
|
||||
|
||||
pop %ebx
|
||||
ret
|
||||
@@ -0,0 +1,19 @@
|
||||
# Jan Potocki 2020
|
||||
|
||||
.global timestamp
|
||||
|
||||
# Segment kodu
|
||||
.text
|
||||
|
||||
timestamp:
|
||||
push %rbx
|
||||
|
||||
xor %rax, %rax
|
||||
cpuid
|
||||
rdtsc
|
||||
|
||||
shl $32, %rdx
|
||||
or %rdx, %rax
|
||||
|
||||
pop %rbx
|
||||
ret
|
||||
@@ -0,0 +1,7 @@
|
||||
all: mul
|
||||
|
||||
mul: mul.s
|
||||
gcc mul.s -o mul
|
||||
|
||||
clean:
|
||||
rm mul
|
||||
@@ -0,0 +1,17 @@
|
||||
Należy napisać program, który:
|
||||
* Ze standardowego strumienia wejściowego odczyta (za pomocą funkcji systemowej _read_) bloki zawierające 512 bajtów każdy. Liczba bloków może być dowolna. Kombinacje bitów w bajtach mogą być dowolne.
|
||||
* Każdy blok potraktowany zostanie jako zapisane po sobie dwie reprezentacje w kodzie naturalnym binarnym. Pierwszy odczytany bajt w bloku zawiera najmniej znaczący bit reprezentacji. Każda z reprezentacji zajmuje ciągłą połowę odczytanego bloku, czyli układ danych w bloku można opisać jako:
|
||||
`A0 A1 A2 ... A255 B0 B1 B2 ... B255`
|
||||
gdzie `Ai` i `Bi` oznaczają kolejne bajty (o indeksach _i_) reprezentacji pierwszej (_A_) i drugiej (_B_).
|
||||
* Po odczytaniu bloku danych, na standardowe wyjście zostanie wypisana reprezentacja iloczynu liczb zapisanych reprezentacjami A i B. Format reprezentacji wyjściowej będzie taki sam, jak wejściowych, ale o odpowiednio dopasowanej wielkości.
|
||||
|
||||
|
||||
Przykładowe dane wejściowe i oczekiwane wyniki są dostępne pod adresem http://zak.iiar.pwr.wroc.pl/materials/architektura/laboratorium%20AK2/Dane/mul.tar.bz2
|
||||
Pliki z rozszerzeniem `.in` to dane wejściowe, pliki `*.out` to oczekiwane odpowiedzi.
|
||||
Docelowo program ma działać dla danych z plików `mul_256.*`, ale dla ułatwienia Państwu pracy na początku realizacji zadania przygotowałem także pliki z reprezentacjami 4-bajtowymi (pliki `mul_4.*`).
|
||||
|
||||
Podczas implementacji największe problemy może Państwu sprawić poprawna realizacja algorytmu mnożenia.
|
||||
Przykładowe koncepcje należy sobie przypomnieć z materiałów z poprzedniego semestru, np. slajdy 38 i 39 pliku http://zak.iiar.pwr.wroc.pl/materials/Arytmetyka%20komputerow/architekura.pdf i podstawy z pliku http://zak.iiar.pwr.wroc.pl/materials/Arytmetyka%20komputerow/mnozenie.pdf
|
||||
Oczywiście zadanie to można zrealizować na wiele sposobów, zmieniając zarówno rozmiar pojedynczych iloczynów częściowych, jak i kolejność ich kumulacji.
|
||||
Można np. kumulację iloczynów częściowych przeprowadzać "wierszami", dodając poszczególne iloczyny częściowe "w poziomie", można także kumulować "kolumnami" sumując iloczyny "w pionie". Można także stosować różne rozwiązania pośrednie.
|
||||
Niezależnie jednak od przyjętego sposobu, koniecznie proszę pamiętać o właściwej propagacji przeniesień.
|
||||
@@ -0,0 +1,149 @@
|
||||
# Jan Potocki 2020
|
||||
|
||||
# Definicje numerow funkcji systemowych i ich parametrow
|
||||
SYSEXIT64 = 60
|
||||
SYSREAD = 0
|
||||
SYSWRITE = 1
|
||||
STDIN = 0
|
||||
STDOUT = 1
|
||||
|
||||
# Stale okreslajace rozmiar przetwarzanych danych
|
||||
num_length = 256
|
||||
# ...tego nie ruszac - inaczej stanie sie "cud nad klawiatura" :-D
|
||||
word_length = 8
|
||||
buf_length = num_length * 2
|
||||
num_words = num_length / word_length
|
||||
buf_words = buf_length / word_length
|
||||
|
||||
.global main
|
||||
|
||||
# Segment niezainicjalizowanych danych
|
||||
.bss
|
||||
|
||||
liczba1: .space num_length
|
||||
liczba2: .space num_length
|
||||
wynik: .space buf_length
|
||||
|
||||
# Segment kodu
|
||||
.text
|
||||
|
||||
main:
|
||||
# Glowna petla
|
||||
petla:
|
||||
mov $SYSREAD, %rax # Wczytanie danych ze standardowego wejscia
|
||||
mov $STDIN, %rdi # ...funkcja systemowa read
|
||||
mov $liczba1, %rsi
|
||||
mov $buf_length, %rdx
|
||||
syscall
|
||||
|
||||
# UWAGA
|
||||
# Tutaj wczytane zostana od razu obie liczby
|
||||
# liczba1 i liczba2 sa w pamieci bezposrednio po sobie...
|
||||
# ...i zostala przekazana do funkcji read dlugosc calego bloku
|
||||
|
||||
cmp $buf_length, %rax # rax - liczba wczytanych bajtow
|
||||
jl koniec # Jezeli nie ma 512, to dane sie skonczyly
|
||||
|
||||
xor %rsi, %rsi # rsi - licznik petli zerujacej
|
||||
|
||||
# Petla zerujaca bufor na wynik
|
||||
wyzeruj:
|
||||
movq $0, wynik(, %rsi, 8)
|
||||
inc %rsi
|
||||
cmp $buf_words, %rsi
|
||||
jl wyzeruj
|
||||
|
||||
xor %rsi, %rsi # rsi - licznik pierwszej petli
|
||||
|
||||
# Mnozenie - petla zewnetrzna
|
||||
petla1:
|
||||
xor %rcx, %rcx # rcx - miejsce na starsza czesc wyniku
|
||||
|
||||
# Flagi przeniesienia (potrzebne sa dwie i tego sie nie uprosci)
|
||||
mov $0, %r8 # r8 - dla koncowego wyniku (w pamieci)
|
||||
clc # RFLAGS - dla biezacego mnozenia (rejestry)
|
||||
pushf # ...oczywiscie z backupem na stosie
|
||||
|
||||
xor %rdi, %rdi # rdi - licznik drugiej petli
|
||||
|
||||
# Mnozenie - petla wewnetrzna
|
||||
petla2:
|
||||
movq liczba1(, %rsi, 8), %rax
|
||||
mulq liczba2(, %rdi, 8)
|
||||
|
||||
mov %rdi, %r9 # r9 - indeks wyniku
|
||||
add %rsi, %r9 # (suma indeksow obu petli)
|
||||
|
||||
# Dodanie starszej czesci wyniku z poprzedniej iteracji
|
||||
popf # Tutaj flagi mamy na stosie...
|
||||
adc %rcx, %rax
|
||||
pushf # ...wiec to jest proste
|
||||
|
||||
# Przygotowanie flagi przeniesienia do sumowania wyniku (patent cz. I)
|
||||
cmp $1, %r8 # Tego nie mamy na stosie...
|
||||
je ustaw_cf1 # ...wiec musimy sprytnym sposobem ;-)
|
||||
|
||||
clc # Przypadek bez przeniesienia
|
||||
jmp dodaj_wynik
|
||||
|
||||
ustaw_cf1:
|
||||
stc # Przypadek z przeniesieniem
|
||||
|
||||
# Dodawanie biezacego wyniku do pamieci
|
||||
# (tam po wszystkich iteracjach bedzie wynik koncowy)
|
||||
dodaj_wynik:
|
||||
adcq %rax, wynik(, %r9, 8)
|
||||
|
||||
# Zapisanie flagi przeniesienia do sumowania wyniku (patent cz. II)
|
||||
jc zapisz_cf
|
||||
|
||||
mov $0, %r8 # Jezeli przeniesienia nie bylo
|
||||
jmp dalej
|
||||
|
||||
zapisz_cf:
|
||||
mov $1, %r8 # Jezeli przeniesienie bylo
|
||||
|
||||
dalej:
|
||||
mov %rdx, %rcx # Przechowanie starszej czesci wyniku w rcx
|
||||
|
||||
inc %rdi
|
||||
cmp $num_words, %rdi
|
||||
jl petla2 # Koniec wewnetrznej petli
|
||||
|
||||
# Tutaj musimy sie jeszcze zajac najstarsza czescia wyniku
|
||||
# (z ostatniej iteracji petli wewnetrznej)
|
||||
inc %r9
|
||||
|
||||
popf # Dodanie przeniesienia z sumowania rejestrow
|
||||
adc $0, %rcx # ...jezeli jakies zostalo
|
||||
|
||||
# Przygotowanie flagi przeniesienia do sumowania wyniku (patent cz. I)
|
||||
cmp $1, %r8
|
||||
je ustaw_cf2
|
||||
|
||||
clc # Przypadek bez przeniesienia
|
||||
jmp dodaj_najstarsze
|
||||
|
||||
ustaw_cf2:
|
||||
stc # Przypadek z przeniesieniem
|
||||
|
||||
dodaj_najstarsze:
|
||||
adcq %rcx, wynik(, %r9, 8)
|
||||
|
||||
inc %rsi
|
||||
cmp $num_words, %rsi
|
||||
jl petla1 # Koniec zewnetrznej petli
|
||||
|
||||
mov $SYSWRITE, %rax # Wypisanie wyniku na standardowe wyjscie
|
||||
mov $STDOUT, %rdi # ...funkcja systemowa write
|
||||
mov $wynik, %rsi
|
||||
mov $buf_length, %rdx
|
||||
syscall
|
||||
|
||||
jmp petla # Koniec glownej petli
|
||||
|
||||
# Wyjscie z programu
|
||||
koniec:
|
||||
mov $SYSEXIT64, %rax # Funkcja systemowa exit...
|
||||
mov $0, %rdi # ...kod zakonczenia - 0
|
||||
syscall
|
||||
@@ -0,0 +1,10 @@
|
||||
all: echo
|
||||
|
||||
echo: echo.o
|
||||
ld -melf_i386 echo.o -o echo
|
||||
|
||||
echo.o: echo.s
|
||||
as --32 echo.s -o echo.o
|
||||
|
||||
clean:
|
||||
rm echo echo.o
|
||||
@@ -0,0 +1,43 @@
|
||||
# Jan Potocki 2022
|
||||
|
||||
# Definicje numerow funkcji systemowych i ich argumentow
|
||||
SYSEXIT = 1
|
||||
SYSREAD = 3
|
||||
SYSWRITE = 4
|
||||
STDIN = 0
|
||||
STDOUT = 1
|
||||
SYSCALL32 = 0x80
|
||||
|
||||
# Stale
|
||||
buff_len = 1
|
||||
end_char = 'Q'
|
||||
|
||||
.global _start
|
||||
|
||||
.bss
|
||||
# Bufor na czytany znak
|
||||
buff: .space buff_len
|
||||
|
||||
.text
|
||||
_start:
|
||||
mov $SYSREAD, %eax # Wczytanie znaku ze standardowego wejscia
|
||||
mov $STDIN, %ebx # ...funkcja systemowa read
|
||||
mov $buff, %ecx
|
||||
mov $buff_len, %edx
|
||||
int $SYSCALL32
|
||||
|
||||
mov $end_char, %eax # Sprawdzenie, czy wczytano znak konczacy
|
||||
cmp %eax, buff
|
||||
je koniec # ...jezeli tak, skok na koniec
|
||||
|
||||
mov $SYSWRITE, %eax # Wypisanie znaku na standardowe wyjscie
|
||||
mov $STDOUT, %ebx # ...funkcja systemowa write
|
||||
mov $buff, %ecx
|
||||
mov $buff_len, %edx
|
||||
int $SYSCALL32
|
||||
jmp _start # Skok na poczatek (petla)
|
||||
|
||||
koniec:
|
||||
mov $SYSEXIT, %eax # Funkcja systemowa exit...
|
||||
mov $0, %ebx # ...kod zakonczenia 0
|
||||
int $SYSCALL32
|
||||
@@ -0,0 +1,7 @@
|
||||
all: png
|
||||
|
||||
png: png.c filter.s timestamp64.s
|
||||
gcc -g -lpng -O3 -no-pie png.c filter.s timestamp64.s -o png
|
||||
|
||||
clean:
|
||||
rm png
|
||||
@@ -0,0 +1,6 @@
|
||||
### UWAGA 2026
|
||||
Mój kod napisany w 2020 jest raczej wariacją na temat zadania prof. Tomczaka, którego pełny opis zamieściłem w pliku `Zadanie.md`, niż jego dokładną implementacją. Stąd wykorzystanie jednostki SSE, a nie MMX. Implementację na rozkazach MMX dla 32-bitowej odmiany x86 możecie zobaczyć w tym repozytorium (aczkolwiek pozostawia ona jeszcze pole do większej równoległości):
|
||||
https://github.com/azdrojowa123/AssemblyImageProcessing
|
||||
Obydwa projekty mają pozwolić zrozumieć ideę posługiwania się operacjami SIMD, nie polecam jednak ich bezkrytycznie kopiować.
|
||||
|
||||
Przy testach na komputerze z procesorem AMD Ryzen 5 2600X i 16 GB pamięci DDR4 3000 MHz osiągnąłem tempo około 2.5 cykli zegara na piksel.
|
||||
@@ -0,0 +1,210 @@
|
||||
Celem bieżącego ćwiczenia jest zapoznanie się z jednostką wektorową (MMX) połączone z praktycznym wykorzystaniem wiedzy z zakresu podstaw arytmetyki stałoprzecinkowej.
|
||||
Ćwiczenie należy zrealizować do dnia 4 czerwca 2020 r.
|
||||
|
||||
Opis jednostki MMX jest dostępny w dokumentacji procesora (Basic Architecture) rozdz. 9.
|
||||
Szczególną uwagę proszę zwrócić na równoległy model przetwarzania, tzn. na możliwość zapisania w jednym rejestrze wielu reprezentacji i wykonywania jednym rozkazem działań na wszystkich zapisanych reprezentacjach.
|
||||
|
||||
|
||||
## Opis zadania:
|
||||
|
||||
Napisać funkcję `filter(...)` obliczającą splot (https://en.wikipedia.org/wiki/Kernel_(image_processing)#Convolution) macierzy dwuwymiarowych.
|
||||
Elementami macierzy będą reprezentacje w kodzie naturalnym binarnym.
|
||||
|
||||
Jedna z macierzy (oznaczona w poniższym opisie przez **M**) będzie dość duża (o rozmiarze co najmniej kilkaset na kilkaset elementów), druga (oznaczona przez **k**) będzie macierzą 3x3 elementy.
|
||||
Wynikiem będzie duża macierz **W** o rozmiarze takim, jak macierz **M**.
|
||||
|
||||
W praktycznej realizacji elementy obu macierzy **M** i **W** będą pikselami obrazków, a elementy małej macierzy **k** będą specjalnie dobranymi stałymi pozwalającymi na uzyskanie konkretnego efektu.
|
||||
Państwa implementacje powinny używać macierzy **k** postaci:
|
||||
|
||||
-1 -1 0
|
||||
-1 0 1
|
||||
0 1 1
|
||||
|
||||
ponieważ dla takiej macierzy zostały przygotowane dane pozwalające na sprawdzenie poprawności implementacji.
|
||||
|
||||
Na operację splotu można patrzeć jak na:
|
||||
* kolejne "nakładanie" małej macierzy **k** na kolejne pozycje w macierzy dużej **M**,
|
||||
* mnożenie elementów dużej macierzy przez pokrywające się z nimi elementy macierzy małej,
|
||||
* sumowanie uzyskanych iloczynów,
|
||||
* zapis otrzymanej sumy jako elementu w macierzy wynikowej, który znajduje się na pozycji wyznaczonej przez położenie środkowego elementu macierzy **k**.
|
||||
|
||||
|
||||
### Przykład:
|
||||
|
||||
Macierz **M**, `M(i,j)` oznacza element na pozycji (*i*,*j*):
|
||||
|
||||
M(0,0) M(0,1) M(0,2) M(0,3) ...
|
||||
M(1,0) M(1,1) M(1,2) M(1,3) ...
|
||||
M(2,0) M(2,1) M(2,2) M(2,3) ...
|
||||
M(3,0) M(3,1) M(3,2) M(3,3) ...
|
||||
... ... ... ... ...
|
||||
|
||||
Macierz **k**:
|
||||
|
||||
k(0,0) k(0,1) k(0,2
|
||||
k(1,0) k(1,1) k(1,2)
|
||||
k(2,0) k(2,1) k(2,2)
|
||||
|
||||
---
|
||||
Iteracja 1 (znakiem `~` oznaczono położenie środkowego elementu macierzy **k**):
|
||||
|
||||
|
||||
"Nałożenie" macierzy:
|
||||
|
||||
|
||||
k(0,0) k(0,1) k(0,2) M(0,3) ...
|
||||
k(1,0) ~k(1,1) k(1,2) M(1,3) ...
|
||||
k(2,0) k(2,1) k(2,2) M(2,3) ...
|
||||
M(3,0) M(3,1) M(3,2) M(3,3) ...
|
||||
... ... ... ...
|
||||
|
||||
Mnożenie i sumowanie:
|
||||
|
||||
W(1,1) =
|
||||
M(0,0)*k(0,0) + M(0,1)*k(0,1) + M(0,2)*k(0,2) +
|
||||
M(1,0)*k(1,0) + M(1,1)*k(1,1) + M(1,2)*k(1,2) +
|
||||
M(2,0)*k(2,0) + M(2,1)*k(2,1) + M(2,2)*k(2,2)
|
||||
|
||||
---
|
||||
Iteracja 2:
|
||||
|
||||
"Nałożenie" macierzy:
|
||||
|
||||
M(0,0) k(0,0) k(0,1) k(0,2) ...
|
||||
M(1,0) k(1,0) ~k(1,1) k(1,2) ...
|
||||
M(2,0) k(2,0) k(2,1) k(2,2) ...
|
||||
M(3,0) M(3,1) M(3,2) M(3,3) ...
|
||||
... ... ... ...
|
||||
|
||||
Mnożenie i sumowanie:
|
||||
|
||||
W(1,2) =
|
||||
M(0,1)*k(0,0) + M(0,2)*k(0,1) + M(0,3)*k(0,2) +
|
||||
M(1,1)*k(1,0) + M(1,2)*k(1,1) + M(1,3)*k(1,2) +
|
||||
M(2,1)*k(2,0) + M(2,2)*k(2,1) + M(2,3)*k(2,2)
|
||||
|
||||
---
|
||||
Jedna z kolejnych iteracji:
|
||||
|
||||
"Nałożenie" macierzy:
|
||||
|
||||
M(0,0) M(0,1) M(0,2) M(0,3) ...
|
||||
M(1,0) k(0,0) k(0,1) k(0,2) ...
|
||||
M(2,0) k(1,0) ~k(1,1) k(1,2) ...
|
||||
M(3,0) k(2,0) k(2,1) k(2,2) ...
|
||||
... ... ... ...
|
||||
|
||||
Mnożenie i sumowanie:
|
||||
|
||||
W(2,2) =
|
||||
M(1,1)*k(0,0) + M(1,2)*k(0,1) + M(1,3)*k(0,2) +
|
||||
M(2,1)*k(1,0) + M(2,2)*k(1,1) + M(2,3)*k(1,2) +
|
||||
M(3,1)*k(2,0) + M(3,2)*k(2,1) + M(3,3)*k(2,2)
|
||||
---
|
||||
|
||||
Państwa zadaniem będzie zaimplementowanie funkcji realizującej tę operację z wykorzystaniem jednostki MMX dostępnej w procesorach rodziny x86.
|
||||
Zaimplementowana funkcja koniecznie musi w jak największym stopniu wykorzystywać możliwości wykonywania wielu operacji jedną instrukcją.
|
||||
Oznacza to, że implementacja powinna jak najwięcej działań przeprowadzać równolegle.
|
||||
Implementacje wykorzystujące rozkazy MMX, ale przeprowadzające obliczenia bez równoległości ("pojedynczo", szeregowo) będą niżej punktowane.
|
||||
|
||||
|
||||
Po zaimplementowaniu funkcji należy zmierzyć jej czas trwania korzystając z instrukcji `rdtsc`.
|
||||
Po zmierzeniu czasu trwania należy także obliczyć i podać jakiś parametr pozwalający określić wydajność funkcji niezależnie od rozmiaru macierzy.
|
||||
Przykładową wartością może być np. liczba cykli zegara na jeden element macierzy wynikowej.
|
||||
Bardzo szybkie implementacje mogą uzyskać wyniki na poziomie pojedynczych cykli zegara na element, lub nawet mniej w niektórych przypadkach.
|
||||
Podczas pomiarów wydajności proszę pamiętać o włączeniu optymalizacji kodu.
|
||||
|
||||
|
||||
## Uwagi techniczne
|
||||
|
||||
Pod adresem http://zak.iiar.pwr.wroc.pl/materials/architektura/laboratorium%20AK2/MMX/png.tar.bz2 dostępny jest kod programu (plik `png.c`), który:
|
||||
* tworzy w pamięci reprezentację macierzy **M** na podstawie obrazka w formacie png o nazwie podanej jako argument programu,
|
||||
* wywołuje funkcję, której napisanie jest Państwa zadaniem,
|
||||
* wynik zapisuje w postaci obrazka w pliku `out.png`.
|
||||
|
||||
Oprócz kodu programu, w pliku `png.tar.bz2` znajdują się także przykładowe obrazy będące danymi wejściowymi oraz spodziewane odpowiedzi (w plikach o nazwach zawierających `_emboss`).
|
||||
Do sprawdzania uzyskanych wyników można np. wykorzystać jedną z technik wymienionych pod adresem https://askubuntu.com/questions/209517/does-diff-exist-for-images, są dostępne także inne narzędzia do porównywania obrazków.
|
||||
Można także przygotować swoje funkcje porównujące wartości w poszczególnych pikselach, ale jest to rozwiązanie niezalecane ze względu na czasochłonność.
|
||||
Proszę także wziąć pod uwagę, że w tym zadaniu możliwe są różne sposoby implementacji dające nieco inne wyniki.
|
||||
Jeśli więc zaobserwowane różnice są niewielkie (na poziomie kilku najmniej znaczących bitów), to wyniki można uznać za poprawne.
|
||||
|
||||
Program `png.c` korzysta z biblioteki libpng.
|
||||
Na moim systemie użyłem wersji 1.2, ale nie powinno to mieć znaczenia.
|
||||
Biblioteka libpng powinna być automatycznie zainstalowana w większości systemów linuksowych.
|
||||
W razie potrzeby powinno się ją dać bardzo łatwo doinstalować metodami standardowymi dla danego systemu.
|
||||
Czasami może zachodzić potrzeba doinstalowania plików nagłówkowych, które zazwyczaj są w pakiecie zawierającym `-dev` w nazwie, np. `libpng12-dev`.
|
||||
|
||||
Kod programu `png.c` powstał jako dość swobodna kopia przykładów z dokumentacji biblioteki libpng: http://www.libpng.org/pub/png/pngbook.html
|
||||
Działa dla załączonych obrazków, ale jeśli chcielibyście Państwo użyć także swoich danych, najprawdopodobniej należałoby albo przebudować program, albo odpowiednio przygotować obrazki wejściowe.
|
||||
Szczegółów nie podaję, ponieważ jest ich dość dużo, a nie są bezpośrednio związane z treścią ćwiczenia.
|
||||
Proszę także tego kodu nie traktować jako wzorcowego przykładu użycia biblioteki libpng, ponieważ ten kod powstał wyłącznie jako tymczasowe narzędzie ułatwiające realizację ćwiczenia.
|
||||
|
||||
W pierwszym kroku realizacji zadania należy skonstruować odpowiednie polecenia kompilacji i linkowania tak, aby uzyskać plik wykonywalny.
|
||||
Zazwyczaj wystarcza dodanie opcji `-lpng` podczas linkowania, ale zdecydowanie zalecam sprawdzenie w dokumentacji działania opcji `gcc -I`, `-L` i `-l`.
|
||||
W moim systemie pliki biblioteki są w katalogu `/usr/lib/x86_64-linux-gnu/`, a plik nagłówkowy w katalogu `/usr/include`.
|
||||
|
||||
Po uruchomieniu kodu zalecam najpierw napisanie dla rozgrzewki jakiejś prostej funkcji w C, która pozwoli Państwu nauczyć się swobodnie poruszać po reprezentacji macierzy **M**.
|
||||
Często stosowanym rozwiązaniem jest po prostu użycie dwóch pętli, ale są oczywiście możliwe także inne rozwiązania.
|
||||
Dobrym pomysłem jest np. próba narysowania kreski lub paska na obrazku wynikowym.
|
||||
Najniższa wartość elementu odpowiada czarnemu pikselowi, najwyższa wartość elementu pikselowi białemu.
|
||||
|
||||
W pamięci operacyjnej macierz **M** zapisana jest "wierszami" (https://en.wikipedia.org/wiki/Row-_and_column-major_order), czyli pierwszym elementem (pod najniższym adresem) jest element `M(0,0)`, potem `M(0,1)`, `M(0,2)` ... `M(1,0)`, `M(1,1)`, ...
|
||||
|
||||
Po opanowaniu poruszania się po macierzy zdecydowanie zalecam najpierw napisanie w C prostej, szeregowej wersji funkcji realizującej zadany problem.
|
||||
Funkcja ta powinna wszystkie obliczenia wykonywać korzystając wyłącznie z arytmetyki stałoprzecinkowej, ponieważ tylko taka jest dostępna w jednostce MMX.
|
||||
Szczególną uwagę proszę zwrócić na dobranie odpowiednich typów danych dla wyników pośrednich.
|
||||
Należy określić, jakie będą zakresy tych wyników i zagwarantować, że podczas obliczeń nie pojawią się błędy spowodowane przepełnieniem.
|
||||
Błędy takie zazwyczaj są widoczne na obrazku wynikowym jako nagłe zmiany jasności sąsiednich pikseli.
|
||||
|
||||
Proszę także pamiętać, że elementy macierzy wynikowej **W** są zapisane w dokładnie takim samym formacie, jak elementy macierzy **M**.
|
||||
Ponieważ więc wyniki obliczeń mogą znacznie przekraczać dopuszczalny zakres wartości, należy je LINIOWO przeskalować tak, aby:
|
||||
* najmniejsza wartość elementu macierzy **W** odpowiadała najmniejszej wartości wyniku końcowego,
|
||||
* największa wartość elementu macierzy **W** odpowiadała największej wartości wyniku końcowego,
|
||||
* a pozostałe wartości wyniku odpowiadały wartościom elementów **W** z zachowaniem relacji większości, tzn. jeśli `a_i < a_j`, to `W(a_i) <= W(a_j)`, gdzie `a_i` oznacza wartość wyniku, a `W(a_i)` wartość macierzy **W** wytworzoną z wyniku `a_i`.
|
||||
|
||||
|
||||
### Przykład:
|
||||
|
||||
Jeśli np. dopuszczalny zakres wartości elementów **W** to **<0, 100>**, a dopuszczalny zakres wartości wyniku **a** to **<0, 1000>**, to liniowe skalowanie można zrealizować zwykłym dzieleniem wyniku przez 10, czyli `W(a) = a / 10`.
|
||||
|
||||
---
|
||||
|
||||
Skalowanie można przeprowadzać w co najmniej dwóch miejscach:
|
||||
|
||||
* PRZED wykonaniem działań, czyli skalować od razu argumenty,
|
||||
* PO wykonaniu działań, czyli skalować wynik końcowy.
|
||||
|
||||
Oczywiście skalowanie można także wykonywać w dowolnym momencie obliczeń.
|
||||
|
||||
Skalowanie PRZED wykonaniem obliczeń pozwala na używanie mniejszych typów danych, co może np. pozwolić na szybsze obliczenia w jednostce MMX, ponieważ więcej działań będzie można wykonać jedną instrukcją.
|
||||
Skalowanie PRZED obliczeniami powoduje jednak większe błędy.
|
||||
|
||||
Skalowanie PO obliczeniach powoduje mniejsze błędy, ale wymaga przeprowadzania obliczeń na szerszych typach danych.
|
||||
|
||||
Z punktu widzenia celów tego ćwiczenia nie ma znaczenia, który z tych rodzajów skalowania Państwo wybierzecie, ważne jest jedynie, aby uzyskać poprawne wyniki.
|
||||
|
||||
Sztuczka techniczna – ponieważ w arytmetyce stałoprzecinkowej może być trudno zapisać dokładnie wymagane współczynniki skalowania, bardzo często stosuje się ich "rozsądne" przybliżenia.
|
||||
W skrajnych przypadkach można nawet zastosować przybliżenie najbliższą potęgą 2, co sprowadza skalowanie do zwykłych przesunięć bitowych.
|
||||
W Państwa implementacjach proszę spróbować poszukać rozsądnego kompromisu pomiędzy szybkością a dokładnością, ze zdecydowanym naciskiem na szybkość.
|
||||
Załączone dane przykładowe powstały dla bardzo niedokładnych przybliżeń.
|
||||
|
||||
W operacji splotu pojawia się problem związany z właściwą obsługą wartości ze skrajnych wieszy/kolumn macierzy.
|
||||
W tym ćwiczeniu poprawna obsługa tych wartości jest zupełnie nieistotna, dlatego zdecydowanie zalecam po prostu zignorowanie tych skrajnych kolumn i wierszy.
|
||||
W załączonych danych skrajne wartości są ustawione przypadkowo, głównie na najmniejszą wartość.
|
||||
|
||||
Po opracowaniu poprawnej wersji w C, można przejść do realizacji właściwej części zadania, czyli do implementacji wykorzystującej równoległość udostępnianą przez jednostkę MMX.
|
||||
Przy pierwszym kontakcie z modelem SIMD zalecam najpierw przemyślenie algorytmu i przygotowanie wstępnej koncepcji, np. w postaci obrazków pokazujących, jak będą ułożone dane w pamięci, w rejestrach, i jak na tych danych będą wykonywane równoległe operacje.
|
||||
Zdecydowanie pomaga rozrysowanie sobie, jak w pamięci zapisane są elementy macierzy **M** i **W**.
|
||||
|
||||
|
||||
Następnie, przygotowanie kodu realizującego to zadanie najprościej będzie chyba Państwu zrobić po prostu w odzielnym pliku asemblerowym.
|
||||
Możliwe są także różne techniki udostępniane przez narzędzie `gcc`, ale przy pierwszym kontakcie zdecydowanie odradzam ich stosowanie.
|
||||
Jeśli jednak ktoś z Państwa ma doświadczenie w tym zakresie i potrafi poprawnie zrealizować to zadanie z poziomu kodu w C, to takie rozwiązania także są dopuszczalne.
|
||||
Jeszcze raz jednak przestrzegam osoby, które nigdy dotychczas nie spotkały się z takimi technikami, ponieważ wymagają one dogłębnego zrozumienia tematu.
|
||||
|
||||
|
||||
Podczas przygotowywania implementacji MMX proszę skupić się wyłącznie na próbach opracowania kodu jak najlepiej wykorzystującego możliwości sprzętu.
|
||||
Zalecam więc np.:
|
||||
* wykorzystanie ustalonej postaci macierzy **k** (zakładamy, że ta macierz się na pewno nie zmieni, dlatego można znacznie uprościć wykonywane działania),
|
||||
* całkowicie zignorować te skrajne wiersze i kolumny macierzy **M**, które nie będą "pasowały" Państwu do koncepcji,
|
||||
* skalowanie wykonać w najprostszy możliwy sposób, nawet kosztem dokładności.
|
||||
@@ -0,0 +1,260 @@
|
||||
# Jan Potocki 2020
|
||||
|
||||
.globl filter
|
||||
.type filter, @function
|
||||
|
||||
.data
|
||||
# Pozwala uzyc rozkazu movdqa zamiast movdqu
|
||||
.align 16
|
||||
|
||||
# Macierz k w 4 kopiach - do rejestru xmm zmiescimy dane dla 4 pikseli splotu
|
||||
# 1. wiersz macierzy dopelniony do 32 bitow
|
||||
kernel128_1:
|
||||
.byte -1, -1, 0, 0
|
||||
.byte -1, -1, 0, 0
|
||||
.byte -1, -1, 0, 0
|
||||
.byte -1, -1, 0, 0
|
||||
# 2. wiersz macierzy dopelniony do 32 bitow
|
||||
kernel128_2:
|
||||
.byte -1, 0, 1, 0
|
||||
.byte -1, 0, 1, 0
|
||||
.byte -1, 0, 1, 0
|
||||
.byte -1, 0, 1, 0
|
||||
# 3. wiersz macierzy dopelniony do 32 bitow
|
||||
kernel128_3:
|
||||
.byte 0, 1, 1, 0
|
||||
.byte 0, 1, 1, 0
|
||||
.byte 0, 1, 1, 0
|
||||
.byte 0, 1, 1, 0
|
||||
|
||||
# Stala na potrzeby normalizacji - wartosc splotu z k miesci sie w przedziale
|
||||
# od 3*(-1*255) do 3*(1*255) czyli <-765,765>, wiec dodanie 765 pozwoli
|
||||
# przesunac ja w przedzial <0,1530>.
|
||||
# Na tym etapie obliczen liczby beda zapisane w 16-bitowej reprezentacji.
|
||||
bias128: .word 765, 765, 765, 765, 765, 765, 765, 765
|
||||
|
||||
# Argumenty:
|
||||
# rdi - adres M
|
||||
# rsi - adres W
|
||||
# rdx - rozdzielczosc w poziomie
|
||||
# rcx - rozdzielczosc w pionie
|
||||
|
||||
.text
|
||||
|
||||
filter:
|
||||
push %rbp
|
||||
mov %rsp, %rbp
|
||||
push %r15
|
||||
push %r14
|
||||
push %r13
|
||||
push %r12
|
||||
|
||||
# r15 - warunek stopu dla petli SIMD, tyle ile zmiesci sie w wierszu
|
||||
mov %rdx, %r15
|
||||
sub $17, %r15
|
||||
# r14 - warunek stopu dla petli indeksujacej wiersze
|
||||
mov %rcx, %r14
|
||||
sub $2, %r14
|
||||
# r12 - warunek stopu dla petli pomocniczej, dokonczenie wiersza
|
||||
mov %rdx, %r12
|
||||
sub $2, %r12
|
||||
# ...w tej implementacji skrajne piksele obrazu wynikowego pomijamy
|
||||
|
||||
# Wpisanie stalych do rejestrow aby przyspieszyc obliczenia
|
||||
movdqa bias128, %xmm12
|
||||
movdqa kernel128_1, %xmm13
|
||||
movdqa kernel128_2, %xmm14
|
||||
movdqa kernel128_3, %xmm15
|
||||
|
||||
# r9 - pomocniczy indeks adresujacy wiersze w pamieci
|
||||
xor %r9, %r9
|
||||
# r10 - indeks rozdzielczosci pionowej (wierszy)
|
||||
xor %r10, %r10
|
||||
|
||||
# Petla indeksujaca wiersze
|
||||
lp1:
|
||||
# r11 - indeks rozdzielczosci poziomej (pikseli w wierszu)
|
||||
xor %r11, %r11
|
||||
|
||||
# Podstawowa petla SIMD (16 pikseli na iteracje)
|
||||
lp2:
|
||||
# r8 - adres pikseli w pamieci (suma adresu wiersza i indeksu pikseli)
|
||||
mov %r9, %r8
|
||||
add %r11, %r8
|
||||
|
||||
# Instrukcja pinsrd kopiuje 4 kolejne 8-bitowe piksele do fragmentu xmm,
|
||||
# czyli tyle ile trzeba aby obliczyc splot z jednym wierszem k. Ostatni piksel
|
||||
# zachodzi na pierwszy kolejnego splotu - to nie ma znaczenia, bo kazdy wiersz
|
||||
# macierzy k jest do 32-bitowej postaci dopelniony zerem na ostatnim bajcie.
|
||||
# Instrukcja pmaddubsw oblicza iloczyn 8-bitowych pikseli obrazu i wartosci
|
||||
# kernela, sumuje dwa sasiednie iloczyny i w ich miejsce zapisuje wynik
|
||||
# w 16-bitowej reprezentacji U2.
|
||||
|
||||
# Gorny wiersz macierzy k
|
||||
pinsrd $0, 0(%rdi, %r8, 1), %xmm0
|
||||
pinsrd $1, 1(%rdi, %r8, 1), %xmm0
|
||||
pinsrd $2, 2(%rdi, %r8, 1), %xmm0
|
||||
pinsrd $3, 3(%rdi, %r8, 1), %xmm0
|
||||
pmaddubsw %xmm13, %xmm0
|
||||
|
||||
pinsrd $0, 4(%rdi, %r8, 1), %xmm1
|
||||
pinsrd $1, 5(%rdi, %r8, 1), %xmm1
|
||||
pinsrd $2, 6(%rdi, %r8, 1), %xmm1
|
||||
pinsrd $3, 7(%rdi, %r8, 1), %xmm1
|
||||
pmaddubsw %xmm13, %xmm1
|
||||
|
||||
pinsrd $0, 8(%rdi, %r8, 1), %xmm6
|
||||
pinsrd $1, 9(%rdi, %r8, 1), %xmm6
|
||||
pinsrd $2, 10(%rdi, %r8, 1), %xmm6
|
||||
pinsrd $3, 11(%rdi, %r8, 1), %xmm6
|
||||
pmaddubsw %xmm13, %xmm6
|
||||
|
||||
pinsrd $0, 12(%rdi, %r8, 1), %xmm7
|
||||
pinsrd $1, 13(%rdi, %r8, 1), %xmm7
|
||||
pinsrd $2, 14(%rdi, %r8, 1), %xmm7
|
||||
pinsrd $3, 15(%rdi, %r8, 1), %xmm7
|
||||
pmaddubsw %xmm13, %xmm7
|
||||
|
||||
add %rdx, %r8
|
||||
# Przechowanie adresu srodkowego wiersza na pozniej, do zapisu wyniku splotu
|
||||
mov %r8, %r13
|
||||
|
||||
# Srodkowy wiersz macierzy k
|
||||
pinsrd $0, 0(%rdi, %r8, 1), %xmm2
|
||||
pinsrd $1, 1(%rdi, %r8, 1), %xmm2
|
||||
pinsrd $2, 2(%rdi, %r8, 1), %xmm2
|
||||
pinsrd $3, 3(%rdi, %r8, 1), %xmm2
|
||||
pmaddubsw %xmm14, %xmm2
|
||||
|
||||
pinsrd $0, 4(%rdi, %r8, 1), %xmm3
|
||||
pinsrd $1, 5(%rdi, %r8, 1), %xmm3
|
||||
pinsrd $2, 6(%rdi, %r8, 1), %xmm3
|
||||
pinsrd $3, 7(%rdi, %r8, 1), %xmm3
|
||||
pmaddubsw %xmm14, %xmm3
|
||||
|
||||
pinsrd $0, 8(%rdi, %r8, 1), %xmm8
|
||||
pinsrd $1, 9(%rdi, %r8, 1), %xmm8
|
||||
pinsrd $2, 10(%rdi, %r8, 1), %xmm8
|
||||
pinsrd $3, 11(%rdi, %r8, 1), %xmm8
|
||||
pmaddubsw %xmm14, %xmm8
|
||||
|
||||
pinsrd $0, 12(%rdi, %r8, 1), %xmm9
|
||||
pinsrd $1, 13(%rdi, %r8, 1), %xmm9
|
||||
pinsrd $2, 14(%rdi, %r8, 1), %xmm9
|
||||
pinsrd $3, 15(%rdi, %r8, 1), %xmm9
|
||||
pmaddubsw %xmm14, %xmm9
|
||||
|
||||
add %rdx, %r8
|
||||
|
||||
# Dolny wiersz macierzy k
|
||||
pinsrd $0, 0(%rdi, %r8, 1), %xmm4
|
||||
pinsrd $1, 1(%rdi, %r8, 1), %xmm4
|
||||
pinsrd $2, 2(%rdi, %r8, 1), %xmm4
|
||||
pinsrd $3, 3(%rdi, %r8, 1), %xmm4
|
||||
pmaddubsw %xmm15, %xmm4
|
||||
|
||||
pinsrd $0, 4(%rdi, %r8, 1), %xmm5
|
||||
pinsrd $1, 5(%rdi, %r8, 1), %xmm5
|
||||
pinsrd $2, 6(%rdi, %r8, 1), %xmm5
|
||||
pinsrd $3, 7(%rdi, %r8, 1), %xmm5
|
||||
pmaddubsw %xmm15, %xmm5
|
||||
|
||||
pinsrd $0, 8(%rdi, %r8, 1), %xmm10
|
||||
pinsrd $1, 9(%rdi, %r8, 1), %xmm10
|
||||
pinsrd $2, 10(%rdi, %r8, 1), %xmm10
|
||||
pinsrd $3, 11(%rdi, %r8, 1), %xmm10
|
||||
pmaddubsw %xmm15, %xmm10
|
||||
|
||||
pinsrd $0, 12(%rdi, %r8, 1), %xmm11
|
||||
pinsrd $1, 13(%rdi, %r8, 1), %xmm11
|
||||
pinsrd $2, 14(%rdi, %r8, 1), %xmm11
|
||||
pinsrd $3, 15(%rdi, %r8, 1), %xmm11
|
||||
pmaddubsw %xmm15, %xmm11
|
||||
|
||||
# Suma kolumn splotu
|
||||
paddw %xmm2, %xmm0
|
||||
paddw %xmm3, %xmm1
|
||||
paddw %xmm8, %xmm6
|
||||
paddw %xmm9, %xmm7
|
||||
paddw %xmm4, %xmm0
|
||||
paddw %xmm5, %xmm1
|
||||
paddw %xmm10, %xmm6
|
||||
paddw %xmm11, %xmm7
|
||||
|
||||
# Suma wierszy splotu
|
||||
phaddw %xmm1, %xmm0
|
||||
phaddw %xmm7, %xmm6
|
||||
|
||||
# Normalizacja wyniku splotu - dodanie zdefiniowanego obciazenia i skalowanie
|
||||
# z powrotem do 8-bitowej reprezentacji.
|
||||
# Dokladny wynik pozwoliloby uzyskac dzielenie przez 6 (1530/6=255),
|
||||
# przesuniecie o 3 bity w prawo jest wydajniejsze, a wciaz pozwala uzyskac
|
||||
# rozsadna dokladnosc (1530>>3=191)
|
||||
paddw %xmm12, %xmm0
|
||||
paddw %xmm12, %xmm6
|
||||
psrlw $3, %xmm0
|
||||
psrlw $3, %xmm6
|
||||
|
||||
# Spakowanie 8-bitowych wynikow do jednego rejestru i zapis do pamieci
|
||||
packuswb %xmm6, %xmm0
|
||||
movdqu %xmm0, 1(%rsi, %r13, 1)
|
||||
|
||||
# Sprawdzenie, czy nastepna iteracja petli SIMD zmiesci sie w wierszu
|
||||
add $16, %r11
|
||||
cmp %r15, %r11
|
||||
jl lp2
|
||||
|
||||
# Sprawdzenie, czy wiersz zostal przeliczony w calosci...
|
||||
cmp %r12, %r11
|
||||
je next_row
|
||||
|
||||
# ...jesli nie - na dobieg pomocnicza petla (1 piksel na iteracje)
|
||||
lp2a:
|
||||
mov %r9, %r8
|
||||
add %r11, %r8
|
||||
|
||||
# Gorny wiersz macierzy k
|
||||
pinsrd $0, 0(%rdi, %r8, 1), %xmm0
|
||||
pmaddubsw %xmm13, %xmm0
|
||||
add %rdx, %r8
|
||||
mov %r8, %r13
|
||||
|
||||
# Srodkowy wiersz macierzy k
|
||||
pinsrd $0, 0(%rdi, %r8, 1), %xmm2
|
||||
pmaddubsw %xmm14, %xmm2
|
||||
add %rdx, %r8
|
||||
|
||||
# Dolny wiersz macierzy k
|
||||
pinsrd $0, 0(%rdi, %r8, 1), %xmm4
|
||||
pmaddubsw %xmm15, %xmm4
|
||||
|
||||
# Suma i normalizacja splotu
|
||||
paddw %xmm2, %xmm0
|
||||
paddw %xmm4, %xmm0
|
||||
phaddw %xmm0, %xmm0
|
||||
paddw %xmm12, %xmm0
|
||||
psrlw $3, %xmm0
|
||||
|
||||
# Spakowanie 8-bitowego wyniku w najmlodszym fragmencie rejestru i zapis
|
||||
packuswb %xmm0, %xmm0
|
||||
pextrb $0, %xmm0, 1(%rsi, %r13, 1)
|
||||
|
||||
# Sprawdzenie, czy tym razem wiersz jest juz przeliczony w calosci
|
||||
inc %r11
|
||||
cmp %r12, %r11
|
||||
jl lp2a
|
||||
|
||||
next_row:
|
||||
# Przesuniecie adresu wiersza w dol
|
||||
add %rdx, %r9
|
||||
# Sprawdzenie, czy caly obraz jest przeliczony
|
||||
inc %r10
|
||||
cmp %r14, %r10
|
||||
jl lp1
|
||||
|
||||
pop %r12
|
||||
pop %r13
|
||||
pop %r14
|
||||
pop %r15
|
||||
pop %rbp
|
||||
ret
|
||||
@@ -0,0 +1,147 @@
|
||||
#include <png.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
#define ERROR \
|
||||
fprintf (stderr, "ERROR at %s:%d.\n", __FILE__, __LINE__) ; \
|
||||
return -1 ;
|
||||
|
||||
#define HEADER_SIZE (1)
|
||||
|
||||
void filter(unsigned char * M, unsigned char * W, int width, int height);
|
||||
unsigned long long timestamp();
|
||||
|
||||
int main (int argc, char ** argv)
|
||||
{
|
||||
if (2 != argc)
|
||||
{
|
||||
printf ("\nUsage:\n\n%s file_name.png\n\n", argv[0]) ;
|
||||
|
||||
return 0 ;
|
||||
}
|
||||
|
||||
const char * file_name = argv [1] ;
|
||||
|
||||
unsigned char header [HEADER_SIZE] ;
|
||||
|
||||
FILE *fp = fopen (file_name, "rb");
|
||||
if (NULL == fp)
|
||||
{
|
||||
fprintf (stderr, "Can not open file \"%s\".\n", file_name) ;
|
||||
ERROR
|
||||
}
|
||||
|
||||
if (fread (header, 1, HEADER_SIZE, fp) != HEADER_SIZE)
|
||||
{
|
||||
ERROR
|
||||
}
|
||||
|
||||
if (0 != png_sig_cmp (header, 0, HEADER_SIZE))
|
||||
{
|
||||
ERROR
|
||||
}
|
||||
|
||||
png_structp png_ptr =
|
||||
png_create_read_struct
|
||||
(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL );
|
||||
if (NULL == png_ptr)
|
||||
{
|
||||
ERROR
|
||||
}
|
||||
|
||||
png_infop info_ptr = png_create_info_struct (png_ptr);
|
||||
if (NULL == info_ptr)
|
||||
{
|
||||
png_destroy_read_struct (& png_ptr, NULL, NULL);
|
||||
|
||||
ERROR
|
||||
}
|
||||
|
||||
if (setjmp (png_jmpbuf (png_ptr)))
|
||||
{
|
||||
png_destroy_read_struct (& png_ptr, & info_ptr, NULL);
|
||||
|
||||
ERROR
|
||||
}
|
||||
|
||||
png_init_io (png_ptr, fp);
|
||||
png_set_sig_bytes (png_ptr, HEADER_SIZE);
|
||||
png_read_info (png_ptr, info_ptr);
|
||||
|
||||
png_uint_32 width, height;
|
||||
int bit_depth, color_type;
|
||||
|
||||
png_get_IHDR
|
||||
(
|
||||
png_ptr, info_ptr,
|
||||
& width, & height, & bit_depth, & color_type,
|
||||
NULL, NULL, NULL
|
||||
);
|
||||
|
||||
if (8 != bit_depth)
|
||||
{
|
||||
ERROR
|
||||
}
|
||||
if (0 != color_type)
|
||||
{
|
||||
ERROR
|
||||
}
|
||||
|
||||
size_t size = width ;
|
||||
size *= height ;
|
||||
|
||||
unsigned char * M = malloc (size) ;
|
||||
|
||||
png_bytep ps [height] ;
|
||||
ps [0] = M ;
|
||||
for (unsigned i = 1 ; i < height ; i++)
|
||||
{
|
||||
ps [i] = ps [i-1] + width ;
|
||||
}
|
||||
png_set_rows (png_ptr, info_ptr, ps);
|
||||
png_read_image (png_ptr, ps) ;
|
||||
|
||||
printf
|
||||
(
|
||||
"Image %s loaded:\n"
|
||||
"\twidth = %lu\n"
|
||||
"\theight = %lu\n"
|
||||
"\tbit_depth = %u\n"
|
||||
"\tcolor_type = %u\n"
|
||||
, file_name, width, height, bit_depth, color_type
|
||||
) ;
|
||||
|
||||
unsigned char * W = malloc (size) ;
|
||||
|
||||
unsigned long long tstart = timestamp();
|
||||
filter (M, W, width, height) ;
|
||||
unsigned long long tstop = timestamp();
|
||||
|
||||
printf("\nTSC cycles: %llu\n", tstop-tstart);
|
||||
|
||||
png_structp write_png_ptr =
|
||||
png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
||||
if (NULL == write_png_ptr)
|
||||
{
|
||||
ERROR
|
||||
}
|
||||
|
||||
for (unsigned i = 0 ; i < height ; i++)
|
||||
{
|
||||
ps [i] += W - M ;
|
||||
}
|
||||
png_set_rows (write_png_ptr, info_ptr, ps);
|
||||
|
||||
FILE *fwp = fopen ("out.png", "wb");
|
||||
if (NULL == fwp)
|
||||
{
|
||||
ERROR
|
||||
}
|
||||
|
||||
png_init_io (write_png_ptr, fwp);
|
||||
png_write_png (write_png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
|
||||
fclose (fwp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
After Width: | Height: | Size: 125 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 168 KiB |
|
After Width: | Height: | Size: 120 KiB |
|
After Width: | Height: | Size: 444 KiB |
|
After Width: | Height: | Size: 342 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 834 KiB |
|
After Width: | Height: | Size: 3.2 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 927 KiB |
|
After Width: | Height: | Size: 2.4 MiB |
|
After Width: | Height: | Size: 224 KiB |
|
After Width: | Height: | Size: 155 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 954 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 832 KiB |
|
After Width: | Height: | Size: 405 KiB |
|
After Width: | Height: | Size: 7.8 MiB |
|
After Width: | Height: | Size: 5.9 MiB |
|
After Width: | Height: | Size: 315 KiB |
@@ -0,0 +1,19 @@
|
||||
# Jan Potocki 2020
|
||||
|
||||
.global timestamp
|
||||
|
||||
# Segment kodu
|
||||
.text
|
||||
|
||||
timestamp:
|
||||
push %rbx
|
||||
|
||||
xor %rax, %rax
|
||||
cpuid
|
||||
rdtsc
|
||||
|
||||
shl $32, %rdx
|
||||
or %rdx, %rax
|
||||
|
||||
pop %rbx
|
||||
ret
|
||||