PERSONAL - Grundlagenpraktikum: Rechnerarchitektur

x86-64 ASM vs x86-32 ASM

Registerüberblick

3ccd9b4530b8339cb1cf7e74c41cc4ab.png

Immediate-Operanden

IA-64 und IA-32 Software Development Manual

Instruktionen lesen und verstehen

System Calls

System Calls auf x86-64 Linux Systeme

Layout einer Programmbinary

Start eines Programms

Stack bei Programmstart

802d44729f26d08067e6f13c1889d553.png

Textaufgabe auf der Konsole

Beenden eines Programms

Grundlagen - C

Datentypen in C

4b82803a14dd6096d9c23e16862ec1c1.png

Integers

Beispielcode

unsigned long l = 42;
signed char c = -1;
unsigned i = UINT_MAX; // impl. unsigned int
unsigned long l = 42;
signed char c = -1;
unsigned i = UINT_MAX; // impl. unsigned int

Floating-Point-Zahlen

void

Pointer-Datentypen

1baee9a607994a984c1b9c384122a294.png

Funktionen in C

void foo ( int n ) ; // <-- Deklaration
void foo ( int n ) { // <-- Definition
    ...
}

void bar ( unsigned n ) { // <-- Deklaration + Definition
    ...
}
void foo ( int n ) ; // <-- Deklaration
void foo ( int n ) { // <-- Definition
    ...
}

void bar ( unsigned n ) { // <-- Deklaration + Definition
    ...
}
int foo ( void ) ; // <-- RICHTIG : akzeptiert keine Parameter

int bar () ; // <-- FALSCH : kann mit beliebigen Parametern
             // definiert / aufgerufen werden
int foo ( void ) ; // <-- RICHTIG : akzeptiert keine Parameter

int bar () ; // <-- FALSCH : kann mit beliebigen Parametern
             // definiert / aufgerufen werden
void foo ( unsigned n , short s ) {
    ...
    return; // <-- kein Rückgabewert, hier optional
}

int bar ( long long multi_word_parameter ) {
    ...
    return -42; // <-- int als Rückgabewert
}
void foo ( unsigned n , short s ) {
    ...
    return; // <-- kein Rückgabewert, hier optional
}

int bar ( long long multi_word_parameter ) {
    ...
    return -42; // <-- int als Rückgabewert
}

Die main-Funktion

int main ( void ) { // keine Parameter
    ...
    return EXIT_SUCCESS ; // 0
}

int main ( int argc , const char ** argv ) { // 2 Parameter (Anz. der Kommandozeilenargumente in argc und die Argumente als Array von Strings in argv)
// erstes Kommandozeilenargument ist üblicherweise Name des Programms
    ...
    return 1; // Implementation-defined error code
}
int main ( void ) { // keine Parameter
    ...
    return EXIT_SUCCESS ; // 0
}

int main ( int argc , const char ** argv ) { // 2 Parameter (Anz. der Kommandozeilenargumente in argc und die Argumente als Array von Strings in argv)
// erstes Kommandozeilenargument ist üblicherweise Name des Programms
    ...
    return 1; // Implementation-defined error code
}

Variablen in C

TYPE_NAME [ = VALUE ]; // Deklaration
const TYPE_NAME = VALUE; // Deklaration und Zuweisung einer konstanten Variable
TYPE_NAME [ = VALUE ]; // Deklaration
const TYPE_NAME = VALUE; // Deklaration und Zuweisung einer konstanten Variable
const int a;
a = 4; // COMPILER-FEHLER
const int a;
a = 4; // COMPILER-FEHLER
const TYPE* PTR [= ADDR ]; // Pointer auf konstante Daten

TYPE* const PTR = ADDR ; // Konstanter Pointer auf
                         // variable Daten

const TYPE* const PTR = ADDR ; // Konstanter Pointer auf
                               // konstante Daten
const TYPE* PTR [= ADDR ]; // Pointer auf konstante Daten

TYPE* const PTR = ADDR ; // Konstanter Pointer auf
                         // variable Daten

const TYPE* const PTR = ADDR ; // Konstanter Pointer auf
                               // konstante Daten

Scopes

void foo () {
    int a = 42;
}

void bar () {
    int b = a ; // FEHLER : a ist nur in foo Sichtbar

    {
        int c = b ; // OK
    }

    int d = b ; // OK
    int e = c ; // FEHLER : c ist hier nicht mehr sichtbar
}
void foo () {
    int a = 42;
}

void bar () {
    int b = a ; // FEHLER : a ist nur in foo Sichtbar

    {
        int c = b ; // OK
    }

    int d = b ; // OK
    int e = c ; // FEHLER : c ist hier nicht mehr sichtbar
}

Zuweisung von Werten

int i ;
i = -2;           // negative Konstante im Dezimalsystem
i = 0xDEADBEEF ;  // Konstante im Hexadezimalsystem
i = 011;          // Konstante im Oktalsystem ( führende Null !!)
i = 'A';          // "character literal" - hier wird automatisch
                  // der entsprechende numerische Wert für den
                  // Buchstaben "A" eingefügt
                  // Siehe 'man 7 ascii' für eine Tabelle.

double d;
d = 2.0;          // double - Konstante
d = 2.0 f ;       // float - Konstante

int a = 2.0       // gibt a den Wert 2 als Integer
int i ;
i = -2;           // negative Konstante im Dezimalsystem
i = 0xDEADBEEF ;  // Konstante im Hexadezimalsystem
i = 011;          // Konstante im Oktalsystem ( führende Null !!)
i = 'A';          // "character literal" - hier wird automatisch
                  // der entsprechende numerische Wert für den
                  // Buchstaben "A" eingefügt
                  // Siehe 'man 7 ascii' für eine Tabelle.

double d;
d = 2.0;          // double - Konstante
d = 2.0 f ;       // float - Konstante

int a = 2.0       // gibt a den Wert 2 als Integer

Arithmetische und logische Operatoren

Operation direkte Zuweisung Bedeutung Ergebnis Ergebnis-Typ
a = a + 42 a += 42 Addition 84 unsigned
a = a - 42 a -= 42 Subtraktion 0 unsigned
a = a * 42 a *= 42 Multiplikation 1764 unsigned
a = a / 5 a /= 5 Division 8 unsigned
a = a % 5 a %= 5 Modulo 2 unsigned
a = a && 0 - logisches UND 0 int
a = a || 0 - logisches ODER 1 int
a = !a - logisches NOT 0 int
a = a << 2 a <<= 2 Linksshift 168 unsigned
a = a >> 2 a >>= 2 Rechtsshift 10 unsigned
a = a & 0x3 a &= 0x3 bitweises UND 2 unsigned
a = a | 0x5 a |= 0x5 bitweises ODER 47 unsigned
a = a ^ 0xff a ^= 0xff bitweises XOR 213 unsigned
a = ~a - bitweises NOT 4294967253 unsigned

