Probleme d'indirection sur structures

Probleme d'indirection sur structures - C - Programmation

Marsh Posté le 03-02-2006 à 11:47:26    

Bonjour, :hello:  
 
tout part d'un bon gros programme en C, à base de plusieurs structures qui s'entremelent (j'aurais preferé le faire en objet, mais je n'avais pas le choix du langage...).
Une structure A posséde un champ qui est un tableau de structure B. Mon premier reflexe a alors été de créer un tableau de pointeurs sur struct B dans A (ce qui donnait struct B **tab_b).
On pouvait alors accéder aux champs de B avec A->B[i]->champ
Ensuite, relisant mon code, et suite à plusieurs discussions, j'ai décidé qu'il était inutile de surcharger la mémoire avec un tableau de pointeurs, mais plutot mettre  
directement un tableau de structures... je me suis dit qu'avec une indirection en moins, cela accelererait le code. Donc c'est parti, maintenant j'ai struct B *tab_b dans A,
et j'y accède avec A->B[i].champ (on modifiant bien evidemment la façon d'allouer la mémoire et tout ce qui va avece pour que cette méthode fonctionne)
Et là, je suis etonné, mais il semble que cela ralentisse l'execution. (le programme fonctionne parfaitement, mes tests sont OK, tout va bien)
 
Qu'en pensez vous ? Avant tout, cette méthode de gestion des tableaux de structure est elle correcte, ou vaut il mieux garder des tableau de pointeurs lorsque les deux méthodes sont possibles (plus propre ?)
Moi je pense que l'optimiseur du compilo se demerdait mieux avec un tableau de pointeur. Je pense aussi qu'avec cette modif, il a plus de mémoire  
à manipuler pour réallouer... Ou alors, c'est ma methode de mesure du temps d'execution qui n'est pas correcte.
 
Avant toute réponse j'entend bien que certains cas ne sont exclusivements possibles qu'avec l'une ou l'autre méthode ; dans mon cas, je ne parle que des cas de  
figures pour lesquelles il est possible de faire les deux (soit un tableau de pointeurs sur structure, soit un tableau de structure)
 
Merci pour ceux qui prendront le temps ... :-)

Reply

Marsh Posté le 03-02-2006 à 11:47:26   

Reply

Marsh Posté le 03-02-2006 à 11:56:51    

aloue un tableau d'objets plutot qu'un tableau de pointeurs sur objets si tu n'en a pas besoin, c'est plus simple, plus sur et certainement plus efficace (memoire moins fragementée, acces avec une indirection en moins...), et bien sur pas d'allocation dynamique si la taille est connue à la compilation


Message édité par skelter le 03-02-2006 à 11:57:13
Reply

Marsh Posté le 03-02-2006 à 11:58:47    

vérifie que tu fasses pas des recopies de A, peut etre ?

Reply

Marsh Posté le 03-02-2006 à 12:01:14    

chrisbk a écrit :

vérifie que tu fasses pas des recopies de A, peut etre ?


 
Désolé comprend pas .... :heink:  :sweat:

Reply

Marsh Posté le 03-02-2006 à 12:04:47    

ah merde, non, rien [:petrus75]

Reply

Marsh Posté le 03-02-2006 à 15:31:28    

Bon je viens de reessayer avec la version d'avant et bien y a bien une difference de vitesse d'execution ... donc comprend pas, comme si le '.' est moins bien géré que la '->' ...

Reply

Marsh Posté le 03-02-2006 à 15:44:17    

'->' est juste un sucre syntaxique, p-> est strictement équivalent à (*p).
 
tu peux montrer ton code de test ou tu compare les 2 méthodes ?

Reply

Marsh Posté le 03-02-2006 à 15:57:35    

ba en fait non ... :/  
C un assez gros prog (programme de reco automatique de la parole)...
J'utilise CVS et avant de supprimer les indirections superflues, j'ai commité mon prog.
Ensuite j'ai fait mes modifs (suppression des indirections, modification des allocations dynamiques etc...).
 
