Struct mit Arrays durch Arrays initialisieren

Hallo allerseits,

folgendes setting:

Code:
typedef uint32_t arr[3];
typedef arr mat[3];  // uint32_t mat[3][3];

struct mat_struct {
    mat A;
    mat B;
    mat C;
}

typedef struct mat_struct Mat;

void main()
{
    mat zero = {0}, one = {1}, two = {2};  // Drei-Dimensionale Matrix
    Mat Matrix = {zero, one, two};  // Jede Eigenschaft ist eine 3D-Matrix.
}

Letzteres würde ich gerne realisieren. Ich könnte natürlich in einer Anwendung entsprechend "Mat_cpy(Matrix->A, zero);" usw. aufrufen, aber geht es nicht direkt, wie in der ersten Zeile der Main?

Btw. die erwartete Fehlermeldung ist:
Code:
warning: initialization makes integer from pointer without a cast
 
Zuletzt bearbeitet:
mat zero = {0}, one = {1}, two = {2};
Das initializiert nur jeweils das erste Element (der Rest ist gleich 0).

Es gitbt gcc-Erweiterung:
Code:
typedef uint32_t arr[3];                                                                                               
typedef arr mat[3];  // uint32_t mat[3][3];                                                                            
#define ARR_SIZE (sizeof(arr)/sizeof(uint32_t)-1)                                                                      
struct mat_struct {                                                                                                    
    mat A;                                                                                                             
    mat B;                                                                                                             
    mat C;                                                                                                             
};                                                                                                                     
                                                                                                                       
typedef struct mat_struct Mat;                                                                                         
                                                                                                                       
int main(void)                                                                                                         
{                                                                                                                      
                                                                                                                       
    mat zero = {{0}}, one = {{1}}, two = {{2}};  // Drei-Dimensionale Matrix                                           
    Mat Matrix = { {{0}}, {{0}},                                                                                       
        {{[0 ... ARR_SIZE]= 2},                                                                                        
         {[0 ... ARR_SIZE] = 3},                                                                                       
         {[0 ... ARR_SIZE] = 42}                                                                                       
        }                                                                                                              
    };  // Jede Eigenschaft ist eine 3D-Matrix.                                                                        
    for (size_t i = 0; i < sizeof(arr)/sizeof(uint32_t); ++i) {                                                        
        for (size_t j = 0; j < sizeof(mat)/sizeof(arr); ++j) {                                                         
            printf("[%zu][%zu] = %u\n", i,j, Matrix.C[i][j]);                                                          
        }                                                                                                              
    }                                                                                                                  
    return EXIT_SUCCESS;                                                                                               
}
Aber genauso gut könnte man es "zu Fuß", mittels Schleifen lösen.
 
Aktuell mache ich es über zwei Teil-Funktionen. Jede ist für das Befüllen einer "Ebene" zuständig. So z.B. "arr_fill()" und "mat_fill()" die jeweils durch die Anzahl der Arrayelement durch iterieren und entsprechend aufgerufen werden. Ich hatte gehofft, dass eine einfachere Lösung existieren würde. Pointer auf diese Elemente setzen, würde mir gar nicht helfen. Ich würde lieber den Speicher entsprechend kopieren (memcpy), aber damit ist, glaube ich, auch nichts gewonnen.

In meinem speziellen Fall rufe ich 4x die Funktion "arr_fill()" auf, welche 11x "aim=source" setzt. Weißt du, ob memcpy schneller ist? Ich kann das ja mal benchmarken :D
 
Im 1-dim Fall:
Kopiere einen Array mit 11 Elementen in einen anderen. 2 Varianten, Fall 1: for-Schleife "arr1=arr2". Fall2: memcpy(arr1, arr2, 11);
#1 = 40 cycle, #2 = 10 cycle.

Kopiere 4 arrays mit 11 Elementen: #1 ist der naive Weg (nested-loops), #2 memcpy.
#1 = 202, #2 = 5

Kopiere 16 arrays mit 11 Elementen:
#1 = 735, #2 = 8


Kann man irgendwo sehen, wie memcpy arbeitet? Das sind ja schon erhebliche Unterschiede..
 
Kann man irgendwo sehen, wie memcpy arbeitet? Das sind ja schon erhebliche Unterschiede..
gcc ... -S -masm=intel, denn es kann sein, dass statt memcpy Aufruf direkt Code generiert wird.
Ist vermutlich "aligned copy mittels SSE Anweisungen und XMM/YMM-Register" vs. "32-bit Einzelkopie" + "ungünstig verschachtelte Schleife, die für Cachemisses sorgt".

Oder, je nach Komplexität des Testcodes, kann auch sein dass die Variante mit memcpy einfach besser zur Compilierzeit optimiert werden kann ("compile-time evaluation" sozusagen).
 