Kontrollflussstrukturen

if-else-Bedingungen

if (x > 2.4) {
    ...
} else if (x < 0 x123456789) { // else if branch optional
    ...
} else { // else branch optional
    ...
}
if (x > 2.4) {
    ...
} else if (x < 0 x123456789) { // else if branch optional
    ...
} else { // else branch optional
    ...
}

while und do-while Schleifen

// while
while (n-- > 0) {
    ...
    if ( x == y ) {
        break; // beendet schleife
    }
    ...
}

// do...while
// code im schleifenkörper wird mindestens 1 mal ausgeführt
do {
    ...
    if ( x == y ) {
        continue; // bricht aktuelle iteration der schleife
    }
    ...
} while (--n > 0) ;
// while
while (n-- > 0) {
    ...
    if ( x == y ) {
        break; // beendet schleife
    }
    ...
}

// do...while
// code im schleifenkörper wird mindestens 1 mal ausgeführt
do {
    ...
    if ( x == y ) {
        continue; // bricht aktuelle iteration der schleife
    }
    ...
} while (--n > 0) ;

for-Schleifen

// Variante 0 - standard
for (int i = 0; i < 42; i++) { ... }

// Variante 1 - init. mehrere Variablen des gleichen Typs
for (int i = 0, j = 0; ...) { ... }

// Variante 2 - bereits deklarierte Variable
int k;
for (k = 0; k < 42; k++) { ... }

// Variante 3 - Schleife ohne Abbruchbedingung
for (;;) { ... } // analog zu "while (1) { ... }""

// Variante 4 - i-- im 2. Teil
for (unsigned i = n ; i-- > 0;) { ... }
// Variante 0 - standard
for (int i = 0; i < 42; i++) { ... }

// Variante 1 - init. mehrere Variablen des gleichen Typs
for (int i = 0, j = 0; ...) { ... }

// Variante 2 - bereits deklarierte Variable
int k;
for (k = 0; k < 42; k++) { ... }

// Variante 3 - Schleife ohne Abbruchbedingung
for (;;) { ... } // analog zu "while (1) { ... }""

// Variante 4 - i-- im 2. Teil
for (unsigned i = n ; i-- > 0;) { ... }

switch-Statements

switch (x) {
    case -42:
        ...
        break;
    case 'A':
        ...
        /* fall through */
    case 'B':
        ...
        break;
    default:
        ...
        break;
}
switch (x) {
    case -42:
        ...
        break;
    case 'A':
        ...
        /* fall through */
    case 'B':
        ...
        break;
    default:
        ...
        break;
}

Der C-Präprozessor

Makros

#define NUMBER 42   // Ersetze NUMBER durch 42
#define MYNUM 2 + 3 // Ersetze MYNUM durch 2 + 3

int a = NUMBER ;    // = 42
int b = MYNUM * 2;  // = 2 + 3 * 2 = 8 ( nicht (!) 10)

#undef MYNUM        // Mache Definition ruckgängig
#define NUMBER 42   // Ersetze NUMBER durch 42
#define MYNUM 2 + 3 // Ersetze MYNUM durch 2 + 3

int a = NUMBER ;    // = 42
int b = MYNUM * 2;  // = 2 + 3 * 2 = 8 ( nicht (!) 10)

#undef MYNUM        // Mache Definition ruckgängig

if-else-Konstrukte

#define MYFLAG 0

#if MYFLAG
const char c = ’A ’;
#else
const char c = ’B ’;
#endif

#if 0
int x = 42; // auskommentierter Code
#endif
#define MYFLAG 0

#if MYFLAG
const char c = ’A ’;
#else
const char c = ’B ’;
#endif

#if 0
int x = 42; // auskommentierter Code
#endif

#include-Direktiven

#include <system_header.h> // Copy - paste Inhalte von system_header.h an diese Stelle
#include "local_header.h" // Copy - paste Inhalte von local_header.h an diese Stelle
#include <system_header.h> // Copy - paste Inhalte von system_header.h an diese Stelle
#include "local_header.h" // Copy - paste Inhalte von local_header.h an diese Stelle

Header-Dateien

// foo.h
void func(void);

// foo.c
#include "foo.h"

void func(void) {...}

// main.c
#include "foo.h"

int main(void) {
    func();
    return 0;
}
// foo.h
void func(void);

// foo.c
#include "foo.h"

void func(void) {...}

// main.c
#include "foo.h"

int main(void) {
    func();
    return 0;
}

Sichtbarkeit

// foo.h
void func(void);

// foo.c
#include "foo.h"

static void helper(void) {...}
void func(void) {...}

// main.c
#include "foo.h"

static void helper(void){...}
int main(void) {
    func();
    return 0;
}
// foo.h
void func(void);

// foo.c
#include "foo.h"

static void helper(void) {...}
void func(void) {...}

// main.c
#include "foo.h"

static void helper(void){...}
int main(void) {
    func();
    return 0;
}

Standard-Header

// Systemweite Bibliotheksheader
# include < stdio.h >  // Input - Output Funktionalität
# include < string.h > // Funktionen zur Stringmanipulation

# include < stddef.h > // Definiert u.a. size_t (unsigned Typ, max. Größe von Objekten im Speicher).
                       // m.a.W. gibt es kein Speicherobjekt mit einer größeren Größe als size_t
                       // Bereits indirekt durch stdio.h eingebunden.

// Lokaler Header des Projekts
# include "myheader.h"
// Systemweite Bibliotheksheader
# include < stdio.h >  // Input - Output Funktionalität
# include < string.h > // Funktionen zur Stringmanipulation

# include < stddef.h > // Definiert u.a. size_t (unsigned Typ, max. Größe von Objekten im Speicher).
                       // m.a.W. gibt es kein Speicherobjekt mit einer größeren Größe als size_t
                       // Bereits indirekt durch stdio.h eingebunden.

// Lokaler Header des Projekts
# include "myheader.h"

stdint.h und stdbool.h

Signed Unsigned Größe
int8_t uint8_t 8 Bit
int16_t uint16_t 16 Bit
int32_t uint32_t 32 Bit
int64_t uint64_t 64 Bit

printf

// Hello World in C
# include < stdio .h > // <-- Wir brauchen die Deklaration von printf

int main (void) {
// Schreibe "Hello World!" gefolgt von einer Newline
    printf ("Hello World \n");
    return 0;
}
// Hello World in C
# include < stdio .h > // <-- Wir brauchen die Deklaration von printf

int main (void) {
// Schreibe "Hello World!" gefolgt von einer Newline
    printf ("Hello World \n");
    return 0;
}

Format Strings

unsigned a = 0x42;
printf(" The value of a is : % u\n", a);
unsigned a = 0x42;
printf(" The value of a is : % u\n", a);

Conversion Specifiers

