Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

assert() et génération de code

13 réponses
Avatar
Pierre Maurette
Bonjour,

Le rapport avec ma question précédente "Ça se fait,ça ?" n'est
imaginaire.

Je connais les règles de base sur l'optimisation code source, en gros,
vu sur la toile:

(1) Don't do it.
(2) (For experts only!) Don't do it yet.

Néanmoins, il est des cas qui ressemblent à de la contre-optimisation.
En particulier si je sais des choses sur une donnée - non constante -
que le compilateur ne peut intégrer dans sa démarche.

void func(/* ... */, size_t val)
{
/*
du code dans lequel val est utilisé lourdement,
à la fois dans des offset et dans des bornes de boucles
*/
}

Le compilateur va générer le code en considérant que val peut prendre
toutes les valeurs du type. Mais moi, je *sais* que val ne pourra
prendre que les valeurs 1 et 3. Et je vois clairement qu'il sera plus
efficace de dupliquer tout le code, une fois en remplaçant val par
(size_t)1, une fois par (size_t)3, avec un test de tête. Sans même
parler de duplication de la fonction.

Je sais bien que la norme laisse liberté au compilateur de générer du
code pour peu que le résultat soit celui que le programmeur est en
droit d'attendre. Mais d'un autre coté le programmeur est en droit
d'attendre un minimum de qualité.

Y-a-t-il un moyen reconnu de faire passer l'information au compilateur
avec un maximum de chances que le message passe ?

Dans ce cas précis, faire de val une enum ? Introduire dans la fonction
ou ailleurs un:
const size_t val[] = {1, 3};

Ou alors, solution plus générale (on peut avoir une inégalité, ou
l'indication d'une congruence à 4 par exemple):
assert(val == 1 || val == 3);
ou
assert((val % 4) == 0);

Je serais moi-même compilateur, c'est la solution de l'assert() qui me
conviendrait le mieux.

J'ai fait (trop ?) rapidement des tests avec gcc, il semble qu'il s'en
tamponne le coquillard, de mon assert().

Bonne journée législative...

--
Pierre Maurette

10 réponses

1 2
Avatar
espie
In article ,
Pierre Maurette wrote:
Ou alors, solution plus générale (on peut avoir une inégalité, ou
l'indication d'une congruence à 4 par exemple):
assert(val == 1 || val == 3);
ou
assert((val % 4) == 0);

Je serais moi-même compilateur, c'est la solution de l'assert() qui me
conviendrait le mieux.


Ouaip, ils n'ont pas du tout suivi cette voie, ils ont prefere rajouter
plein de cochonneries dependant du compilo. J'ai oublie la syntaxe
exacte, mais c'est des __attribute__ qui permettent de dire si telle branche
du test sera prise ou pas...

j'avoue etre d'accord avec toi, c'est plutot dommage de ne pas utiliser
directement assert, qui fait partie de la norme et qui pourrait etre
utilise pour de l'optimisation... il y a certainement eu une grosse
discussion dans la ml gcc sur le pourquoi et le comment, et le fait que
ca allait casser des trucs.

Vu les conneries qu'ils ont deja fait ces dernieres annees (la plus belle
etant sans doute de virer les memcpy a 0 "inutiles"... du beau travail
pour le code cryptographique), ils n'en etaient pourtant plus a une
pres...

Avatar
espie
In article <467581ae$0$5083$,
Harpo wrote:
On Sun, 17 Jun 2007 10:08:45 +0000, Marc Espie wrote:

Vu les conneries qu'ils ont deja fait ces dernieres annees (la plus belle
etant sans doute de virer les memcpy a 0 "inutiles"...


Qu'est-ce que c'est ?