Dans ce code, j'ai une partie critique qui doit etre la plus optimisée possible (mon algo de reco doit le faire en temps reel : 1 seconde d'execution pour le traitement de 10000 vecteurs à 36 dimensions). J'ai donc entourée cette partie d'un clock() et renvoyé la différence (avec la division qui va bien pour l'avoir en seconde).
 
Du coup j'ai pu tester le prog non modifié (4.75 secondes) (checkout du CVS dans un autre dossier) et le prog modifié (4.98 secondes) (la version non commitée du prog). Je l'ai fait plusieurs fois pour avoir une moyenne bien entendu...
 
La différence sera significative lorsque je traiterai des fichiers d'1 heure...
 
A priori il n'y a pas de grosse imperfection dans le prog puisqu'il fonctionne ( :-) ) et que Insure me dit qu'il n'y a pas d'erreurs detectée (debordement memoire, lecture de pointeurs NULL, ...)
 
Donc comprend pas, m'aurait on trompé ?

Reply

Marsh Posté le 03-02-2006 à 16:26:24    

Au pif, si ta structure est sensiblement plus grosse que les pointeurs et selon l'ordre de tes accès ptètre qu'une différence se fait dans la gestion du cache processeur.


---------------
Me: Django Localization, Yogo Puzzle, Chrome Grapher, C++ Signals, Brainf*ck.
Reply

Marsh Posté le 03-02-2006 à 16:35:11    

elle fais quel taille la structure A ? tu compiles avec quoi et avec quels options ?

Reply

Marsh Posté le 03-02-2006 à 16:35:11   

Reply

Marsh Posté le 03-02-2006 à 16:58:52    

compilation : gcc -lm -Wall -ansi -O3
 
Et oui effectivement ma structure est largement plus grosse qu'un pointeur. C'est plus compliqué que deux structures A et B ... en moyenne on va dire qu'une structure dans mon programme fait 24 octets (en profondeur 1 : c'est à dire un pointeur = 4).

Reply

Marsh Posté le 03-02-2006 à 17:19:14    

24 octets c'est pas grand chose, sans tableau de pointeur c'est plus cache-friendly
tu fais quoi comme traitement sur ces tableaux ?

Reply

Marsh Posté le 07-02-2006 à 10:10:29    

Désolé de pas avoir répondu avant ...
 
En fait, je fais principalement de l'accès aux données des structures dans les tableaux, mais aussi un peu de modif ; la partie dont je mesure le temps d'execution de comprend pas de malloc pas de realloc juste des lectures et quelques ecritures dans ces tableaux de structures ...

Reply

Marsh Posté le 07-02-2006 à 12:27:37    

faudrais que tu montres un peu de code, le minimun pour les 2 cas :

  • la définition des structure
  • le code simplifié du traitement (la ou se fait la différence de perf) en une 20aines de lignes max


Reply

Marsh Posté le 07-02-2006 à 15:12:34    

julien_54 a écrit :

Une structure A posséde un champ qui est un tableau de structure B. Mon premier reflexe a alors été de créer un tableau de pointeurs sur struct B dans A (ce qui donnait struct B **tab_b).
On pouvait alors accéder aux champs de B avec A->B[i]->champ


En lisant ta description moi j'aurais plutôt écrit "A.B[i]->champ". Mais c'est un détail, "A" doit probablement être un pointeur sur une "struct A"...
 

julien_54 a écrit :

Ensuite, relisant mon code, et suite à plusieurs discussions, j'ai décidé qu'il était inutile de surcharger la mémoire avec un tableau de pointeurs, mais plutot mettre directement un tableau de structures... je me suis dit qu'avec une indirection en moins, cela accelererait le code. Donc c'est parti, maintenant j'ai struct B *tab_b dans A,
et j'y accède avec A->B[i].champ


Idem remarque précédente...
 

julien_54 a écrit :