Specifier Argumenttyp Ausgabe
d signed int Dezimaldarstellung
u unsigned int Dezimaldarstellung
x / X unsigned int Hex-Darstellung
c signed int ASCII-Zeichen
s const char* String

Disassemblieren mit objdump

Generierter Maschinencode

c09cf45f1e416efe8542ed5a21fecdc1.png

Optimierungsstufen

Stufen

Weitere Optimierung

fe300a0b80da54961180b28f99542dab.png

Extra: Godbolt Compiler Explorer

5ffd52abac66c4c499a2d49e5c8bbc78.png

Debugging mit GDB

Ausführen von Programme in GDB, Breakpoints, Programmfluss

Bsp: Array von Pointer und Strings

(gdb) p argc
$5 = 3

(gdb) x/3a argv
0x...: 0x... 0x...
0x...: 0x...

(gdb) x/s argv[1]
0x...: "hello"
(gdb) p argc
$5 = 3

(gdb) x/3a argv
0x...: 0x... 0x...
0x...: 0x...

(gdb) x/s argv[1]
0x...: "hello"

Optimierungen

Optimierung der Fibonacci-Reihenfolge

Basis-Fibonaccifunktion

# include <stdint.h>
uint64_t fib1 ( uint64_t n ) {
    if(n <= 1) {
        return n ; // fib (0) = 0 , fib (1) = 1
    }
    return fib1(n - 1) + fib1(n - 2);
}
# include <stdint.h>
uint64_t fib1 ( uint64_t n ) {
    if(n <= 1) {
        return n ; // fib (0) = 0 , fib (1) = 1
    }
    return fib1(n - 1) + fib1(n - 2);
}

Laufzeitklassen

Optimierung für kleine Eingabewerte

Fibonacci: Lineare Schleife statt Doppelter Rekursion

uint64_t fib2 ( uint64_t n ) {
    if (n == 0) { // base case
        return 0;
    }
    if (n > 93) { // integers greater than 93 cannot be represented using uint64_t
        return UINT64_MAX ;
    }

    uint64_t a = 0; 
    uint64_t b = 1;
    uint64_t i = 1;
    for (; i < n ; i++) {
        uint64_t tmp = b ;
        b += a ;
        a = tmp ;
    }

    return b ;
}
uint64_t fib2 ( uint64_t n ) {
    if (n == 0) { // base case
        return 0;
    }
    if (n > 93) { // integers greater than 93 cannot be represented using uint64_t
        return UINT64_MAX ;
    }

    uint64_t a = 0; 
    uint64_t b = 1;
    uint64_t i = 1;
    for (; i < n ; i++) {
        uint64_t tmp = b ;
        b += a ;
        a = tmp ;
    }

    return b ;
}

Fibonacci: Logarithmische Laufzeit mit Formel von Binet

Fibonacci: Lookup-Table (LUT)

// All 94 64 - bit fibonacci numbers ( n = 0 ,... ,93)
uint64_t lut[] = {0 ,1 ,1 ,2 ,3 ,5 ,8 ,13 ,21 ,34 ,55 ,89 ,144 ,233 ,377, ..., 7540113804746346429 ,12200160415121876738};

uint64_t fib3 (uint64_t n) {
    if (n > 93) {
        return UINT64_MAX;
    }

    return lut[n];
}
// All 94 64 - bit fibonacci numbers ( n = 0 ,... ,93)
uint64_t lut[] = {0 ,1 ,1 ,2 ,3 ,5 ,8 ,13 ,21 ,34 ,55 ,89 ,144 ,233 ,377, ..., 7540113804746346429 ,12200160415121876738};

uint64_t fib3 (uint64_t n) {
    if (n > 93) {
        return UINT64_MAX;
    }

    return lut[n];
}

Speicherplatzoptimierung: LUT verkleinern

# include < stdint .h >

// LUT for n = {0 ,16 ,32 ,48 ,64 ,80}
uint64_t lut0 [] = {0 ,987 ,2178309 ,4807526976 ,10610209857723 , 23416728348467685};

// LUT for n = {1 ,17 ,33 ,49 ,65 ,81}
uint64_t lut1 [] = {1 ,1597 ,3524578 ,7778742049 ,17167680177565 , 37889062373143906};

uint64_t fib4 ( uint64_t n ) {
    // case for numbers exceeding 64-bit limit
    if ( n > 93) {
        return UINT64_MAX ;
    }	

    // calculate index in interval to find first 2 numbers of interval
    uint64_t index = n / 16;
    uint64_t a = lut0 [ index ];
    uint64_t b = lut1 [ index ];
    
    // get pos. of first fibonacci number in interval
    index *= 16;
    if ( index == n ) // if number is already saved, return it
        return a ;

    // calculate fibonacci number using standard loop
    index++;
    for (; index < n ; index ++) {
        uint64_t tmp = b ;
        b += a ;
        a = tmp ;
    }

    return b ;
}
# include < stdint .h >

// LUT for n = {0 ,16 ,32 ,48 ,64 ,80}
uint64_t lut0 [] = {0 ,987 ,2178309 ,4807526976 ,10610209857723 , 23416728348467685};

// LUT for n = {1 ,17 ,33 ,49 ,65 ,81}
uint64_t lut1 [] = {1 ,1597 ,3524578 ,7778742049 ,17167680177565 , 37889062373143906};

uint64_t fib4 ( uint64_t n ) {
    // case for numbers exceeding 64-bit limit
    if ( n > 93) {
        return UINT64_MAX ;
    }	

    // calculate index in interval to find first 2 numbers of interval
    uint64_t index = n / 16;
    uint64_t a = lut0 [ index ];
    uint64_t b = lut1 [ index ];
    
    // get pos. of first fibonacci number in interval
    index *= 16;
    if ( index == n ) // if number is already saved, return it
        return a ;

    // calculate fibonacci number using standard loop
    index++;
    for (; index < n ; index ++) {
        uint64_t tmp = b ;
        b += a ;
        a = tmp ;
    }

    return b ;
}

597f319f784dd224a5090ca3b6376125.png

Komplexe Datenstrukturen in C

sizeof-Operator

size_t size;
size = sizeof(char);   // 1
size = sizeof(size_t); // 8
size = sizeof(void *); // 8

size_t a = sizeof(size_t);
size_t b = sizeof(a);
// a == b, da 'a' vom Typ 'size_t' ist
size_t size;
size = sizeof(char);   // 1
size = sizeof(size_t); // 8
size = sizeof(void *); // 8

size_t a = sizeof(size_t);
size_t b = sizeof(a);
// a == b, da 'a' vom Typ 'size_t' ist

Alignment

Pointer

int i = 0;
int* i_ptr = &i; // declare pointer variable of type int (int*) that points to address of i (&i)
int i_new = *i_ptr; // deref. pointer, i_new == i
int i = 0;
int* i_ptr = &i; // declare pointer variable of type int (int*) that points to address of i (&i)
int i_new = *i_ptr; // deref. pointer, i_new == i