Ich habe mal die Def. von memcpy rausgesucht

Code:
//Quelle: http://clc-wiki.net/wiki/C_standard_library:string.h:memcpy
[COLOR=#000000][B][I]#[/I][/B][/COLOR][URL="http://clc-wiki.net/wiki/hash_include"][COLOR=black][B][I]include[/I][/B][/COLOR][/URL][COLOR=#008800][I]<[/I][/COLOR][URL="http://clc-wiki.net/wiki/stddef.h"][COLOR=#B06CC8][I]stddef.h[/I][/COLOR][/URL][COLOR=#008800][I]>[/I][/COLOR][COLOR=#888888][I]/*[/I][/COLOR][COLOR=#888888][I] size_t [/I][/COLOR][COLOR=#888888][I]*/[/I][/COLOR][URL="http://clc-wiki.net/wiki/void"][COLOR=#A1A100]void[/COLOR][/URL] [COLOR=#008800]*[/COLOR][URL="http://clc-wiki.net/wiki/memcpy"][COLOR=#B06CC8]memcpy[/COLOR][/URL][COLOR=#008800]([/COLOR][URL="http://clc-wiki.net/wiki/void"][COLOR=#A1A100]void[/COLOR][/URL] [COLOR=#008800]*[/COLOR]dest[COLOR=#008800],[/COLOR] [URL="http://clc-wiki.net/wiki/const"][COLOR=#A1A100]const[/COLOR][/URL] [URL="http://clc-wiki.net/wiki/void"][COLOR=#A1A100]void[/COLOR][/URL] [COLOR=#008800]*[/COLOR]src[COLOR=#008800],[/COLOR] [URL="http://clc-wiki.net/wiki/size_t"][COLOR=#A1A100]size_t[/COLOR][/URL] n[COLOR=#008800])[/COLOR]
[COLOR=#008800]{[/COLOR]
    [URL="http://clc-wiki.net/wiki/char"][COLOR=#A1A100]char[/COLOR][/URL] [COLOR=#008800]*[/COLOR]dp [COLOR=#008800]=[/COLOR] dest[COLOR=#008800];[/COLOR]
    [URL="http://clc-wiki.net/wiki/const"][COLOR=#A1A100]const[/COLOR][/URL] [URL="http://clc-wiki.net/wiki/char"][COLOR=#A1A100]char[/COLOR][/URL] [COLOR=#008800]*[/COLOR]sp [COLOR=#008800]=[/COLOR] src[COLOR=#008800];[/COLOR]
    [URL="http://clc-wiki.net/wiki/while"][COLOR=#008800]while[/COLOR][/URL] [COLOR=#008800]([/COLOR]n[COLOR=#008800]-[/COLOR][COLOR=#008800]-[/COLOR][COLOR=#008800])[/COLOR]
        [COLOR=#008800]*[/COLOR]dp[COLOR=#008800]+[/COLOR][COLOR=#008800]+[/COLOR] [COLOR=#008800]=[/COLOR] [COLOR=#008800]*[/COLOR]sp[COLOR=#008800]+[/COLOR][COLOR=#008800]+[/COLOR][COLOR=#008800];[/COLOR]
    [URL="http://clc-wiki.net/wiki/return"][COLOR=#008800]return[/COLOR][/URL] dest[COLOR=#008800];[/COLOR]
[COLOR=#008800]}[/COLOR]

im Vergleich:
Code:
#typedef uint32_t arr[11];
void arr_cpy(arr A, arr B){ // A <- B.
 	uint8_t i;
	for(i=0; i < 11; i++)
	{
		A[i] = B[i];
	}
}

Ich sehe, dass beide Schleifen (wenigstens theoretisch) identisch sind. Der Unterschied liegt praktisch darin, dass memcpy sich lokale Kopien der Anfangspointer erzeugt. Ich bin mir nur noch nicht ganz sicher, wie "*dp++" operiert, ob das byte-weise durchgeht, und daher "n" die Anzahl der Bytes (also 11*32/8=44) sein sollte, oder ob die Angabe "n=11" genügt. Laut meines Tests (anschließende Evaluierung) genügt "11". Anhand des Codes sehe ich jedenfalls, dass tatsächlich die Inhalte kopiert werden und nicht die Adressen.

Ich gehe mal davon aus, dass memcmp ähnlich optimal funktioniert und jeden naiven Vergleich, wie z.B.
Code:
uint8_t n=11; 
while(n--) if(A[n] != B[n]) return 0; 
return 1;
um Längen übertrifft..

Eigentlich müsste ich ja "16*11*4" als "n" übergeben, aber übergebe ich nur "16" ist die Kopie dennoch korrekt. Kannst du mir das erklären?




Übrigens ist es auch sehr interessant folgendes Ergebnis bei 1k Durchläufen zu erhalten:
Funktion vs. memcpy
Code:
arr_cpy(arr C, arr A){ //C<-A
    memcpy(C,A,43); //43 genügen
};

vs
Code:
memcpy(C,A,43);

arr_cpy benötigt im Schnitt 63 cycles, memcpy konstant 33. D.h. ein Funktionsaufruf, der selber memcpy nutzt, ist ein Geschwindigkeitsverlust. Hast du dafür eine Erklärung?

Testvektor:
Code:
uint32_t A[11] = {0xc1132657, 0x034d505e, 0x1fe97257, 0xa01702ae, 0xc20e847d, 0x3ec4cc5a, 0xcce0a723, 0xec61ef0f, 0x3b3f2cba, 0x832e34e3, 0x45de1};
 
Zuletzt bearbeitet:
Ich habe mal die Def. von memcpy rausgesucht
*hust*
nochmal: gcc -S und dann den Code schauen.
Denn gcc hat noch builtins:
Code:
#define memcpy(dest, src, len) \
  ((__ssp_bos0 (dest) != (size_t) -1)                                   \
   ? __builtin___memcpy_chk (dest, src, len, __ssp_bos0 (dest))         \
   : __memcpy_ichk (dest, src, len))
static inline __attribute__((__always_inline__)) void *
__memcpy_ichk (void *__restrict__ __dest, const void *__restrict__ __src,
               size_t __len)
{
  return __builtin___memcpy_chk (__dest, __src, __len, __ssp_bos0 (__dest));
}
es *kann* memcpy benutzen, *kann* aber auch genauso gut __builtin_memcpy generieren.
Sowas z.B sehe ich für:
Code:
 Mat Matrix = { {{0}}, {{0}},                                                                                       
        {{[0 ... ARR_SIZE]= 2},                                                                                        
         {[0 ... ARR_SIZE] = 3},                                                                                       
         {[0 ... ARR_SIZE] = 42}                                                                                       
        }                                                                                                              
    };  // Jede Eigenschaft ist eine 3D-Matrix.                                                                        
    memcpy(Matrix.B, Matrix.C, sizeof(mat));
Code:
 mov     rdi, rsp
        mov     DWORD PTR [rsp+104], 42
        mov     r12, rsp
        rep stosq
        mov     DWORD PTR [rsp+72], 2
        mov     DWORD PTR [rsp+76], 2
        mov     DWORD PTR [rsp+80], 2
        mov     DWORD PTR [rsp+84], 3
        mov     DWORD PTR [rsp+88], 3
        mov     DWORD PTR [rsp+92], 3
        mov     DWORD PTR [rsp+96], 42
        mov     DWORD PTR [rsp+100], 42



Ich sehe, dass beide Schleifen (wenigstens theoretisch) identisch sind. Der Unterschied liegt praktisch darin, dass memcpy sich lokale Kopien der Anfangspointer erzeugt. Ich bin mir nur noch nicht ganz sicher, wie "*dp++" operiert, ob das byte-weise durchgeht, und daher "n" die Anzahl der Bytes (also 11*32/8=44) sein sollte, oder ob die Angabe "n=11" genügt.
Da muss man nicht raten:
man memcpy
> DESCRIPTION
> The memcpy() function copies len bytes from string src to string dst

Eigentlich müsste ich ja "16*11*4" als "n" übergeben, aber übergebe ich nur "16" ist die Kopie dennoch korrekt. Kannst du mir das erklären?
Genügt für memcpy oder Deinen Code? Zusammenhängender (und idealerweise compilierbarer) Code wäre schon hilfreich.
 
Die Größe, die ich an memcpy übergeben muss, hat sich nun geklärt.

Der nachfolgende Code sollte kompilierbar sein. Der Cyclecounter müsste abgestimmt werden, ich habe einen auf dem Cortex M4. Als Beispiel der Code am Ende
Code:
#include <string.h>
#include <stdint.h>

typedef uint32_t arr[11];

void arr_cpy(arr A, arr B)
{ // A <- B.
     uint8_t i=11;
    while(i--)
    {
        A[i] = B[i];
    }
}
void arr2_cpy(GF_p_t A, GF_p_t B)
{ // A <- B.
     memcpy(A,B,43);
}
void main(){
    arr       A = {0xc1132657, 0x034d505e, 0x1fe97257, 0xa01702ae, 0xc20e847d, 0x3ec4cc5a, 0xcce0a723, 0xec61ef0f, 0x3b3f2cba, 0x832e34e3, 0x45de1},
                C = {0}, D= {0}, E={0};
    // start cycle count
    arr_cpy(C,A);
    memcpy(D,A,43);

    arr2cpy(E,A);
    // end cycle count
    // print cycle count for each op.
}

output
Code:
arr_cpy: 46
memcpy: 33
arr2_cpy: 63

Edit..dieser Editor verträgt sich eindeutig nicht mit Chrome..

Cycle Counter:
Code:
#ifndef TIMER
	#define TIMER
	
	#define start_timer()    *((volatile uint32_t*)0xE0001000) = 0x40000001  // Enable CYCCNT register
	#define stop_timer()   *((volatile uint32_t*)0xE0001000) = 0x40000000  // Disable CYCCNT register
	#define get_timer()   *((volatile uint32_t*)0xE0001004)               // Get value from CYCCNT register
	
	/***********
	* How to use:
	*		uint32_t it1, it2; 		// start and stop flag                                             
			
			start_timer();			// start the timer.
			it1 = get_timer();		// store current cycle-count in a local
			
			// do something
			
			it2 = get_timer() - it1; 	// Derive the cycle-count difference
			stop_timer();				// If timer is not needed any more, stop
	
	print_int(it2);					// Display the difference
	****/
#endif

Offenbar ist es ratsamer "memcpy" direkt anzuwenden und nicht in eine weitere Funktion auszulagern. Auf der anderen Seite, wäre dann arr_cpy effizienter als arr2_cpy. Compiliert wird übrigens mit -Ofast und vielen anderen Optionen, die die Architektur der Hardware beschreiben und bestimmte Optionen vornehmen.

Code:
arm-none-eabi-gcc -c -Ofast       -D TRADE -D MOD_P -D MONTI -finline-functions -finline-limit=100 -I./libopencm3/include -fno-common  -mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -MD -DSTM32F4 -ggdb3


EDIT
Hier noch ein weiterer Versuch mehr Performance rauszuholen.
Code:
#define pCPY(x,y) (__builtin_memcpy(x,y,44))
void arr3_cpy(A,B)
{
    pCPY(A,B);
}
>>>cycles: 60
 
Zuletzt bearbeitet:
arr_cpy benötigt im Schnitt 63 cycles, memcpy konstant 33. D.h. ein Funktionsaufruf, der selber memcpy nutzt, ist ein Geschwindigkeitsverlust. Hast du dafür eine Erklärung?

Wenn so ein grober Unfug nicht wegoptimiert wird, hast du ja die ganzen Operationen bei nem Funktionsaufruf...Stichwort Stack/Heap etc. Das ist eben ein Overhead.
 
Offenbar ist es ratsamer "memcpy" direkt anzuwenden und nicht in eine weitere Funktion auszulagern. Auf der anderen Seite, wäre dann arr_cpy effizienter als arr2_cpy. Compiliert wird übrigens mit -Ofast und vielen anderen Optionen, die die Architektur der Hardware beschreiben und bestimmte Optionen vornehmen.

Code:
arm-none-eabi-gcc -c -Ofast       -D TRADE -D MOD_P -D MONTI -finline-functions -finline-limit=100 -I./libopencm3/include -fno-common  -mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -MD -DSTM32F4 -ggdb3
Wenn ich den Code schon mit -O2 kompiliere, bekomme ich:
Code:
arr_cpy:
.LFB0:
        .cfi_startproc
        mov     eax, 10
        .p2align 4,,10
        .p2align 3
.L2:
        movzx   edx, al
        sub     eax, 1
        mov     ecx, DWORD PTR [rsi+rdx*4]
        cmp     al, -1
        mov     DWORD PTR [rdi+rdx*4], ecx
        jne     .L2
        rep ret
...
arr2cpy:
.LFB1:
        .cfi_startproc
        mov     rax, QWORD PTR [rsi]
        mov     QWORD PTR [rdi], rax
        mov     rax, QWORD PTR [rsi+8]
        mov     QWORD PTR [rdi+8], rax
        mov     rax, QWORD PTR [rsi+16]
        mov     QWORD PTR [rdi+16], rax
        mov     rax, QWORD PTR [rsi+24]
        mov     QWORD PTR [rdi+24], rax
        mov     rax, QWORD PTR [rsi+32]
        mov     QWORD PTR [rdi+32], rax
        movzx   eax, WORD PTR [rsi+40]
        mov     WORD PTR [rdi+40], ax
        movzx   eax, BYTE PTR [rsi+42]
        mov     BYTE PTR [rdi+42], al
        ret
...
main:
.LFB2:
        .cfi_startproc
        rep ret
        .cfi_endproc
.LFE2:

Ja, _leere_ main. DCE - Dead Code Elimination.
Timernutzung ändern nicht viel daran:
Code:
 struct timespec tmp_time = timer_start();                                                                          
    arr_cpy(C,A);                                                                                                      
    printf("arr_cpy %zu \n", timer_end(tmp_time));
Code:
call    clock_gettime
        mov     rsi, QWORD PTR [rsp+8]
        mov     edi, OFFSET FLAT:.LC4
        xor     eax, eax
        sub     rsi, rbx
        call    printf
        mov     rsi, rsp
        mov     edi, 15
        call    clock_gettime
        mov     rsi, rsp
        mov     edi, 15
        mov     rbx, QWORD PTR [rsp+8]
        call    clock_gettime
        mov     rsi, QWORD PTR [rsp+8]
        mov     edi, OFFSET FLAT:.LC5
        xor     eax, eax
        sub     rsi, rbx
        call    printf
Daher empfehle ich auch dringendst die Asm-Ausgabe sich anzuschauen, um zu sehen, was in Wirklichkeit gemessen wird ;)
 
Die ASM ist gewaltig groß..
Code:
arm-none-eabi-gcc -S -Ofast -finline-functions -finline-limit=100 -I./libopencm3/include -fno-common  -mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -MD -DSTM32F4 -ggdb3 -o funktion.txt -c test/funktion.c
Anhang anzeigen 4338



funktion.c
Code:
#include <string.h>
#include <stdint.h>

typedef uint32_t arr[11];

void arr_cpy(arr A, arr B);
void arr2_cpy(arr A, arr B);

void main(){
    arr       A = {0xc1132657, 0x034d505e, 0x1fe97257, 0xa01702ae, 0xc20e847d, 0x3ec4cc5a, 0xcce0a723, 0xec61ef0f, 0x3b3f2cba, 0x832e34e3, 0x45de1},
                C = {0}, D= {0}, E={0};
    // start cycle count
    arr_cpy(C,A);
    memcpy(D,A,43);

    arr2_cpy(E,A);
    // end cycle count
    // print cycle count for each op.
}

void arr_cpy(arr A, arr B)
{ // A <- B.
     uint8_t i=11;
    while(i--)
    {
        A[i] = B[i];
    }
}
void arr2_cpy(arr A, arr B)
{ // A <- B.
     memcpy(A,B,43);
}

Ohne die ganzen Optionen etwas schmaler:
Code:
	.cpu arm7tdmi
	.eabi_attribute 23, 1
	.eabi_attribute 24, 1
	.eabi_attribute 25, 1
	.eabi_attribute 26, 1
	.eabi_attribute 30, 2
	.eabi_attribute 34, 0
	.eabi_attribute 18, 4
	.file	"funktion.c"
	.section	.text.startup,"ax",%progbits
	.align	2
	.global	main
	.syntax unified
	.arm
	.fpu softvfp
	.type	main, %function
main:
	@ Function supports interworking.
	@ args = 0, pretend = 0, frame = 0
	@ frame_needed = 0, uses_anonymous_args = 0
	@ link register save eliminated.
	bx	lr
	.size	main, .-main
	.text
	.align	2
	.global	arr_cpy
	.syntax unified
	.arm
	.fpu softvfp
	.type	arr_cpy, %function
arr_cpy:
	@ Function supports interworking.
	@ args = 0, pretend = 0, frame = 0
	@ frame_needed = 0, uses_anonymous_args = 0
	@ link register save eliminated.
	ldr	r3, [r1, #40]
	str	r3, [r0, #40]
	ldr	r3, [r1, #36]
	str	r3, [r0, #36]
	ldr	r3, [r1, #32]
	str	r3, [r0, #32]
	ldr	r3, [r1, #28]
	str	r3, [r0, #28]
	ldr	r3, [r1, #24]
	str	r3, [r0, #24]
	ldr	r3, [r1, #20]
	str	r3, [r0, #20]
	ldr	r3, [r1, #16]
	str	r3, [r0, #16]
	ldr	r3, [r1, #12]
	str	r3, [r0, #12]
	ldr	r3, [r1, #8]
	str	r3, [r0, #8]
	ldr	r3, [r1, #4]
	str	r3, [r0, #4]
	ldr	r3, [r1]
	str	r3, [r0]
	bx	lr
	.size	arr_cpy, .-arr_cpy
	.align	2
	.global	arr2_cpy
	.syntax unified
	.arm
	.fpu softvfp
	.type	arr2_cpy, %function
arr2_cpy:
	@ Function supports interworking.
	@ args = 0, pretend = 0, frame = 0
	@ frame_needed = 0, uses_anonymous_args = 0
	push	{r4, lr}
	mov	r2, #43
	bl	memcpy
	pop	{r4, lr}
	bx	lr
	.size	arr2_cpy, .-arr2_cpy
	.ident	"GCC: (GNU Tools for ARM Embedded Processors 6-2017-q1-update) 6.3.1 20170215 (release) [ARM/embedded-6-branch revision 245512]"
 
Zuletzt bearbeitet:
Die ASM ist gewaltig groß..
Die interessanten Dinge sind zusammenhängen und befinden sich ganz am Anfang. Und ich sehe da ehrlich gesagt nicht viele prinzipielle Unterschiede zum generierten x86 Code:
Code:
arr_cpy:
.LFB1:
	.loc 1 22 0
	.cfi_startproc
	@ args = 0, pretend = 0, frame = 0
	@ frame_needed = 0, uses_anonymous_args = 0
	@ link register save eliminated.
.LVL0:
	.loc 1 26 0
	ldr	r3, [r1, #40]
	str	r3, [r0, #40]
.LVL1:
	ldr	r3, [r1, #36]
	str	r3, [r0, #36]
.LVL2:
	ldr	r3, [r1, #32]
	str	r3, [r0, #32]
.LVL3:
	ldr	r3, [r1, #28]
	str	r3, [r0, #28]
.LVL4:
	ldr	r3, [r1, #24]
	str	r3, [r0, #24]
.LVL5:
	ldr	r3, [r1, #20]
	str	r3, [r0, #20]
.LVL6:
	ldr	r3, [r1, #16]
	str	r3, [r0, #16]
.LVL7:
	ldr	r3, [r1, #12]
	str	r3, [r0, #12]
.LVL8:
	ldr	r3, [r1, #8]
	str	r3, [r0, #8]
.LVL9:
	ldr	r3, [r1, #4]
	str	r3, [r0, #4]
.LVL10:
	ldr	r3, [r1]
	str	r3, [r0]
.LVL11:
	bx	lr
	.cfi_endproc
Code:
arr2_cpy:
.LFB2:
	.loc 1 30 0
	.cfi_startproc
	@ args = 0, pretend = 0, frame = 0
	@ frame_needed = 0, uses_anonymous_args = 0
	@ link register save eliminated.
.LVL12:
	push	{r4, r5, r6}
	.cfi_def_cfa_offset 12
	.cfi_offset 4, -12
	.cfi_offset 5, -8
	.cfi_offset 6, -4
	.loc 1 31 0
	add	r3, r1, #32
.LVL13:
.L4:
	ldr	r5, [r1]	@ unaligned
	ldr	r4, [r1, #4]	@ unaligned
	ldr	r2, [r1, #8]	@ unaligned
	ldr	r6, [r1, #12]	@ unaligned
	str	r6, [r0, #12]	@ unaligned
	adds	r1, r1, #16
	cmp	r1, r3
	str	r5, [r0]	@ unaligned
	str	r4, [r0, #4]	@ unaligned
	str	r2, [r0, #8]	@ unaligned
	add	r0, r0, #16
	bne	.L4
	ldr	r3, [r1]	@ unaligned
.LVL14:
	ldr	r2, [r1, #4]	@ unaligned
	str	r2, [r0, #4]	@ unaligned
	str	r3, [r0]	@ unaligned
	ldrh	r3, [r1, #8]	@ unaligned
	ldrb	r2, [r1, #10]	@ zero_extendqisi2
	strb	r2, [r0, #10]
	strh	r3, [r0, #8]	@ unaligned
	.loc 1 32 0
	pop	{r4, r5, r6}
	.cfi_restore 6
	.cfi_restore 5
	.cfi_restore 4
	.cfi_def_cfa_offset 0
	bx	lr
	.cfi_endproc
Und 'ne leere main:
Code:
main:
.LFB0:
	.file 1 "test/funktion.c"
	.loc 1 9 0
	.cfi_startproc
	@ args = 0, pretend = 0, frame = 0
	@ frame_needed = 0, uses_anonymous_args = 0
	@ link register save eliminated.
	bx	lr
	.cfi_endproc
 
Ich habe da jetzt nochmal "#define arr3_cpy(A,B) memcpy(A,B,44)" hinzugefügt und komme damit immerhin auf stolze "38" cycles, anstelle von "30" bei einem direkten Aufruf mit gleicher Identität. Ich verstehe nur nicht, wie das zu erklären ist.

Das mit der leeren Main kann ich auch nicht erklären. In dem präsentierten Beispiel steht ja was in der Main.

Mit gleicher Zeile konnte ich einen Vergleich verbessern, anstelle jeden Eintrag auf Gleichheit zu überprüfen, habe ich entsprechend arr_eq(A,B) als memcmp(A,B,44)==0 definiert. Der Prozessor braucht nun bei Gleichheit nur noch einen Cycle, anstelle von 196. "for(i=0; i<11; i++ ) if( A != B) return 0; return 1" war der Code praktisch vorher.

Offenbar ist "theoretisch effektiv" programmieren und effektiv programmieren ein unterschiedliches paar Schuhe.

Btw. ich hatte gedacht, dass ein Mikrocontroller, der Multiplikation von 32-Bit in Hardware programmiert hat, auch einen 32-Bit Vergleich in einem Cycle schafft und dass der arm-Compiler entsprechend das ganze übersetzt. Offenbar muss man hier wirklich auf die kleinen Details achten.


Kurzes "Logbuch":
Die rechte Seite stellt immer die "naive Methode" dar. Unter dieser ist eine "Nested-Loop" zu verstehen, die jedes Element (im worst-case) vergleicht. Verglichen werden nur die Worst-case Scenarien.

Code:
typedef uint32_t arr[11];
typedef arr arr_4[4];
typedef arr arr_16[16];

#define arr_4_eq(A,B) memcmp(A,B,4*44)==0
>> cycles: 1 vs. 755

#define arr_16_eq(A,B) memcmp(A,B,16*44)==0
>> cycles: 1 vs 2944

#define arr_4_cpy(A,B) memcpy(A,B,4*44)
>> cycles:  vs 204

#define arr_16_cpy(A,B) memcpy(A,B,16*44)
>> cycles: 586 vs 744


Bitte zeigt mir mögliche Probleme auf, die ich derzeit nicht berücksichtige oder ansage. Speziell bei memcmp/memcpy.


Neues Compiler-Problem
Ich bin gerade auf ein "Problem" gestoßen. Wenn ich in einer Header-Datei obige Definitionen speichere und das Modul (Funktionen und Header ohne Main) in eine Bibliothek umwandle, werden mir vom Compiler beim Laden dieser Bibliothek Fehler angezeigt, dass die anderen Funktionen nicht existieren, trotzdem die *.h eingebunden wird.

Gleicher Compile-Aufruf wie vorher, sagt mir nun, dass einige Funktionen, aus einer anderen *.c, die verlinkt wird, " undefined reference to" sind. Der Fehler trat auf, als ich die Funktionsdefinitionen gegen #define ausgetauscht habe. Mein Vorgehen dazu:
Auskommentieren der Definition in der *.c, auskommentieren der Deklaration in der *.h. Am Anfang der *.h (nachdem ich alle *.h includiere) stehen nun die Zeilen mit #define.
 
Zuletzt bearbeitet:
Ich habe da jetzt nochmal "#define arr3_cpy(A,B) memcpy(A,B,44)" hinzugefügt und komme damit immerhin auf stolze "38" cycles, anstelle von "30" bei einem direkten Aufruf mit gleicher Identität. Ich verstehe nur nicht, wie das zu erklären ist.

Das mit der leeren Main kann ich auch nicht erklären. In dem präsentierten Beispiel steht ja was in der Main.
Die "leere Main" kommt daher, dass die Werte sonst nirgendwo gebraucht - und deshalb gar nicht erst berechnet werden (dead code elimination bei der Optimierung).
Bzw.
c99 Standard hat gesagt.:
In the abstract machine, all expressions are evaluated as specified by the semantics.An
actual implementation need not evaluate part of an expression if it can deduce that its
value is not used
and that no needed side effects are produced (including any caused by
calling a function or accessing a volatile object).

Eine anschließende Ausgabe der Matrix (oder Weiternutzung in einer Berechnung, die dann aus- oder zurückgegeben wird) wäre hier nötig. Ebenso sind die gemessenen Timings zu beweifeln (siehe 1 vs 755) sofern man "zu Testzwecken" die Matrizen vorinitialisiert. Daher sollte man nicht vergessen, was "die Wurzel des Bösen ist" und Optimierungen samt Benchmarks erst später, mit realen Daten und Datenfluss, ansetzen.

Ich bin gerade auf ein "Problem" gestoßen. Wenn ich in einer Header-Datei obige Definitionen speichere und das Modul (Funktionen und Header ohne Main) in eine Bibliothek umwandle, werden mir vom Compiler beim Laden dieser Bibliothek Fehler angezeigt, dass die anderen Funktionen nicht existieren, trotzdem die *.h eingebunden wird.

Gleicher Compile-Aufruf wie vorher, sagt mir nun, dass einige Funktionen, aus einer anderen *.c, die verlinkt wird, " undefined reference to" sind. Der Fehler trat auf, als ich die Funktionsdefinitionen gegen #define ausgetauscht habe. Mein Vorgehen dazu:
Auskommentieren der Definition in der *.c, auskommentieren der Deklaration in der *.h. Am Anfang der *.h (nachdem ich alle *.h includiere) stehen nun die Zeilen mit #define.
Ich tippe darauf, dass die #define-Macros nicht überall "sichtbar" sind. Incldue-Guard zum Headerfile hinzufügen und überall, wo die Defs benutzt werden includen.
 
Mit dem Include-Guard gehe ich genauso vor und binde in jeder Datei, die eine Funktion davon braucht entsprechend die Header ein. Ich habe nun die Files explizit gelinkt und nicht mehr gesagt, dass er die Lib laden soll. So lange es kompilierbar bleibt, genügt es erstmal. Am Ende ist es auch egal, in wie viele Libs das Projekt unterteilt wird.


Nun ein (hoffentlich kompilierbares) Minimalbeispiel mit einem heute aufgetretenen Fehler.

Zuerst der Testaufbau
Code:
#include <string.h>
#include <stdio.h>

#include <stdint.h>
#define TEST 1
typedef unit32_t arr[11];

#define arr_cpy(A,B) memcpy(A,B,44)
#define arr_eq(A,B) memcmp(A,B,44)==0

void main()
{
    arr A = {0xc1132657, 0x034d505e, 0x1fe97257, 0xa01702ae, 0xc20e847d, 0x3ec4cc5a, 0xcce0a723, 0xec61ef0f, 0x3b3f2cba, 0x832e34e3, 0x45de1},
        B = {0xc1132657, 0x034d505e, 0x1fe97257, 0xa01702ae, 0xc20e847d, 0x3ec4cc5a, 0xcce0a723, 0xec61ef0f, 0x3b3f2cba, 0x832e34e3, 0x45de0},
        C = {0},
        D = {0};

    uint32_t t=0, i = TEST;

    while(i--){
        arr_cpy(A,C);
        memcpy(B, D,44);
        t+= arr_eq(C,D);
    }
    printf("t=%u", t);}
}

Meine Ausgabe ist: t=1000, obwohl B im letzten Bit zu A verschieden ist. Erwartet hätte ich 0. Übrigens sagt mir mein Countermesser, dass beide Funktionen nur einen Cycle benötigen, um den Datensatz zu kopieren. Tausche ich die #define arr_cpy gegen folgende Funktion aus, sagt er mir "cycles(arr_cpy)=46, cycles(memcpy)=30". Davon aber mal abgesehen. Viel interessanter ist, dass mir "t=1000" ausgegeben wird.

Code:
void arr_cpy(arr A, arr B)
{ //A<- B
    for(uint8_t i=0; i<11; i++)
    {
        A[i] = B[i];
    }
}


Noch eine kleine Notiz:
Wenn ich ein ein Polynom in allgemeiner Summenschreibweise notiere "SUM a_k x^k" dann ist a_k = A[k] und x=2^32. Die Bitfolge wird also alle 32-Bits geteilt und entsprechend in den Eintrag verschoben. Möchte ich also A<B wissen, muss ich für k=10: A[k] < B[k] ? return 1 : k-=1, repeat; praktisch umsetzen. memcmp geht da allerdings anders vor. Auch das reverse-memcmp gibt mir falsche Ergebnisse aus.
 
Zuletzt bearbeitet:
Code:
#include <string.h>
#include <stdio.h>

#include <stdint.h>
#define TEST 1
typedef unit32_t arr[11];

#define arr_cpy(A,B) memcpy(A,B,44)
#define arr_eq(A,B) memcmp(A,B,44)==0

void main()
{
    arr A = {0xc1132657, 0x034d505e, 0x1fe97257, 0xa01702ae, 0xc20e847d, 0x3ec4cc5a, 0xcce0a723, 0xec61ef0f, 0x3b3f2cba, 0x832e34e3, 0x45de1},
        B = {0xc1132657, 0x034d505e, 0x1fe97257, 0xa01702ae, 0xc20e847d, 0x3ec4cc5a, 0xcce0a723, 0xec61ef0f, 0x3b3f2cba, 0x832e34e3, 0x45de0},
        C = {0},
        D = {0};

    uint32_t t=0, i = TEST;

    while(i--){
        arr_cpy(A,C);
        memcpy(B, D,44);
        t+= arr_eq(C,D);
    }
    printf("t=%u", t);}
}

Meine Ausgabe ist: t=1000, obwohl B im letzten Bit zu A verschieden ist. Erwartet hätte ich 0. Übrigens sagt mir mein Countermesser, dass beide Funktionen nur einen Cycle benötigen, um den Datensatz zu kopieren. Tausche ich die #define arr_cpy gegen folgende Funktion aus, sagt er mir "cycles(arr_cpy)=46, cycles(memcpy)=30". Davon aber mal abgesehen. Viel interessanter ist, dass mir "t=1000" ausgegeben wird.
B ist zwar zu A verschieden, aber verglichen werden hier C und D (die == 0 sind) ;)
Vielleicht war das so gemeint?
Code:
while(i--){                                                                                                    
            arr_cpy(C,A);                                                                                      
            memcpy(D, B,44);                                                                                   
            t+= arr_eq(C,D);                                                                                   
          }
 
:wall:

ehm..ja :thumb_up: ärgerlich sowas :D

Ich denke, falls ich bei dem anderen ein wenig Input brauche, eröffne ich dafür einen neuen Thread (inverting memcmp).

Viele Grüße und vielen Dank für die Hilfe!
 
Zurück
Oben