J'ai ecrit memcpy en pensant memset, en fait.
memset fait partie de la norme, c'est donc une fonction magique.
Dans les versions recentes, gcc remplace systematiquement memset par
des boucles (ce qui occasionne son lot de problemes dans des cas d'alignement
un peu bizarres... ca encore c'est pas trop grave).

La ou ca pose vraiment probleme, c'est sur un cas d'utilisation assez
courant, style:


void
f()
{
char passwd[250];
// code qui demande un mot de passe a l'utilisateur.
// code qui s'en sert
memset(passwd, 0, sizeof passwd);
}

comme passwd est une variable locale, non utilisee ailleurs, gcc decrete
que le memset ne sert a rien... et hop, un passwd dans la nature.


Avatar
Thierry B.
--{ Marc Espie a plopé ceci: }--

void
f()
{
char passwd[250];
// code qui demande un mot de passe a l'utilisateur.
// code qui s'en sert
memset(passwd, 0, sizeof passwd);
}

comme passwd est une variable locale, non utilisee ailleurs, gcc decrete
que le memset ne sert a rien... et hop, un passwd dans la nature.



Sans déc' ? Là, ces jours-ci, je suis en train de gruiker un
"serveur" d'authentification, et j'avais classé le memset dans
les axiomes de base. Tu pourrais fournir un ECM qu'on puisse
tous tester chez nous pour voir l'étendue des dégats ?


--
"Low cost, stand-alone, easy to use". The trigone of computing.

Avatar
Pierre Maurette

[...]

void
f()
{
char passwd[250];
// code qui demande un mot de passe a l'utilisateur.
// code qui s'en sert
memset(passwd, 0, sizeof passwd);
}

comme passwd est une variable locale, non utilisee ailleurs, gcc decrete
que le memset ne sert a rien... et hop, un passwd dans la nature.


Vous pourriez essayer:

void
f()
{
volatile char passwd[250];
// code qui demande un mot de passe a l'utilisateur.
// code qui s'en sert
memset(passwd, 0, sizeof passwd);
}

La logique du volatile (piou piou), en gros à mon sens dire au
compilateur "T'arrêtes de faire le beau et de réfléchir à ma place, sur
cette variable, tu fais ce que je te dis de faire et au moment où je te
le dis, non mais !", voudrait que memset() soit appelé.
Mais la logique de gcc, parfois...

--
Pierre Maurette

Avatar
Xavier Roche
comme passwd est une variable locale, non utilisee ailleurs, gcc decrete
que le memset ne sert a rien... et hop, un passwd dans la nature.


Je n'ai pas le problème sous gcc à priori (version 3.3 ou 4.1)

Avatar
rixed
On 2007-06-17, Pierre Maurette wrote:
Le compilateur va générer le code en considérant que val peut prendre
toutes les valeurs du type. Mais moi, je *sais* que val ne pourra
prendre que les valeurs 1 et 3. Et je vois clairement qu'il sera plus
efficace de dupliquer tout le code, une fois en remplaçant val par
(size_t)1, une fois par (size_t)3, avec un test de tête. Sans même
parler de duplication de la fonction.

Je sais bien que la norme laisse liberté au compilateur de générer du
code pour peu que le résultat soit celui que le programmeur est en
droit d'attendre. Mais d'un autre coté le programmeur est en droit
d'attendre un minimum de qualité.

Y-a-t-il un moyen reconnu de faire passer l'information au compilateur
avec un maximum de chances que le message passe ?


Je m'étonnes de ne pas avoir vu de réponse de la part des participant
versés dans les arcannes de gcc, mais je crois que gcc est capable
de se rendre compte tout seul que la fonction ne sera appelée qu'avec
seulement deux valeurs différente et d'en tenir compte pour générer du
code plus spécifique. En gros, le fonctionnement serait le suivant :

- compiler le programme une première fois avec les options qui vont bien
pour que le code compilé inclue du code qui sauvegarde des statistiques,
entre autres les valeurs les plus courantes des paramètres d'entrée des
fonctions

- exécuter le programme "suffisament"

- recompiler le programme avec en entrée supplémentaire les statistiques
en questions.

Le programme résultant devrait inclure des versions spécialisées de ta
fonction pour les valeur 1 et 3.

Ne connaissant pas plus de détail sur la procédure, ce résumé
insatisfaisant n'a pour fonction que de faire sortir de leur réserve
ceux qui savent vraiment comment ça marche et comment on s'en sert.

Sinon, il me semble que ta question élude une partie importante du
problème : comment faire passer l'info non pas au compilateur, mais au
programmeur qui va relire. Pour ça, j'ai un penchant personnel pour
l'enum ; qui règle également la question des performances dans ce cas, je
pense.

Avatar
espie
In article <f558t8$f3t$,
Xavier Roche wrote:
comme passwd est une variable locale, non utilisee ailleurs, gcc decrete
que le memset ne sert a rien... et hop, un passwd dans la nature.


Je n'ai pas le problème sous gcc à priori (version 3.3 ou 4.1)


Tu n'as juste pas une version suffisamment recente... quand je disais
que ca bougeait beaucoup.


Avatar
espie
In article ,
Pierre Maurette wrote:

[...]

void
f()
{
char passwd[250];
// code qui demande un mot de passe a l'utilisateur.
// code qui s'en sert
memset(passwd, 0, sizeof passwd);
}

comme passwd est une variable locale, non utilisee ailleurs, gcc decrete
que le memset ne sert a rien... et hop, un passwd dans la nature.


Vous pourriez essayer:

void
f()
{
volatile char passwd[250];
// code qui demande un mot de passe a l'utilisateur.
// code qui s'en sert
memset(passwd, 0, sizeof passwd);
}

La logique du volatile (piou piou), en gros à mon sens dire au
compilateur "T'arrêtes de faire le beau et de réfléchir à ma place, sur
cette variable, tu fais ce que je te dis de faire et au moment où je te
le dis, non mais !", voudrait que memset() soit appelé.
Mais la logique de gcc, parfois...


En fait, non, ca ne suffit pas, pour deux raisons.
- d'abord, memset ne prend pas des parametres volatile. Donc stricto
sensu, il y a eu un cast, et c'est pas du tout evident de savoir
si memset devrait prendre en compte le volatile ou pas (plus les
references sous la main, mais ce genre de trucs a ete discute sur
les ml gcc).
- ensuite, c'est brutalement inefficace, puisque toutes les autres
operations sur passwd vont utiliser le cote volatile de la bestiole...
Comme, assez souvent, on va vouloir faire `quelques' calculs sur passwd
(genre, evaluation d'un hash cryptographique), on risque de se manger
un facteur 10 dans les dents, a un endroit qui n'en a pas forcement
besoin...

Il y a un flag: -fno-builtin-memset, mais c'est un peu tout ou rien,
sur la totalite des utilisations de memset dans une unite de compilation
(et ca desactive pas mal d'optimisations utiles, elles).


Avatar
Vincent Lefevre
Dans l'article <f55qed$10ac$,
Marc Espie écrit:

En fait, non, ca ne suffit pas, pour deux raisons.
- d'abord, memset ne prend pas des parametres volatile. Donc stricto
sensu, il y a eu un cast, et c'est pas du tout evident de savoir
si memset devrait prendre en compte le volatile ou pas (plus les
references sous la main, mais ce genre de trucs a ete discute sur
les ml gcc).
- ensuite, c'est brutalement inefficace, puisque toutes les autres
operations sur passwd vont utiliser le cote volatile de la bestiole...
Comme, assez souvent, on va vouloir faire `quelques' calculs sur passwd
(genre, evaluation d'un hash cryptographique), on risque de se manger
un facteur 10 dans les dents, a un endroit qui n'en a pas forcement
besoin...


Tu peux remplacer le memset par une boucle utilisant volatile (via
un cast ou une affectation de pointeur).

De toute façon, il te faudra faire des choses non standard pour être
sûr que la page mémoire ne doit pas être placée sur disque (swap).

--
Vincent Lefèvre - Web: <http://www.vinc17.org/>
100% accessible validated (X)HTML - Blog: <http://www.vinc17.org/blog/>
Work: CR INRIA - computer arithmetic / Arenaire project (LIP, ENS-Lyon)

Avatar
espie
In article <20070618174727$,
Vincent Lefevre <vincent+ wrote:

De toute façon, il te faudra faire des choses non standard pour être
sûr que la page mémoire ne doit pas être placée sur disque (swap).


Oui, comme par exemple de faire tourner OpenBSD, avec le swap chiffre...

1 2