Pointerarithmetik

// Pointerarithmetik
int i = 0;
int* i_ptr = &i; // 0x1234
i_ptr++;         // 0x1238
i_ptr -= 2;      // 0x1230 (undefined behavior, i_ptr points to element outside of array)
// Pointerarithmetik
int i = 0;
int* i_ptr = &i; // 0x1234
i_ptr++;         // 0x1238
i_ptr -= 2;      // 0x1230 (undefined behavior, i_ptr points to element outside of array)

e7802507e788b3091ba3e069335d5e0b.png

Pointerarithmetik - Arraysubscript

void-Pointer und implizite Pointer-Casts

int* i_ptr = ...;
void* v_ptr = i_ptr; // every pointer can become a void-pointer
int* i_ptr2 = v_ptr; // a void-pointer can become any pointer
int i = *v_ptr;      // compiler error!
int* i_ptr = ...;
void* v_ptr = i_ptr; // every pointer can become a void-pointer
int* i_ptr2 = v_ptr; // a void-pointer can become any pointer
int i = *v_ptr;      // compiler error!

Explizite Pointer-Casts

int* i_ptr = ...;
char* c_ptr = (char*) i_ptr;   // zugriff möglich
short* s_ptr = (short*) i_ptr; // zugriff undefined
long* l_ptr = (long*) i_ptr;   // cast undefined wegen strengeren alignment-anforderungen von long
int* i_ptr = ...;
char* c_ptr = (char*) i_ptr;   // zugriff möglich
short* s_ptr = (short*) i_ptr; // zugriff undefined
long* l_ptr = (long*) i_ptr;   // cast undefined wegen strengeren alignment-anforderungen von long

Arrays

a7d7e896607b1aea3748a0c7501ed074.png

int arr[3] = {1,2,3};
size_t arr_len = sizeof(arr) / sizeof(arr[0]); // 12 / 4 = 3
int arr[3] = {1,2,3};
size_t arr_len = sizeof(arr) / sizeof(arr[0]); // 12 / 4 = 3

3bce867ce565f5397d575dbf05f5d4c9.png

Arrays als Parameter

// pointersyntax
void func ( int * arr , size_t arr_length ) { // sizeof(arr) ermittelt NICHT größe des arrays!
    for ( size_t i = 0; i < arr_length ; i ++) {
        printf ( " % d \ n " , arr [ i ]) ;
    }
}

int main ( void ) {
    int arr [3] = {1 ,2 ,3};
    func ( arr , sizeof ( arr ) / sizeof ( arr [0]) ) ;
    // ...
}

// arraysyntax
// parameter sind immer noch pointer!
void func1 ( int arr [ arr_length ] , size_t arr_length ) {
    for ( size_t i = 0; i < arr_length ; i ++) {
        printf ( " % d \ n " , arr [ i ]) ;
    }
}
void func2 ( int arr [3]) { // sizeof(arr) würde immer noch größe des pointers zurückgeben!
    for ( size_t i = 0; i < 3; i ++) {
        printf ( " % d \ n " , arr [ i ]) ;
    }
}
int main ( void ) {
    int arr [3] = {1 ,2 ,3};
    func1 ( arr , sizeof ( arr ) / sizeof ( arr [0]) ) ;
    func2 ( arr ) ; // ...
}
// pointersyntax
void func ( int * arr , size_t arr_length ) { // sizeof(arr) ermittelt NICHT größe des arrays!
    for ( size_t i = 0; i < arr_length ; i ++) {
        printf ( " % d \ n " , arr [ i ]) ;
    }
}

int main ( void ) {
    int arr [3] = {1 ,2 ,3};
    func ( arr , sizeof ( arr ) / sizeof ( arr [0]) ) ;
    // ...
}

// arraysyntax
// parameter sind immer noch pointer!
void func1 ( int arr [ arr_length ] , size_t arr_length ) {
    for ( size_t i = 0; i < arr_length ; i ++) {
        printf ( " % d \ n " , arr [ i ]) ;
    }
}
void func2 ( int arr [3]) { // sizeof(arr) würde immer noch größe des pointers zurückgeben!
    for ( size_t i = 0; i < 3; i ++) {
        printf ( " % d \ n " , arr [ i ]) ;
    }
}
int main ( void ) {
    int arr [3] = {1 ,2 ,3};
    func1 ( arr , sizeof ( arr ) / sizeof ( arr [0]) ) ;
    func2 ( arr ) ; // ...
}

Strings

08de26cfbd3533ced6701cd3286817ef.png

structs

struct Penguin{
    int age;
    char* name;
}

struct Penguin1 { // size: 8
    int id; // 4
    unsigned char age; // 1
    char color; // 1
    // 2 byte padding...
    // 4 + 1 + 1 + 2 = 8
}

struct Penguin2 { // size: 12
    unsigned char age; // 1
    // 3 byte padding...
    int id; // 4
    char color; // 1
    // 3 byte padding...
    // 1 + 3 + 4 + 1 + 3 = 12
}
struct Penguin{
    int age;
    char* name;
}

struct Penguin1 { // size: 8
    int id; // 4
    unsigned char age; // 1
    char color; // 1
    // 2 byte padding...
    // 4 + 1 + 1 + 2 = 8
}

struct Penguin2 { // size: 12
    unsigned char age; // 1
    // 3 byte padding...
    int id; // 4
    char color; // 1
    // 3 byte padding...
    // 1 + 3 + 4 + 1 + 3 = 12
}

eee8effe0e07c53783f9998e2eef149b.png

penguin.age = 0; // type of penguin is struct Penguin
penguin_ptr -> age = 0; // type of penguin_ptr is struct Penguin*
penguin.age = 0; // type of penguin is struct Penguin
penguin_ptr -> age = 0; // type of penguin_ptr is struct Penguin*
// compound literal
struct Penguin penguin1 = { .age = 0, .name = "Tux" };
struct Penguin penguin2 = { .age = 0 }; // impl. penguin2.name = NULL
struct Penguin penguin3 = { 0, "Tux" }; // festgelegte Reihenfolge

// NULL
struct Penguin penguin4 = { 0 };
// compound literal
struct Penguin penguin1 = { .age = 0, .name = "Tux" };
struct Penguin penguin2 = { .age = 0 }; // impl. penguin2.name = NULL
struct Penguin penguin3 = { 0, "Tux" }; // festgelegte Reihenfolge

// NULL
struct Penguin penguin4 = { 0 };

struct als Parameter