Et là, je suis etonné, mais il semble que cela ralentisse l'execution.


 
Premier cas:
Tu as des tas de structures B un peu partout
Tu as aussi une structure A qui contient les adresses de chaque structure B
On peut schématiquement représenter ça (c'est pas évident sans dessin) où toutes ces structures B baguenaudent de ci, de là (instant poésie)... mais tu as chaque fois leurs adresses pour les référencer. Pour simplifier, ce serait presque comme une liste chaînée ou une inode. Mais la structure A reste elle-même très légère car elle ne mémorise que des adresses.
 
Second cas
Ta structure A a été modifiée et contient maintenant l'ensemble de tes structures B qui se suivent en mémoire. Elle est maintenant bien plus lourde à gérer.
A chaque fois que tu fais "A.B[i]" le compilo doit décaler non pas de "i * sizeof(adresse)" octets mais "i * sizeof(B)" octets. Plus de calcul => plus de temps...
 

julien_54 a écrit :

Qu'en pensez vous ? Avant tout, cette méthode de gestion des tableaux de structure est elle correcte, ou vaut il mieux garder des tableau de pointeurs lorsque les deux méthodes sont possibles (plus propre ?)


Je pense que si tu regardes comment est fait l'inode, tu auras déjà un bon point de repère.
 

julien_54 a écrit :

Moi je pense que l'optimiseur du compilo se demerdait mieux avec un tableau de pointeur. Je pense aussi qu'avec cette modif, il a plus de mémoire à manipuler pour réallouer...


T'as gagné. A une époque on gagnait du temps de calcul sur les tableaux de structures en les taillant à une taille pile poil puissance de 2 (256, 512, 1024, 2048, etc) car cela tombait sur des mots mémoires et l'optimiseurs pouvait décaler plus rapidement mais je ne sais pas si c'est encore utile aujourd'hui. Tu peux essayer de générer l'assembleur pour chaqu'un de tes 2 cas et voir la taille du source généré. C'est un bon point de repère. Mais je pense que le tableau d'adresses est encore ce qu'il y a de plus rapide.

Message cité 2 fois
Message édité par Sve@r le 07-02-2006 à 15:15:46

---------------
Vous ne pouvez pas apporter la prospérité au pauvre en la retirant au riche.
Reply

Marsh Posté le 07-02-2006 à 15:29:29    

Sve@r a écrit :

A chaque fois que tu fais "A.B[i]" le compilo doit décaler non pas de "i * sizeof(adresse)" octets mais "i * sizeof(B)" octets. Plus de calcul => plus de temps...


 
c'est la meme chose ??  
avec la premiere methode tout est fragementé avec une indirection en plus lors de l'acces, ca dois forcement compter ?

Reply

Marsh Posté le 07-02-2006 à 17:34:05    

Sve@r a écrit :

En lisant ta description moi j'aurais plutôt écrit "A.B[i]->champ". Mais c'est un détail, "A" doit probablement être un pointeur sur une "struct A"...
 
Exactement (bonne remarque...)
 
Idem remarque précédente...
 
  :sweat:  
 
Premier cas:
Tu as des tas de structures B un peu partout
Tu as aussi une structure A qui contient les adresses de chaque structure B
On peut schématiquement représenter ça (c'est pas évident sans dessin) où toutes ces structures B baguenaudent de ci, de là (instant poésie)... mais tu as chaque fois leurs adresses pour les référencer. Pour simplifier, ce serait presque comme une liste chaînée ou une inode. Mais la structure A reste elle-même très légère car elle ne mémorise que des adresses.
 
Si l'on m'avait dit que l'on peut faire de la poésie en parlant d'allocation mémoire en C ...
 
C'est quoi inode ? (je vais demander à mon ami Google ...)
 
Second cas
Ta structure A a été modifiée et contient maintenant l'ensemble de tes structures B qui se suivent en mémoire. Elle est maintenant bien plus lourde à gérer.
A chaque fois que tu fais "A.B[i]" le compilo doit décaler non pas de "i * sizeof(adresse)" octets mais "i * sizeof(B)" octets. Plus de calcul => plus de temps...
 
 
Je pense que si tu regardes comment est fait l'inode, tu auras déjà un bon point de repère.
 
 
T'as gagné. A une époque on gagnait du temps de calcul sur les tableaux de structures en les taillant à une taille pile poil puissance de 2 (256, 512, 1024, 2048, etc) car cela tombait sur des mots mémoires et l'optimiseurs pouvait décaler plus rapidement mais je ne sais pas si c'est encore utile aujourd'hui. Tu peux essayer de générer l'assembleur pour chaqu'un de tes 2 cas et voir la taille du source généré. C'est un bon point de repère. Mais je pense que le tableau d'adresses est encore ce qu'il y a de plus rapide.


 
OK on pense la meme chose ...
Merci pour ta réponse ...
 

Reply

Marsh Posté le 07-02-2006 à 17:35:05    

oups je me suis loupé pour la réponse précedente (certaines de mes remarques sont mélangées avec le texte de la réponse ...)

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed