12
votes

Quelles sont les sémantiques du «restreindre» de C99 en ce qui concerne les pointeurs aux indicateurs?

Je suis en train de faire beaucoup d'arithmétique de matrice et je voudrais profiter de de C99 restreindre code> Qualifiant de pointeur.

Je voudrais configurer mes matrices comme des pointeurs sur des pointeurs pour permettre subscripting facile, comme ceci: p>

void mmultiply ( int nrows, int ncols, int **Out, int **A, int **B);


2 commentaires

Un grand gros "merci" à tous les répondeurs! C'est une honte que je ne puisse choisir qu'une réponse "correcte" - vous avez tous aidé à réfléchir à des problèmes impliqués.


Onze ans plus tard et j'aimerais revenir à temps pour crier à mon ancien moi-même pour ne pas utiliser de blas . Ok, d'accord, openblas n'a pas existé à l'époque, mais L'ancien NetLib Blas a fait. Dans tous les cas, optimiser les multiplications matricielles n'est pas le genre de chose que vous voulez vous retrouver faire sur un caprice!


3 Réponses :


2
votes

L'extérieur (seconde) restreint indique au compilateur qu'aucun des tableaux des pointeurs (A, B et OUT). L'intérieur (premier) restreint indique au compilateur qu'aucun des matrices d'INT (pointé par des éléments des tableaux des pointeurs) alias.

Si vous accédez à la fois à un [0] [col * NRows + rangée] et à un [COL] [ligne], alors vous enfreignez la restriction intérieure, les choses peuvent donc casser.


0 commentaires

4
votes

Pour la première question, "oui", cela signifiera quelque chose de différent si vous utilisez à la fois des qualificateurs restreindre , en particulier que les pointeurs ne seront pas également aliasés. Quant à savoir si cela fait une différence: théoriquement oui, dans la pratique, cela dépend de l'optimiseur.

Pour la deuxième question, "oui", il suppose que tout ce qui est accessible via un pointeur de rangée n'est accessible que par le pointeur de la rangée.

Vous pouvez lancer const là aussi.

Enfin, s'il s'agit de GCC AT -O2, -O3, ou -OS, le compilateur fait déjà une analyse d'alias basée sur des types. Je suis sûr que d'autres compilateurs le font aussi. Cela signifie que restreindre les pointeurs vs L'INT est déjà compris, laissant uniquement les tableaux qui pourraient éventuellement se stocker.

En résumé, l'optimiseur supposera que les pointeurs ne sont pas stockés dans l'INT, et il sait que cela ne fait aucun pointeur écrit pendant la boucle.

Donc, vous obtiendrez probablement le même code avec seulement celui restreint.


3 commentaires

Il faut faire preuve de prudence que la plupart des compilateurs sont beaucoup plus laxistes que GCCX86 sur l'aliasing. MSVC05, par exemple, envisagera même un int * et un float * pour éventuellement alias, à moins que vous n'utilisiez restreindre de le dire autrement. (Oui, je sais que ce n'est pas par la spécification, mais le compilateur le fait de toute façon, comme j'ai appris de regarder / Facs.)


@Crashworks: il n'est pas nécessaire dans la spécification, mais une utilisation correcte de restreindre peut permettre au code de réaliser essentiellement les mêmes avantages de performance qu'un aliasing strict, mais sans casser aucun code existant, et rien dans la norme interdit aux compilateurs de traiter toutes les opérations comme elles agissent directement sur la mémoire.


Si une fonction prend un pointeur de restriction, et il y a déjà un autre pointeur sur le site d'appel, vous avez maintenant deux pointeurs sur le même objet, qui enfreint la restriction, n'est-ce pas?



2
votes

int ** restreindre n'affirme que la mémoire adressée à l'extérieur, A et B ne se chevauchent pas (sauf que A et B peuvent se chevaucher, en supposant que votre fonction ne modifie pas l'une ou l'autre d'entre elles) . Cela signifie que les tableaux des pointeurs. Il n'affirme rien sur le contenu de la mémoire pointée à l'extérieur, A et B. Note de bas de page 117 dans N1124 indique:

si l'identifiant P a type (int ** restreindre), puis les expressions du pointeur P et P + 1 sont basées sur le objet pointeur restreint désigné par p, mais les expressions du pointeur * p et p [1] ne sont pas.

Par analogie avec Const , je soupçonne que la qualification avec restreindre va-t-elle affirmer ce que vous voulez, qui n'a aucune des valeurs de la matrice ne pointe de la mémoire. Mais en lisant la norme, je ne peux pas me prouver que cela le fait. Je pense que "laissez d une déclaration d'un identifiant ordinaire qui fournit un moyen de désigner un objet P en tant que pointeur qualifié de restriction de type t" signifie effectivement que pour int * restreindre * restreindre un , alors un [0] et A [1] sont des objets désignés comme un pointeur qualifié de restriction sur INT. Mais c'est assez lourd légalois.

Je ne sais pas si votre compilateur fera tout ce qui est avec cette connaissance, pensez-vous. Il est clair que cela pourrait, c'est une question de savoir s'il est mis en œuvre.

Donc, je ne sais pas vraiment ce que vous avez gagné sur un tableau classique C 2-D, où vous venez d'allouer lignes * cols * Tailleof (int) et index avec Un [cols * rangée + col] . Ensuite, vous n'avez clairement besoin que d'une utilisation de restreindre et de tout compilateur qui fait quoi que ce soit avec restreindre sera en mesure de commander des lectures de A et B à travers écrit. Sans restreindre , bien sûr, il ne peut donc pas, alors en faisant ce que vous faites, vous vous jetez sur la miséricorde de votre compilateur. S'il ne peut pas faire face à une double restriction, seul le cas restreint unique, alors votre double indirection vous a coûté l'optimisation.

Au début, la multiplication est susceptible d'être plus rapide qu'une indemnité de pointeur supplémentaire de toute façon. Vous vous souciez évidemment de la performance ou que vous n'utiliseriez pas du tout restreint, donc je testerais des performances assez soigneusement (sur tous les compilateurs que vous vous souciez de) avant de faire ce changement pour le bien plus agréable syntaxe et de ne pas avoir à vous rappeler combien de personnes Colonnes Il y a dans votre tableau à chaque fois que vous y accédez.

est d'accéder aux éléments à travers un [0] [col * nrows + rangée] non défini?

Oui, si l'élément est modifié par l'un des accès, car cela rend un [0] un alias pour la mémoire accessible via un [col]. Ce serait bien si seulement A et B étaient des pointeurs qualifiés restrictifs, mais pas si un [0] et un [col] sont.

Je suppose que vous ne modifiez pas A dans cette fonction, alors cet alias va bien. Si vous avez fait la même chose avec le comportement, cela serait indéfini.


1 commentaires

Merci pour la réponse réfléchie. Vous avez raison sur la multiplication étant plus rapide que l'indirection du pointeur; J'avais déjà testé cela pour être généralement vrai. Cependant, je prévois de regrouper les dimensions avec les données dans une structure matricielle pour transmettre. Sans surprise, Déroferience NRows à partir du pointeur de structure et multipliant les coûts de la même manière que la déséroférance des pointeurs de données. Dans les fonctions qui y garantissent, je peux la déréférence selon les pointes d'avance et utiliser l'indexation de la matrice 1-D. Et si je le fais de toute façon, je peux aussi bien fournir la syntaxe plus belle [col] [ligne] ailleurs.