struct Penguin {
    char * name ;
    unsigned age ;
};
void print_penguin_name1 ( struct Penguin * penguin ) {
    printf ( " name : % s \ n " , penguin - > name ) ;
}
void print_penguin_name2 ( struct Penguin penguin ) {
    printf ( " name : % s \ n " , penguin . name ) ;
}
int main ( void ) {
    struct Penguin penguin = { " tux " , 5 };
    print_penguin_name1 (& penguin ) ;
    print_penguin_name2 ( penguin ) ;
}
struct Penguin {
    char * name ;
    unsigned age ;
};
void print_penguin_name1 ( struct Penguin * penguin ) {
    printf ( " name : % s \ n " , penguin - > name ) ;
}
void print_penguin_name2 ( struct Penguin penguin ) {
    printf ( " name : % s \ n " , penguin . name ) ;
}
int main ( void ) {
    struct Penguin penguin = { " tux " , 5 };
    print_penguin_name1 (& penguin ) ;
    print_penguin_name2 ( penguin ) ;
}

struct vs. union

struct Penguin {
    struct {
        unsigned height ;
        unsigned age ;
        unsigned id ; // oops
    };
    char id [256]; // compiler error: attribute id already "defined"
};
struct Penguin {
    struct {
        unsigned height ;
        unsigned age ;
        unsigned id ; // oops
    };
    char id [256]; // compiler error: attribute id already "defined"
};

dd42a224ab3f7ad02839923cac3f2915.png
28af3c188c0279dac19227c666beca6f.png

struct mit union

struct Dimension { ... };
struct Shape {
    int shape_kind; // 1 = circle, 2 = rect
    union {
        int circle_radius;
        struct Dimension rect;
    };
};

struct Shape my_circ = { .shape_kind = 1, { .circle_radius = 10 }};
struct Dimension { ... };
struct Shape {
    int shape_kind; // 1 = circle, 2 = rect
    union {
        int circle_radius;
        struct Dimension rect;
    };
};

struct Shape my_circ = { .shape_kind = 1, { .circle_radius = 10 }};

union als Parameter

void f(union Number num){
    // Zugriff auf 'a' mit 'num.a'
}

void g(union Number* num){
    // Zugriff auf 'a' mit 'num->a'
}
void f(union Number num){
    // Zugriff auf 'a' mit 'num.a'
}

void g(union Number* num){
    // Zugriff auf 'a' mit 'num->a'
}

Mehrdimensionale Arrays

unsigned matrix [3][4] = {
    { 1 , 2 , 3 , 4 } , { 5 , 6 , 7 , 8 } , { 9 , 10 , 11 , 12 }
};
for ( size_t i = 0; i < sizeof ( matrix ) / sizeof (* matrix ) ; i ++) {
    for ( size_t j = 0; j < sizeof ( matrix [ i ]) / sizeof (* matrix [ i ]) ; j ++) {
        printf ( " % u " , matrix [ i ][ j ]) ;
    }
}
printf ( " \ n " ) ;

// Auslassen der expliziten Größenangabe der “obersten Ebene”:
unsigned matrix [][4] = {
    { 1 , 2 , 3 , 4 } , { 5 , 6 , 7 , 8 } , { 9 , 10 , 11 , 12 }
};
unsigned matrix [3][4] = {
    { 1 , 2 , 3 , 4 } , { 5 , 6 , 7 , 8 } , { 9 , 10 , 11 , 12 }
};
for ( size_t i = 0; i < sizeof ( matrix ) / sizeof (* matrix ) ; i ++) {
    for ( size_t j = 0; j < sizeof ( matrix [ i ]) / sizeof (* matrix [ i ]) ; j ++) {
        printf ( " % u " , matrix [ i ][ j ]) ;
    }
}
printf ( " \ n " ) ;

// Auslassen der expliziten Größenangabe der “obersten Ebene”:
unsigned matrix [][4] = {
    { 1 , 2 , 3 , 4 } , { 5 , 6 , 7 , 8 } , { 9 , 10 , 11 , 12 }
};

Speicherbereiche

ac75a1a11e3974911e6e0b4608e5a0fe.png

Stack vs. Heap

Speicherverwaltung auf dem Heap

// Beispiel: Allokation auf dem Heap
char* p = malloc(256 * sizeof(char));
if ( p == NULL ) {
    // Behandlung von Fehler bei Speicherallokation
    abort();
}
// ... arbeite mit p
free(p);
// Beispiel: Allokation auf dem Heap
char* p = malloc(256 * sizeof(char));
if ( p == NULL ) {
    // Behandlung von Fehler bei Speicherallokation
    abort();
}
// ... arbeite mit p
free(p);

Effizientes Debugging

printf-Debugging

assert()

Buffer Overflows

Segmentation Fault

Inhärent Unsichere Funktionen

Überprüfung von malloc()

Format String Injection

Memory Leak

Use After Free und Double Free

if (! strlen ( buf ) ) {
    printf ( " You didn ’t enter your name !\ n " ) ;
    return 1;
} else if ( strlen ( buf ) > 20) {
    printf ( " You have a really long name , % s !\ n " , buf ) ;
    free ( buf ) ;
}
// buf gets used after being freed -> use after free
printf ( " Thank you for introducing yourself , % s !\ n " , buf ) ;
// buf gets freed again -> double free
free ( buf ) ;
if (! strlen ( buf ) ) {
    printf ( " You didn ’t enter your name !\ n " ) ;
    return 1;
} else if ( strlen ( buf ) > 20) {
    printf ( " You have a really long name , % s !\ n " , buf ) ;
    free ( buf ) ;
}
// buf gets used after being freed -> use after free
printf ( " Thank you for introducing yourself , % s !\ n " , buf ) ;
// buf gets freed again -> double free
free ( buf ) ;

Undefined Behavior

Vermeidung von Fehlern - Sanitizer

Kommandozeilenargumente in C

#include <stdio.h>

int main(int argc, char** argv){
    for(int i = 0; i < argc; i++){
        printf(";%s;\n", argv[i]);
    }
    return 0;
}
#include <stdio.h>

int main(int argc, char** argv){
    for(int i = 0; i < argc; i++){
        printf(";%s;\n", argv[i]);
    }
    return 0;
}
$ ./myprog.o -o filename -g
;./myprog.o;
;-o;
;filename;
;-g;

$ ./myprog.o -n "Hallo Welt"
;./myprog.o;
;-n;
;Hallo Welt;
$ ./myprog.o -o filename -g
;./myprog.o;
;-o;
;filename;
;-g;

$ ./myprog.o -n "Hallo Welt"
;./myprog.o;
;-n;
;Hallo Welt;

System V Application Binary Interface

Registertypen

myfun:
  push rbx
  mov rbx, [rdi]
  ...
  pop rbx
  ret
myfun:
  push rbx
  mov rbx, [rdi]
  ...
  pop rbx
  ret

Stack-Alignment

myfun:
  push rbx
  mov rbx, [rdi]
  ...
  call do_sth
  add rax, rbx
  pop rbx
  ret
myfun:
  push rbx
  mov rbx, [rdi]
  ...
  call do_sth
  add rax, rbx
  pop rbx
  ret

7820834418aff783a15a561ae8186c43.png

Struct-Layout

Structs als Funktionsparameter

//2 64-Bit Integer (16 Byte)
void func(struct { uint64_t a; uint64_t b; } param);
// a -> rdi, b -> rsi

// 2 32-Bit Integer (8 Byte)
void func(struct { uint32_t a; uint32_t b; } param);
// a -> rdi[31:0], b -> rdi[63:32]

// mehrere zusammengefasste Felder (12 Byte)
void func(struct { uint16_t a; uint16_t b; uint8_t c; } param);
// a -> rdi[15:0], b -> rdi[31:16], c -> rdi[39:32]

// struct größer als 16 Byte (24 Byte)
void func(struct { uint64_t a; uint64_t b; uint64_t c; } param);
// a -> [rsp], b -> [rsp + 8], c -> [rsp + 16]
//2 64-Bit Integer (16 Byte)
void func(struct { uint64_t a; uint64_t b; } param);
// a -> rdi, b -> rsi

// 2 32-Bit Integer (8 Byte)
void func(struct { uint32_t a; uint32_t b; } param);
// a -> rdi[31:0], b -> rdi[63:32]

// mehrere zusammengefasste Felder (12 Byte)
void func(struct { uint16_t a; uint16_t b; uint8_t c; } param);
// a -> rdi[15:0], b -> rdi[31:16], c -> rdi[39:32]

// struct größer als 16 Byte (24 Byte)
void func(struct { uint64_t a; uint64_t b; uint64_t c; } param);
// a -> [rsp], b -> [rsp + 8], c -> [rsp + 16]

Structs als Rückgabewerte

struct ComputeRes { uint64_t a, b, c; };
struct ComputeRes compute(int param);

// verhält sich wie:
struct ComputeRes* compute(struct ComputeRes* retval, int param);
struct ComputeRes { uint64_t a, b, c; };
struct ComputeRes compute(int param);

// verhält sich wie:
struct ComputeRes* compute(struct ComputeRes* retval, int param);

Calling Convention: Zusammenfassung

Fixkommazahlen / Festkommazahlen

Fixkommazahlarithmetik

Fließkommazahlen

Aufbau

Genauerer Aufbau

Datentypen: float und double

Größe Dezimalziffern Abs. Min Abs. Max
float 323232 \approx 77\approx 7 \approx 1.18 \cdot 10^{-38}1.181038\approx 1.18 \cdot 10^{-38} \approx 3.4 \cdot 10^{38}3.41038\approx 3.4 \cdot 10^{38}
double 646464 \approx 1515\approx 15 \approx 10^{-308}10308\approx 10^{-308} \approx 10^{308}10308\approx 10^{308}

Konvertierung zu einer Fließkommazahl und umgekehrt

Addition und Subtraktion von Fließkommazahlen

Multiplikation und Divison von Fließkommazahlen

Probleme bei Genauigkeit

Denormale / Subnormale Zahlen

Null mit Vorzeichen

Unendlich / Infinity

Not A Number / NaN

Weitere Floating Point Formate

Fließ- und Fixkommazahlen: Zusammenfassung

Streaming SIMD Extensions (SSE)

SSE Register

Konstanten

Arithmetik

Vergleiche

cmpFloat :
    ucomiss xmm1 , xmm0
    jp _Lunordered ; xmm0 or xmm1 NaN
    jb _Llesser ; xmm1 < xmm0
    ja _Lgreater ; xmm1 > xmm0
    je _Lequal ; xmm1 == xmm0
cmpFloat :
    ucomiss xmm1 , xmm0
    jp _Lunordered ; xmm0 or xmm1 NaN
    jb _Llesser ; xmm1 < xmm0
    ja _Lgreater ; xmm1 > xmm0
    je _Lequal ; xmm1 == xmm0

Codeeispiel (func \to 1/xfunc1/xfunc \to 1/x)

#include <stdio.h>

extern float func(float x);

int main(int argc, char **argv){
    float res = func(2.0);
    printf("Result: %f\n", res);
    return 0;
}
#include <stdio.h>

extern float func(float x);

int main(int argc, char **argv){
    float res = func(2.0);
    printf("Result: %f\n", res);
    return 0;
}
.intel_syntax noprefix
.global func
.text
func:
    mov r8, 1
    cvtsi2ss xmm1, r8
    divss xmm1, xmm0
    movss xmm0, xmm1
    ret
.intel_syntax noprefix
.global func
.text
func:
    mov r8, 1
    cvtsi2ss xmm1, r8
    divss xmm1, xmm0
    movss xmm0, xmm1
    ret

Erweiterte Calling Convention

   float fn(int, float, char*, float);
// xmm0     edi  xmm0   rsi    xmm1
   float fn(int, float, char*, float);
// xmm0     edi  xmm0   rsi    xmm1
ex:
    mov rdi, 1
    movss xmm0, [rip + .Lconstx]
    mov rs1, 2
    movss xmm1, [rip + .Lconsty]
    call fn
    ...
ex:
    mov rdi, 1
    movss xmm0, [rip + .Lconstx]
    mov rs1, 2
    movss xmm1, [rip + .Lconsty]
    call fn
    ...

SIMD - SSE

SIMD - Single Instruction Multiple Data(stream)

SSE-Instruktionen für SIMD

Integer-Instruktionen

Vektoraddition: 32- und 64-bit

Inkrementieren eines Elements mittels SIMD

; void add_one(int x[4])
add_one:
    mov eax, 1
    movd xmm0, eax
    pshufd xmm0, xmm0, 0x00
    movdqu xmm1, [rdi]
    paddd xmm1, xmm0
    movdqu [rdi], xmm1
    ret
; void add_one(int x[4])
add_one:
    mov eax, 1
    movd xmm0, eax
    pshufd xmm0, xmm0, 0x00
    movdqu xmm1, [rdi]
    paddd xmm1, xmm0
    movdqu [rdi], xmm1
    ret

Gleitkomma-Instruktionen

Addition eines Vektors auf sich selbst mit SIMD

; void add_self(float *, size_t)
; assume that rdi & 3 == 0
add_self:
    movups xmm0, [rdi]
    addps xmm0, xmm0
    movups [rdi], xmm0
    add rdi, 16
    sub rsi, 4
    jnz add_self
    ret
; void add_self(float *, size_t)
; assume that rdi & 3 == 0
add_self:
    movups xmm0, [rdi]
    addps xmm0, xmm0
    movups [rdi], xmm0
    add rdi, 16
    sub rsi, 4
    jnz add_self
    ret

SIMD-Alignment

Aligned Zugriff

SIMD-Stolperfallen

Compiler und Vektorisierung

SIMD-Intrinsics

