Compare commits

...

9 Commits

Author SHA1 Message Date
MKjanek32 b9c81db777 Wydzielenie zadania z README 2026-05-21 04:24:12 +02:00
MKjanek32 bcac1a48fa Opis zadania w osobnym pliku 2026-05-21 04:23:09 +02:00
MKjanek32 35853aa4ff R2026.05.1 - filtrowanie obrazu przy pomocy SSE 2026-05-20 16:20:01 +02:00
MKjanek32 3db92e93f4 Aktualizacja linków 2026-05-20 16:05:31 +02:00
MKjanek32 c1afe154bb fibb_tsc: poprawka timestamp(), zabezpieczenie EBX/RBX 2026-05-20 15:51:26 +02:00
MKjanek32 9a28acb0c9 R2023.05.1 - aktualizacja wykładu 2023-05-24 20:05:11 +02:00
MKjanek32 f2242fdb42 Aktualizacja README (semestr letni 2022) 2022-04-04 23:12:05 +02:00
MKjanek32 6fee8ccd48 Wydanie R2022.04.1 2022-04-04 23:09:51 +02:00
Jan Potocki 0ba84bef75 Wydanie R2020.05.1 2020-05-09 22:17:22 +02:00
45 changed files with 1200 additions and 3 deletions
+7 -3
View File
@@ -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 Developers 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
+22
View File
@@ -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
+43
View File
@@ -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
+40
View File
@@ -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
+50
View File
@@ -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
+47
View File
@@ -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
+23
View File
@@ -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;
}
+38
View File
@@ -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;
}
+20
View File
@@ -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;
}
+16
View File
@@ -0,0 +1,16 @@
# Jan Potocki 2020
.global timestamp
# Segment kodu
.text
timestamp:
push %ebx
xor %eax, %eax
cpuid
rdtsc
pop %ebx
ret
+19
View File
@@ -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
+7
View File
@@ -0,0 +1,7 @@
all: mul
mul: mul.s
gcc mul.s -o mul
clean:
rm mul
+17
View File
@@ -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ń.
+149
View File
@@ -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
Binary file not shown.
+10
View File
@@ -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
+43
View File
@@ -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
+7
View File
@@ -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
+6
View File
@@ -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.
+210
View File
@@ -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.
+260
View File
@@ -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
+147
View File
@@ -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;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 834 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 927 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 954 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 832 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

+19
View File
@@ -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