Codebeispiel - Saxpy

#include <immintrin.h>

void saxpy(long n, float alpha, float *x, float *y){
    __m128 valpha = _mm_load1_ps(&alpha);
    
    // simd
    for(size_t i = 0; i < (n - (n % 4)); i += 4) {
        __m128 vx = _mm_loadu_ps(x + i);
        __m128 vy = _mm_loadu_ps(y + i);
        
        vy = _mm_add_ps(_mm_mul_ps(valpha, vx), vy);
        
        _mm_storeu_ps(y + i, vy);
    }
    
    // remaining elements
    for(size_t i = (n - (n % 4)); i < n; i++) {
        y[i] = alpha * x[i] + y[i];
    }
}
#include <immintrin.h>

void saxpy(long n, float alpha, float *x, float *y){
    __m128 valpha = _mm_load1_ps(&alpha);
    
    // simd
    for(size_t i = 0; i < (n - (n % 4)); i += 4) {
        __m128 vx = _mm_loadu_ps(x + i);
        __m128 vy = _mm_loadu_ps(y + i);
        
        vy = _mm_add_ps(_mm_mul_ps(valpha, vx), vy);
        
        _mm_storeu_ps(y + i, vy);
    }
    
    // remaining elements
    for(size_t i = (n - (n % 4)); i < n; i++) {
        y[i] = alpha * x[i] + y[i];
    }
}

Andere Datentypen

Vor- und Nachteile von Intrinsics

Automatische Vektorisierung

Optimierungen

Optimierungsstufe Beschreibung
-O0 keine Optimierungen
-O1 Optimierungen mit wenig Compile-Zeit
-Og O1 mit Fokus auf debugbaren Code
-O2 alle Optimierungen ohne Space-Speed-Tradeoff
-Os O2 mit Fokus auf minimaler Code-Größe
-O3 alle Optimierungen
-Ofast O3 mit Float-Optimierungen (disregard strict standard compliance)

Optimierung von Berechnungen

int x = a * b * 24;
int y = a * b * c;

// optimized
int tmp = a * b;
int x = tmp * 24;
int y = tmp * c;
int x = a * b * 24;
int y = a * b * c;

// optimized
int tmp = a * b;
int x = tmp * 24;
int y = tmp * c;

Optimierung von Schleifen

for(int i = 0; i < 6; i++){
    arr[i] = 2 * arr[i];
}

// optimized: -floop-unroll-and-jam, ab -O3
for(int i = 0; i < 6; i+= 2){
    arr[i] = 2 * arr[i];
    arr[i + 1] = 2 * arr[i + 1];
}
for(int i = 0; i < 6; i++){
    arr[i] = 2 * arr[i];
}

// optimized: -floop-unroll-and-jam, ab -O3
for(int i = 0; i < 6; i+= 2){
    arr[i] = 2 * arr[i];
    arr[i + 1] = 2 * arr[i + 1];
}
for(int i = 0; i < 6; i++){
    arr[i] = 2 * arr[i];
}

for(int i = 0; i < 6; i++){
    arr2[i] = arr2[i] + 24;
}

// optimized: -floop-unroll-and-jam
for(int i = 0; i < 6; i++){
    arr[i] = 2 * arr[i];
    arr2[i] = arr2[i] + 24;
}
for(int i = 0; i < 6; i++){
    arr[i] = 2 * arr[i];
}

for(int i = 0; i < 6; i++){
    arr2[i] = arr2[i] + 24;
}

// optimized: -floop-unroll-and-jam
for(int i = 0; i < 6; i++){
    arr[i] = 2 * arr[i];
    arr2[i] = arr2[i] + 24;
}
int n;
for(int i = 0; i < x; i++){
    n = sizeof(arr) / sizeof(int);
    arr[i] = 2 * arr[i];
}

// optimized: -fmove-loop-invariants, ab -O1
int n = sizeof(arr) / sizeof(int);
for(int i = 0; i < x; i++){
    arr[i] = 2 * arr[i];
}
int n;
for(int i = 0; i < x; i++){
    n = sizeof(arr) / sizeof(int);
    arr[i] = 2 * arr[i];
}

// optimized: -fmove-loop-invariants, ab -O1
int n = sizeof(arr) / sizeof(int);
for(int i = 0; i < x; i++){
    arr[i] = 2 * arr[i];
}
int sum = 0;
for(int i = 0; i < 6; i++){
    for(int j = 0; j < 9; j++){
        sumt += arr[j][i];
    }
}

// optimized: -floop-interchange, ab -O3
int sum = 0;
for(int j = 0; i < 9; j++){
    for(int i = 0; j < 6; i++){
        sumt += arr[j][i];
    }
}
int sum = 0;
for(int i = 0; i < 6; i++){
    for(int j = 0; j < 9; j++){
        sumt += arr[j][i];
    }
}

// optimized: -floop-interchange, ab -O3
int sum = 0;
for(int j = 0; i < 9; j++){
    for(int i = 0; j < 6; i++){
        sumt += arr[j][i];
    }
}

Optimierung von Funktionsaufrufen

static int square(int x){
    return x*x;
}

for(int i = 0; i < n; i++){
    arr[i] = square(i);
}

// optimized: -finline-functions-called-once, ab -O1 bzw. -finline-functions, ab -O2
for(int i = 0; i < n; i++){
    arr[i] = i * i;
}
static int square(int x){
    return x*x;
}

for(int i = 0; i < n; i++){
    arr[i] = square(i);
}

// optimized: -finline-functions-called-once, ab -O1 bzw. -finline-functions, ab -O2
for(int i = 0; i < n; i++){
    arr[i] = i * i;
}
int fac(int k, unsigned n){
    if(n <= 0) return k;
    return fac(k * n, n - 1);
}

// optimized: -foptimize-sibling-calls, ab -O2
int fac(int k, unsigned n){
    fac:
        if(n <= 0) return k;
        k *=n;
        n--;
        goto fac;
}
int fac(int k, unsigned n){
    if(n <= 0) return k;
    return fac(k * n, n - 1);
}

// optimized: -foptimize-sibling-calls, ab -O2
int fac(int k, unsigned n){
    fac:
        if(n <= 0) return k;
        k *=n;
        n--;
        goto fac;
}

Interprozedurale Optimierungen

Low-Level Optimierungen

Optimierte Funktionen

Builtins

Funktionsattribute (Beispiele)

__attribute__((always_inline))
void addTwo(uint8_t* element){
    *element += 2;
}

__attribute__((noinline))
void addTwo(uint8_t* element){
    *element += 2;
}
__attribute__((always_inline))
void addTwo(uint8_t* element){
    *element += 2;
}

__attribute__((noinline))
void addTwo(uint8_t* element){
    *element += 2;
}
__attribute__((const))
extern uint32_t mulPi(uint32_t n); // n * pi 
__attribute__((const))
extern uint32_t mulPi(uint32_t n); // n * pi 
__attribute__ (( pure ) )
int my_memcmp ( const void * ptr1 , const void * ptr2 , size_t n ) {
    while (! n - -)
        if (* ptr1 ++ != * ptr2 ++)
            return * ptr2 - * ptr1 ;
    return 0;
}
__attribute__ (( pure ) )
int my_memcmp ( const void * ptr1 , const void * ptr2 , size_t n ) {
    while (! n - -)
        if (* ptr1 ++ != * ptr2 ++)
            return * ptr2 - * ptr1 ;
    return 0;
}

Layout von Datenstrukturen

Layout von structs

struct PenguinBad { 	// alignment: 8 (char*)
    char type; 		// offset: 0
    char *name; 	// offset: 8
    uint8_t age; 	// offset: 16
} 			// size (mult of alignment): 24

struct PenguinBad { 	// alignment: 8 (char*)
    char type; 		// offset: 0
    uint8_t age; 	// offset: 1
    char *name; 	// offset: 8
} 			// size (mult of alignment): 16
struct PenguinBad { 	// alignment: 8 (char*)
    char type; 		// offset: 0
    char *name; 	// offset: 8
    uint8_t age; 	// offset: 16
} 			// size (mult of alignment): 24

struct PenguinBad { 	// alignment: 8 (char*)
    char type; 		// offset: 0
    uint8_t age; 	// offset: 1
    char *name; 	// offset: 8
} 			// size (mult of alignment): 16

Pointer-Aliasing

void foo(unsigned* a, int* b){
    ...
}

void foo2(unsigned* restrict a, int* restrict b){
    ...
}
void foo(unsigned* a, int* b){
    ...
}

void foo2(unsigned* restrict a, int* restrict b){
    ...
}

Beispiele für Pointer-Aliasing

// arr und sum zeigen nicht auf gleichen speicher
// compiler kann das aber nicht wissen (char* kann alles aliasen)
void count_a(const char *arr, int *sum){
    while(*arr){
        *sum += *arr++ == 'a';
    }
}

// fixed
void count_a(const char* restrict arr, int *sum){
    while(*arr){
        *sum += *arr++ == 'a';
    }
}
// arr und sum zeigen nicht auf gleichen speicher
// compiler kann das aber nicht wissen (char* kann alles aliasen)
void count_a(const char *arr, int *sum){
    while(*arr){
        *sum += *arr++ == 'a';
    }
}

// fixed
void count_a(const char* restrict arr, int *sum){
    while(*arr){
        *sum += *arr++ == 'a';
    }
}
// arr und sum können keine gültigen aliase sein
// diese zeigen also nicht auf gleiche speicherbereiche
// optimierungen erst ab -o2 oder mit -fstrict-aliasing
void count_a_short(const short arr[4], int *sum){
    for(size_t i = 0; i < 4; i++){
        *sum += arr[i] == 'a';
    }
}
// optimierung:
// sum muss nicht bei jeder iteration in den speicher geschrieben werden
// arr und sum können keine gültigen aliase sein
// diese zeigen also nicht auf gleiche speicherbereiche
// optimierungen erst ab -o2 oder mit -fstrict-aliasing
void count_a_short(const short arr[4], int *sum){
    for(size_t i = 0; i < 4; i++){
        *sum += arr[i] == 'a';
    }
}
// optimierung:
// sum muss nicht bei jeder iteration in den speicher geschrieben werden

Vergleiche mit SIMD

; if(a[i] != b[i]) a[i] = 0;
mov ecx, dword ptr [rsi]
cmp dword ptr [rdi], ecx
je .Lskip
mov byte ptr [rdi] 0
.Lskip:
...
; if(a[i] != b[i]) a[i] = 0;
mov ecx, dword ptr [rsi]
cmp dword ptr [rdi], ecx
je .Lskip
mov byte ptr [rdi] 0
.Lskip:
...

Vergleichsbefehle

Anwendung der Bitmaske

; if (a[i] != b[i])
; a[i] = 0;
; xmm0 = a [i]
movdqa xmm0, xmmword ptr [rdi]

; xmm1 = b[i]
movdqa xmm1, xmmword ptr [rsi]
pcmpeqd xmm1, xmm0 		; create bit mask
pand xmm0, xmm1 		; set a[i] to 0 with logical AND
movdqa xmmword ptr [rdi] , xmm0 ; replace a[i] in memory
; if (a[i] != b[i])
; a[i] = 0;
; xmm0 = a [i]
movdqa xmm0, xmmword ptr [rdi]

; xmm1 = b[i]
movdqa xmm1, xmmword ptr [rsi]
pcmpeqd xmm1, xmm0 		; create bit mask
pand xmm0, xmm1 		; set a[i] to 0 with logical AND
movdqa xmmword ptr [rdi] , xmm0 ; replace a[i] in memory

SIMD mit General Purpose Registern

mov rax, [rdi]
shr rax, 1
mov r8, 0x7F7F7F7F7F7F7F7F
and rax, r8
mov rax, [rdi]
shr rax, 1
mov r8, 0x7F7F7F7F7F7F7F7F
and rax, r8

Wann ist SIMD sinnvoll?

SIMD mit AVX

Erweiterte SSE-Instruktionen in AVX

Neue AVX-Instruktionen

Alignment in AVX

SSE und AVX - Adressierungsschemata

Zeitmessung

real	0m.024s <- Abstand zwischen call und finish
user	0m.016s <- CPU-Zeit im User-Mode
sys 	0m.016s <- CPU-Zeit im Kernel-Mode
real	0m.024s <- Abstand zwischen call und finish
user	0m.016s <- CPU-Zeit im User-Mode
sys 	0m.016s <- CPU-Zeit im Kernel-Mode

Zeitmessung im Code

Messen eines Zeitpunktes

#include <time.h>
...
struct timespec start;
clock_gettime(CLOCK_MONOTONIC, &start) ;
for(int i = 0; i < iterations ; ++i)
    foo();
struct timespec end;
clock_gettime(CLOCK_MONOTONIC, &end);

double time = end.tv_sec - start.tv_sec + 1e-9 * (end.tv_nsec - start.tv_nsec);
double avg_time = time / iterations;
#include <time.h>
...
struct timespec start;
clock_gettime(CLOCK_MONOTONIC, &start) ;
for(int i = 0; i < iterations ; ++i)
    foo();
struct timespec end;
clock_gettime(CLOCK_MONOTONIC, &end);

double time = end.tv_sec - start.tv_sec + 1e-9 * (end.tv_nsec - start.tv_nsec);
double avg_time = time / iterations;

Umgebungsbedingungen

Dokumentation

Zeitmessung - Zusammenfassung

Profiling mit perf


Summary by Flavius Schmidt, ge83pux, 2023.
https://home.in.tum.de/~scfl