Samuel Krempp writes:
[...]Oui, c'est assez effrayant. Je n'arrive pas à comprendre
comment la classe de ostream a pu être pensée, sans qu'on
puisse l'effacer. C'est un choix de design que je trouve
profondément stupide, mais d'un autre côté, je n'ai pas
toutes les cartes en main pour juger...
moi aussi, je trouve ça dommage qu'il n'aie été
décidé de forcer l'utilisation kleenex des stringstream,
même dans les cas où il serait avantageux de réutiliser au
moins le buffer d'un formattage à l'autre, alors que ça ne
demande très peu de chose (soit rendre public pptr() et pbase(),
soit fournir une fonction cur_str() en plus de str() ).
Mais c'est un principe général : si tu veux un objet dans
l'état de sa construction, la solution préconsisée est de le
construire de nouveau. J'avoue ne voir que de la logique là dedans.
Samuel Krempp <krempp@crans.truc.en.trop.ens-cachan.fr> writes:
[...]
Oui, c'est assez effrayant. Je n'arrive pas à comprendre
comment la classe de ostream a pu être pensée, sans qu'on
puisse l'effacer. C'est un choix de design que je trouve
profondément stupide, mais d'un autre côté, je n'ai pas
toutes les cartes en main pour juger...
moi aussi, je trouve ça dommage qu'il n'aie été
décidé de forcer l'utilisation kleenex des stringstream,
même dans les cas où il serait avantageux de réutiliser au
moins le buffer d'un formattage à l'autre, alors que ça ne
demande très peu de chose (soit rendre public pptr() et pbase(),
soit fournir une fonction cur_str() en plus de str() ).
Mais c'est un principe général : si tu veux un objet dans
l'état de sa construction, la solution préconsisée est de le
construire de nouveau. J'avoue ne voir que de la logique là dedans.
Samuel Krempp writes:
[...]Oui, c'est assez effrayant. Je n'arrive pas à comprendre
comment la classe de ostream a pu être pensée, sans qu'on
puisse l'effacer. C'est un choix de design que je trouve
profondément stupide, mais d'un autre côté, je n'ai pas
toutes les cartes en main pour juger...
moi aussi, je trouve ça dommage qu'il n'aie été
décidé de forcer l'utilisation kleenex des stringstream,
même dans les cas où il serait avantageux de réutiliser au
moins le buffer d'un formattage à l'autre, alors que ça ne
demande très peu de chose (soit rendre public pptr() et pbase(),
soit fournir une fonction cur_str() en plus de str() ).
Mais c'est un principe général : si tu veux un objet dans
l'état de sa construction, la solution préconsisée est de le
construire de nouveau. J'avoue ne voir que de la logique là dedans.
le Sunday 21 September 2003 18:12, écrivit :Mais c'est un principe général : si tu veux un objet dans l'état de
sa construction, la solution préconsisée est de le construire de
nouveau. J'avoue ne voir que de la logique là dedans.
mais c'est une logique qui a sauté une étape : on peut vouloir
formatter plusieurs choses à la suite, sans modifier l'état du stream.
(excepté son buffer, bien sûr).
D'ailleurs, je ne sais pas quoi penser de la méthode
stringbuf::str(string s). C'est prévu pour réutiliser un stringstream
d'une certaine façon (un constructeur fournit les mêmepossibiltiés).
Peut être même c'est entre autre prévu pour réinitialiser le buffer
avec str(""), mais cette façon de faire est vraiment médiocre :
l'espace alloué est mis à la poubelle, alors qu'on pourrait faire
autrement.
Maintenant, il y a bien le problème que la construction d'un
ostreamstring, y compris l'allocation, est assez lente. Mais AMHA,
la
en fait l'allocation, je la trouve très efficace. c'est plutôt le
reste qui m'a surpris. ça me parait étrange que le reste prenne autant
que l'allocation. Après mes mesures, en gros dans la construction d'un
stringstream la moitié du temps est pris par la construction du
stringbuf, l'autre par celle du stream par dessus.
Je ne sais pas. Que l'initialisation d'un stringbuf soit aussi lente
qu'elle l'est parfois, c'est effectivement dommage. Mais
réalistiquement, une implémentation qui est optimale pour une
utilisation ne le sera pas pour une autre, et ce qu'exige la norme
doit faire un compromis, qui ne serait la plupart du temps optimal
pour personne. Du coup, du moment qu'on a des exigeances
particulières, que ce soit de performance ou d'autre, on se trouve
amené à développer sa propre classe.
c'est très souvent le cas.
Mais là, il ne s'agit pas d'équilibrer les performances entre telle et
telle condition d'utilisation. Permettre de vider le buffer sans le
réallouer ni faire quoi que ce soit d'autre au stream, ça n'a aucun
impact négatif sur les perfs, quelle que soit la situation.
Ca améliore énormément les perfs dans des conditions spécifiques, sans
inconvénient. Bon, on veut peut être éviter d'encourager l'utilisateur
à faire des bêtises avec les buffers. Mais les IOstreams sont
nativement articulés sur l'emboîtage stream / buffer, et rien ne
justifie qu'on ne puisse pas en tirer profit dans le cas des
stringstream. Vider le buffer sans réinitialiser l'état du stream, et
vice-versa, ça serait un feature tout à fait légitime.
Je ne vois aucun problème potentiel à ajouter une méthode cur_str() au
stringbuf (et au stringstream) qui copierait [pbase(), pptr()[ au lieu
du tableau buffer complet :
int i345, jg8;
stringbuf buf;
{
std::ostream os(&buf);
foo(x, y, z, os);
}
string s1 = buf.cur_str();
buf.clear_buffer();
{
std::ostream os(&buf);
bar(z, os);
}
string s2 = buf.cur_str();
dans une boucle, ou même seulement si l'on utilise 4 ou 5 de ces blocs
dans une fonction assez cruciale, cette façon de faire peut être
facilement 2 fois plus rapide que de construire stream *et* buffer à
chaque fois. Dans les cas où on sait comment le stream est utilisé et
qu'il est inutile de le reconstruire, on peut même faire 8 fois plus
rapide que tout reconstruire à chaque fois.
Contrairement au reste des choix du C++, pour les stringstreams la
norme impose une spécification qui écarte une utilisation plusieurs
fois plus rapide dans certains cas, sans contrepartie pour les autres
utilisations. je trouve ça mal foutu..
Pendant que j'y suis, stringbuf devrait fournir publiquement l'accès
aux pointeurs sur son buffer, pour permettre d'utiliser la résultat de
formattage sans copie forcée lors du renvoi d'un string. c'est plus un
détail, c'est loin d'avoir le même impact que les créations inutiles
de stream et de buffer.
GB_Format marche sans stringstream, mais s'en sert quand il est
disponible. Mais j'avoue que si j'avais le temps, une des première
chose que je ferais, c'est de le faire utiliser un streambuf sur
mésure, avec un pool de mémoire privée.
comment faire ça tout en faisant en sorte que la classe soit
utilisable en contexte de threads ? il faut pouvoir mettre des locks,
et donc connaitre la lib de threads qui sera utilisée, non ?
le Sunday 21 September 2003 18:12, kanze@alex.gabi-soft.fr écrivit :
Mais c'est un principe général : si tu veux un objet dans l'état de
sa construction, la solution préconsisée est de le construire de
nouveau. J'avoue ne voir que de la logique là dedans.
mais c'est une logique qui a sauté une étape : on peut vouloir
formatter plusieurs choses à la suite, sans modifier l'état du stream.
(excepté son buffer, bien sûr).
D'ailleurs, je ne sais pas quoi penser de la méthode
stringbuf::str(string s). C'est prévu pour réutiliser un stringstream
d'une certaine façon (un constructeur fournit les mêmepossibiltiés).
Peut être même c'est entre autre prévu pour réinitialiser le buffer
avec str(""), mais cette façon de faire est vraiment médiocre :
l'espace alloué est mis à la poubelle, alors qu'on pourrait faire
autrement.
Maintenant, il y a bien le problème que la construction d'un
ostreamstring, y compris l'allocation, est assez lente. Mais AMHA,
la
en fait l'allocation, je la trouve très efficace. c'est plutôt le
reste qui m'a surpris. ça me parait étrange que le reste prenne autant
que l'allocation. Après mes mesures, en gros dans la construction d'un
stringstream la moitié du temps est pris par la construction du
stringbuf, l'autre par celle du stream par dessus.
Je ne sais pas. Que l'initialisation d'un stringbuf soit aussi lente
qu'elle l'est parfois, c'est effectivement dommage. Mais
réalistiquement, une implémentation qui est optimale pour une
utilisation ne le sera pas pour une autre, et ce qu'exige la norme
doit faire un compromis, qui ne serait la plupart du temps optimal
pour personne. Du coup, du moment qu'on a des exigeances
particulières, que ce soit de performance ou d'autre, on se trouve
amené à développer sa propre classe.
c'est très souvent le cas.
Mais là, il ne s'agit pas d'équilibrer les performances entre telle et
telle condition d'utilisation. Permettre de vider le buffer sans le
réallouer ni faire quoi que ce soit d'autre au stream, ça n'a aucun
impact négatif sur les perfs, quelle que soit la situation.
Ca améliore énormément les perfs dans des conditions spécifiques, sans
inconvénient. Bon, on veut peut être éviter d'encourager l'utilisateur
à faire des bêtises avec les buffers. Mais les IOstreams sont
nativement articulés sur l'emboîtage stream / buffer, et rien ne
justifie qu'on ne puisse pas en tirer profit dans le cas des
stringstream. Vider le buffer sans réinitialiser l'état du stream, et
vice-versa, ça serait un feature tout à fait légitime.
Je ne vois aucun problème potentiel à ajouter une méthode cur_str() au
stringbuf (et au stringstream) qui copierait [pbase(), pptr()[ au lieu
du tableau buffer complet :
int i345, jg8;
stringbuf buf;
{
std::ostream os(&buf);
foo(x, y, z, os);
}
string s1 = buf.cur_str();
buf.clear_buffer();
{
std::ostream os(&buf);
bar(z, os);
}
string s2 = buf.cur_str();
dans une boucle, ou même seulement si l'on utilise 4 ou 5 de ces blocs
dans une fonction assez cruciale, cette façon de faire peut être
facilement 2 fois plus rapide que de construire stream *et* buffer à
chaque fois. Dans les cas où on sait comment le stream est utilisé et
qu'il est inutile de le reconstruire, on peut même faire 8 fois plus
rapide que tout reconstruire à chaque fois.
Contrairement au reste des choix du C++, pour les stringstreams la
norme impose une spécification qui écarte une utilisation plusieurs
fois plus rapide dans certains cas, sans contrepartie pour les autres
utilisations. je trouve ça mal foutu..
Pendant que j'y suis, stringbuf devrait fournir publiquement l'accès
aux pointeurs sur son buffer, pour permettre d'utiliser la résultat de
formattage sans copie forcée lors du renvoi d'un string. c'est plus un
détail, c'est loin d'avoir le même impact que les créations inutiles
de stream et de buffer.
GB_Format marche sans stringstream, mais s'en sert quand il est
disponible. Mais j'avoue que si j'avais le temps, une des première
chose que je ferais, c'est de le faire utiliser un streambuf sur
mésure, avec un pool de mémoire privée.
comment faire ça tout en faisant en sorte que la classe soit
utilisable en contexte de threads ? il faut pouvoir mettre des locks,
et donc connaitre la lib de threads qui sera utilisée, non ?
le Sunday 21 September 2003 18:12, écrivit :Mais c'est un principe général : si tu veux un objet dans l'état de
sa construction, la solution préconsisée est de le construire de
nouveau. J'avoue ne voir que de la logique là dedans.
mais c'est une logique qui a sauté une étape : on peut vouloir
formatter plusieurs choses à la suite, sans modifier l'état du stream.
(excepté son buffer, bien sûr).
D'ailleurs, je ne sais pas quoi penser de la méthode
stringbuf::str(string s). C'est prévu pour réutiliser un stringstream
d'une certaine façon (un constructeur fournit les mêmepossibiltiés).
Peut être même c'est entre autre prévu pour réinitialiser le buffer
avec str(""), mais cette façon de faire est vraiment médiocre :
l'espace alloué est mis à la poubelle, alors qu'on pourrait faire
autrement.
Maintenant, il y a bien le problème que la construction d'un
ostreamstring, y compris l'allocation, est assez lente. Mais AMHA,
la
en fait l'allocation, je la trouve très efficace. c'est plutôt le
reste qui m'a surpris. ça me parait étrange que le reste prenne autant
que l'allocation. Après mes mesures, en gros dans la construction d'un
stringstream la moitié du temps est pris par la construction du
stringbuf, l'autre par celle du stream par dessus.
Je ne sais pas. Que l'initialisation d'un stringbuf soit aussi lente
qu'elle l'est parfois, c'est effectivement dommage. Mais
réalistiquement, une implémentation qui est optimale pour une
utilisation ne le sera pas pour une autre, et ce qu'exige la norme
doit faire un compromis, qui ne serait la plupart du temps optimal
pour personne. Du coup, du moment qu'on a des exigeances
particulières, que ce soit de performance ou d'autre, on se trouve
amené à développer sa propre classe.
c'est très souvent le cas.
Mais là, il ne s'agit pas d'équilibrer les performances entre telle et
telle condition d'utilisation. Permettre de vider le buffer sans le
réallouer ni faire quoi que ce soit d'autre au stream, ça n'a aucun
impact négatif sur les perfs, quelle que soit la situation.
Ca améliore énormément les perfs dans des conditions spécifiques, sans
inconvénient. Bon, on veut peut être éviter d'encourager l'utilisateur
à faire des bêtises avec les buffers. Mais les IOstreams sont
nativement articulés sur l'emboîtage stream / buffer, et rien ne
justifie qu'on ne puisse pas en tirer profit dans le cas des
stringstream. Vider le buffer sans réinitialiser l'état du stream, et
vice-versa, ça serait un feature tout à fait légitime.
Je ne vois aucun problème potentiel à ajouter une méthode cur_str() au
stringbuf (et au stringstream) qui copierait [pbase(), pptr()[ au lieu
du tableau buffer complet :
int i345, jg8;
stringbuf buf;
{
std::ostream os(&buf);
foo(x, y, z, os);
}
string s1 = buf.cur_str();
buf.clear_buffer();
{
std::ostream os(&buf);
bar(z, os);
}
string s2 = buf.cur_str();
dans une boucle, ou même seulement si l'on utilise 4 ou 5 de ces blocs
dans une fonction assez cruciale, cette façon de faire peut être
facilement 2 fois plus rapide que de construire stream *et* buffer à
chaque fois. Dans les cas où on sait comment le stream est utilisé et
qu'il est inutile de le reconstruire, on peut même faire 8 fois plus
rapide que tout reconstruire à chaque fois.
Contrairement au reste des choix du C++, pour les stringstreams la
norme impose une spécification qui écarte une utilisation plusieurs
fois plus rapide dans certains cas, sans contrepartie pour les autres
utilisations. je trouve ça mal foutu..
Pendant que j'y suis, stringbuf devrait fournir publiquement l'accès
aux pointeurs sur son buffer, pour permettre d'utiliser la résultat de
formattage sans copie forcée lors du renvoi d'un string. c'est plus un
détail, c'est loin d'avoir le même impact que les créations inutiles
de stream et de buffer.
GB_Format marche sans stringstream, mais s'en sert quand il est
disponible. Mais j'avoue que si j'avais le temps, une des première
chose que je ferais, c'est de le faire utiliser un streambuf sur
mésure, avec un pool de mémoire privée.
comment faire ça tout en faisant en sorte que la classe soit
utilisable en contexte de threads ? il faut pouvoir mettre des locks,
et donc connaitre la lib de threads qui sera utilisée, non ?
Dans la pratique, j'ai constaté que des GB_Format ne généraient pour
ainsi dire jamais plus de 128 caractères dynamique. L'idée, c'était
d'utiliser une classe Buffer avec un char[128] comme pool, en gérant de
plus près. Typiquement, alors, il n'aurait pas d'allocation dynamique du
tout, tout le buffer se trouvant dans le GB_Format. Et si on déborde le
buffer membre, on passe en allocation dynamique, mais toujours gérée
pour l'ensemble des streambuf, et non pour chacun.
okay. ça revient alors à peu près au même d'utiliser un seul stringbuf par
objet (customisé pour être réutilisable en rajoutant l'accès à [pbase(),
Dans la pratique, j'ai constaté que des GB_Format ne généraient pour
ainsi dire jamais plus de 128 caractères dynamique. L'idée, c'était
d'utiliser une classe Buffer avec un char[128] comme pool, en gérant de
plus près. Typiquement, alors, il n'aurait pas d'allocation dynamique du
tout, tout le buffer se trouvant dans le GB_Format. Et si on déborde le
buffer membre, on passe en allocation dynamique, mais toujours gérée
pour l'ensemble des streambuf, et non pour chacun.
okay. ça revient alors à peu près au même d'utiliser un seul stringbuf par
objet (customisé pour être réutilisable en rajoutant l'accès à [pbase(),
Dans la pratique, j'ai constaté que des GB_Format ne généraient pour
ainsi dire jamais plus de 128 caractères dynamique. L'idée, c'était
d'utiliser une classe Buffer avec un char[128] comme pool, en gérant de
plus près. Typiquement, alors, il n'aurait pas d'allocation dynamique du
tout, tout le buffer se trouvant dans le GB_Format. Et si on déborde le
buffer membre, on passe en allocation dynamique, mais toujours gérée
pour l'ensemble des streambuf, et non pour chacun.
okay. ça revient alors à peu près au même d'utiliser un seul stringbuf par
objet (customisé pour être réutilisable en rajoutant l'accès à [pbase(),
le Monday 22 September 2003 10:08, écrivit :J'ai comme un vague sentiment que le problème de stringbuf, c'est
qu'il essaie à faire trop de chose à la fois, avec le résultat qu'il
n'en fait aucun vraiment bien.
je crois que c'est vraiment ça le problème. d'ailleurs, je vais me
pencher sur l'aspect 'istream' possible d'un stringstream. C'est peut
être la cohabitation de ces 2 modes dans la même classe qui rend
indésirables les features que je réclame. Ce qu'on recherche dans un
ostringstream, c'est simplement de formatter des objets et d'obtenir
une chaine de caractères, et les fonctions qui rendent ça plus
pratiques n'ont pas vraiment de sens sur un istream, ou un iostream.
Je me demande si définir le buffer d'un ostringstream séparément du
buffer d'istringstream ne serait pas le premier pas à faire pour
avancer dans le bon sens..
C'est donc que l'allocation est efficace:-).
oui c'est pas faux :)
Sérieusement, si je régarde l'implémentation g++ (3.3.1) de
basic_stringbuf, je ne vois pas d'allocation : il s'initialise avec
une chaîne vide, sans faire de reserve ni quoique ce soit qui
provoquerait une allocation de buffer. C'est d'ailleurs conforme aux
traditions concernant les autres streambuf -- le buffer n'est alloué
que lors de la première sortie de caractère.
exact. (mais que ce ce soit alloué à la construction ou juste après,
ça ne fait pas de différence quand on utilise effectivement le stream
:)
Dans l'initialisation de basic_ios, en revanche, il y a au moins
trois appels à use_facet, parmi d'autres choses. Mais je me démande
quand même bien ce qui pourrait coûter autant de temps là-dedans.
(Je me démande bien aussi comment ils garantissent la construction
des facettes avant leur utilisation. Il y a sans doute un truc que
je n'ai pas vu.)
c'est assez dur de se faire une idée de ce qui prend du temps dans ces
fonctions qui utilisent les locales..
Y-a-t il qque part des documents donnant des ordres de grandeur, et
des choses à savoir sur ce domaine ?
j'ai vu un papier du working group parlant de performances, qui
disaient des choses intéressantes pour implémenter les IOstreams en
instanciant des locales que qd nécessaires etc.. mais ça ne précise
pas à quoi s'attendre en dehors de ces aspects.
Si je régarde l'implémentation g++, c'est ce que fait la version
non-const de str(). Au moins qu'il y a une erreur dans
l'implémentation de basic_string, et l'assign desalloue.
ah ? je ne sais pas d'où je tiens ça, mais j'étais persuadé que
str("") était forcé de réallouer. (ce qui colle plutot bien aux
mesures de temps d'execution avec g++ 3.3). je rvérifierai la norme
sur ce point ce soir.
Tu veux donc lui imposer une certaine stratégie de bufferisation. En
quelle forme est-ce qu'il doit les fournir publiquement : en tant
que string::iterator, ou en tant que char* ? Dans les deux cas, ça
pourrait poser un problème dans le cas des basic_string non
contigus.
ah mais je ne veux rien savoir de basic_string :-).
la specification du stringbuf précise que son buffer est un 'character
array'. c'est forcément contigü. Alors l'implémentation mets ça dans
un basic_string ou pas, c'est comme elle veut, mais l'accès en tant
que zone contigüe est de toute façon garanti possible.
c'est pour ça que je trouve dommage de ne pas permettre d'acceder
directement à cette zone.
Une bonne zone contigüe, ça sert à quoi si on n'y a pas accès ??
On a une limitation sans le bénéfice qu'on pourrait en tirer..Mon idée était plus simple. Le « pool » serait en fait un membre de
GB_Format -- chaque instance de GB_Format aurait donc son propre
pool, et donc, pas de problème de threads.
Pour le pool même : dans le temps, j'avais une classe Buffer, assez
simple, qui avait une interface un peu comme un vector<char>
simplifié au maximum (grosso modo, operator[], operator char*, et
ensureSize). La classe même contenait un char[] ; tant que la taille
rester inférieur à la taille du buffer membre, il n'y avait pas
d'allocation dynamic.
Dans la pratique, j'ai constaté que des GB_Format ne généraient pour
ainsi dire jamais plus de 128 caractères dynamique. L'idée, c'était
d'utiliser une classe Buffer avec un char[128] comme pool, en gérant
de plus près. Typiquement, alors, il n'aurait pas d'allocation
dynamique du tout, tout le buffer se trouvant dans le GB_Format. Et
si on déborde le buffer membre, on passe en allocation dynamique,
mais toujours gérée pour l'ensemble des streambuf, et non pour
chacun.
okay. ça revient alors à peu près au même d'utiliser un seul stringbuf
par objet (customisé pour être réutilisable en rajoutant l'accès à
[pbase(), pptr()[) en reconstruisant un stream dessus quand
nécessaire, puisqu'en général l'implémentation du stringbuf alloue par
gros blocs (au moins 512 octets pour g++ il me semble)
mais en utilisant son propre buffer, on est sûr de ce comportement.
(je viens de voir que intel-linux-7.1 commence à 32 octets, puis 64,
puis x150% par allocation)
j'ai écrit mon stringbuf customisé, cette-fois ci sans dériver de
stringbuf, il me reste juste à vérifier comment se comporte
clear_buffer lorsque le buffer est utilisé en out *et* in.
je vais pouvoir essayer en pratique comment je voudrais qu'un
stringbuf se comporte..
le Monday 22 September 2003 10:08, kanze@gabi-soft.fr écrivit :
J'ai comme un vague sentiment que le problème de stringbuf, c'est
qu'il essaie à faire trop de chose à la fois, avec le résultat qu'il
n'en fait aucun vraiment bien.
je crois que c'est vraiment ça le problème. d'ailleurs, je vais me
pencher sur l'aspect 'istream' possible d'un stringstream. C'est peut
être la cohabitation de ces 2 modes dans la même classe qui rend
indésirables les features que je réclame. Ce qu'on recherche dans un
ostringstream, c'est simplement de formatter des objets et d'obtenir
une chaine de caractères, et les fonctions qui rendent ça plus
pratiques n'ont pas vraiment de sens sur un istream, ou un iostream.
Je me demande si définir le buffer d'un ostringstream séparément du
buffer d'istringstream ne serait pas le premier pas à faire pour
avancer dans le bon sens..
C'est donc que l'allocation est efficace:-).
oui c'est pas faux :)
Sérieusement, si je régarde l'implémentation g++ (3.3.1) de
basic_stringbuf, je ne vois pas d'allocation : il s'initialise avec
une chaîne vide, sans faire de reserve ni quoique ce soit qui
provoquerait une allocation de buffer. C'est d'ailleurs conforme aux
traditions concernant les autres streambuf -- le buffer n'est alloué
que lors de la première sortie de caractère.
exact. (mais que ce ce soit alloué à la construction ou juste après,
ça ne fait pas de différence quand on utilise effectivement le stream
:)
Dans l'initialisation de basic_ios, en revanche, il y a au moins
trois appels à use_facet, parmi d'autres choses. Mais je me démande
quand même bien ce qui pourrait coûter autant de temps là-dedans.
(Je me démande bien aussi comment ils garantissent la construction
des facettes avant leur utilisation. Il y a sans doute un truc que
je n'ai pas vu.)
c'est assez dur de se faire une idée de ce qui prend du temps dans ces
fonctions qui utilisent les locales..
Y-a-t il qque part des documents donnant des ordres de grandeur, et
des choses à savoir sur ce domaine ?
j'ai vu un papier du working group parlant de performances, qui
disaient des choses intéressantes pour implémenter les IOstreams en
instanciant des locales que qd nécessaires etc.. mais ça ne précise
pas à quoi s'attendre en dehors de ces aspects.
Si je régarde l'implémentation g++, c'est ce que fait la version
non-const de str(). Au moins qu'il y a une erreur dans
l'implémentation de basic_string, et l'assign desalloue.
ah ? je ne sais pas d'où je tiens ça, mais j'étais persuadé que
str("") était forcé de réallouer. (ce qui colle plutot bien aux
mesures de temps d'execution avec g++ 3.3). je rvérifierai la norme
sur ce point ce soir.
Tu veux donc lui imposer une certaine stratégie de bufferisation. En
quelle forme est-ce qu'il doit les fournir publiquement : en tant
que string::iterator, ou en tant que char* ? Dans les deux cas, ça
pourrait poser un problème dans le cas des basic_string non
contigus.
ah mais je ne veux rien savoir de basic_string :-).
la specification du stringbuf précise que son buffer est un 'character
array'. c'est forcément contigü. Alors l'implémentation mets ça dans
un basic_string ou pas, c'est comme elle veut, mais l'accès en tant
que zone contigüe est de toute façon garanti possible.
c'est pour ça que je trouve dommage de ne pas permettre d'acceder
directement à cette zone.
Une bonne zone contigüe, ça sert à quoi si on n'y a pas accès ??
On a une limitation sans le bénéfice qu'on pourrait en tirer..
Mon idée était plus simple. Le « pool » serait en fait un membre de
GB_Format -- chaque instance de GB_Format aurait donc son propre
pool, et donc, pas de problème de threads.
Pour le pool même : dans le temps, j'avais une classe Buffer, assez
simple, qui avait une interface un peu comme un vector<char>
simplifié au maximum (grosso modo, operator[], operator char*, et
ensureSize). La classe même contenait un char[] ; tant que la taille
rester inférieur à la taille du buffer membre, il n'y avait pas
d'allocation dynamic.
Dans la pratique, j'ai constaté que des GB_Format ne généraient pour
ainsi dire jamais plus de 128 caractères dynamique. L'idée, c'était
d'utiliser une classe Buffer avec un char[128] comme pool, en gérant
de plus près. Typiquement, alors, il n'aurait pas d'allocation
dynamique du tout, tout le buffer se trouvant dans le GB_Format. Et
si on déborde le buffer membre, on passe en allocation dynamique,
mais toujours gérée pour l'ensemble des streambuf, et non pour
chacun.
okay. ça revient alors à peu près au même d'utiliser un seul stringbuf
par objet (customisé pour être réutilisable en rajoutant l'accès à
[pbase(), pptr()[) en reconstruisant un stream dessus quand
nécessaire, puisqu'en général l'implémentation du stringbuf alloue par
gros blocs (au moins 512 octets pour g++ il me semble)
mais en utilisant son propre buffer, on est sûr de ce comportement.
(je viens de voir que intel-linux-7.1 commence à 32 octets, puis 64,
puis x150% par allocation)
j'ai écrit mon stringbuf customisé, cette-fois ci sans dériver de
stringbuf, il me reste juste à vérifier comment se comporte
clear_buffer lorsque le buffer est utilisé en out *et* in.
je vais pouvoir essayer en pratique comment je voudrais qu'un
stringbuf se comporte..
le Monday 22 September 2003 10:08, écrivit :J'ai comme un vague sentiment que le problème de stringbuf, c'est
qu'il essaie à faire trop de chose à la fois, avec le résultat qu'il
n'en fait aucun vraiment bien.
je crois que c'est vraiment ça le problème. d'ailleurs, je vais me
pencher sur l'aspect 'istream' possible d'un stringstream. C'est peut
être la cohabitation de ces 2 modes dans la même classe qui rend
indésirables les features que je réclame. Ce qu'on recherche dans un
ostringstream, c'est simplement de formatter des objets et d'obtenir
une chaine de caractères, et les fonctions qui rendent ça plus
pratiques n'ont pas vraiment de sens sur un istream, ou un iostream.
Je me demande si définir le buffer d'un ostringstream séparément du
buffer d'istringstream ne serait pas le premier pas à faire pour
avancer dans le bon sens..
C'est donc que l'allocation est efficace:-).
oui c'est pas faux :)
Sérieusement, si je régarde l'implémentation g++ (3.3.1) de
basic_stringbuf, je ne vois pas d'allocation : il s'initialise avec
une chaîne vide, sans faire de reserve ni quoique ce soit qui
provoquerait une allocation de buffer. C'est d'ailleurs conforme aux
traditions concernant les autres streambuf -- le buffer n'est alloué
que lors de la première sortie de caractère.
exact. (mais que ce ce soit alloué à la construction ou juste après,
ça ne fait pas de différence quand on utilise effectivement le stream
:)
Dans l'initialisation de basic_ios, en revanche, il y a au moins
trois appels à use_facet, parmi d'autres choses. Mais je me démande
quand même bien ce qui pourrait coûter autant de temps là-dedans.
(Je me démande bien aussi comment ils garantissent la construction
des facettes avant leur utilisation. Il y a sans doute un truc que
je n'ai pas vu.)
c'est assez dur de se faire une idée de ce qui prend du temps dans ces
fonctions qui utilisent les locales..
Y-a-t il qque part des documents donnant des ordres de grandeur, et
des choses à savoir sur ce domaine ?
j'ai vu un papier du working group parlant de performances, qui
disaient des choses intéressantes pour implémenter les IOstreams en
instanciant des locales que qd nécessaires etc.. mais ça ne précise
pas à quoi s'attendre en dehors de ces aspects.
Si je régarde l'implémentation g++, c'est ce que fait la version
non-const de str(). Au moins qu'il y a une erreur dans
l'implémentation de basic_string, et l'assign desalloue.
ah ? je ne sais pas d'où je tiens ça, mais j'étais persuadé que
str("") était forcé de réallouer. (ce qui colle plutot bien aux
mesures de temps d'execution avec g++ 3.3). je rvérifierai la norme
sur ce point ce soir.
Tu veux donc lui imposer une certaine stratégie de bufferisation. En
quelle forme est-ce qu'il doit les fournir publiquement : en tant
que string::iterator, ou en tant que char* ? Dans les deux cas, ça
pourrait poser un problème dans le cas des basic_string non
contigus.
ah mais je ne veux rien savoir de basic_string :-).
la specification du stringbuf précise que son buffer est un 'character
array'. c'est forcément contigü. Alors l'implémentation mets ça dans
un basic_string ou pas, c'est comme elle veut, mais l'accès en tant
que zone contigüe est de toute façon garanti possible.
c'est pour ça que je trouve dommage de ne pas permettre d'acceder
directement à cette zone.
Une bonne zone contigüe, ça sert à quoi si on n'y a pas accès ??
On a une limitation sans le bénéfice qu'on pourrait en tirer..Mon idée était plus simple. Le « pool » serait en fait un membre de
GB_Format -- chaque instance de GB_Format aurait donc son propre
pool, et donc, pas de problème de threads.
Pour le pool même : dans le temps, j'avais une classe Buffer, assez
simple, qui avait une interface un peu comme un vector<char>
simplifié au maximum (grosso modo, operator[], operator char*, et
ensureSize). La classe même contenait un char[] ; tant que la taille
rester inférieur à la taille du buffer membre, il n'y avait pas
d'allocation dynamic.
Dans la pratique, j'ai constaté que des GB_Format ne généraient pour
ainsi dire jamais plus de 128 caractères dynamique. L'idée, c'était
d'utiliser une classe Buffer avec un char[128] comme pool, en gérant
de plus près. Typiquement, alors, il n'aurait pas d'allocation
dynamique du tout, tout le buffer se trouvant dans le GB_Format. Et
si on déborde le buffer membre, on passe en allocation dynamique,
mais toujours gérée pour l'ensemble des streambuf, et non pour
chacun.
okay. ça revient alors à peu près au même d'utiliser un seul stringbuf
par objet (customisé pour être réutilisable en rajoutant l'accès à
[pbase(), pptr()[) en reconstruisant un stream dessus quand
nécessaire, puisqu'en général l'implémentation du stringbuf alloue par
gros blocs (au moins 512 octets pour g++ il me semble)
mais en utilisant son propre buffer, on est sûr de ce comportement.
(je viens de voir que intel-linux-7.1 commence à 32 octets, puis 64,
puis x150% par allocation)
j'ai écrit mon stringbuf customisé, cette-fois ci sans dériver de
stringbuf, il me reste juste à vérifier comment se comporte
clear_buffer lorsque le buffer est utilisé en out *et* in.
je vais pouvoir essayer en pratique comment je voudrais qu'un
stringbuf se comporte..
Disons : istringstream pour la première utilisation ci-dessus,
ostringstream pour la séconde, et iostringstream pour la troisième. Avec
chaque fois un streambuf différent, pourquoi pas.
C'est un problème surtout chez les vieux (comme moi). Dans le temps (il
y a au moins quinze ans), j'ai appris à éviter les allocations comme la
peste. L'habitude reste, même quand il ne vaut pas la peine.
c'est assez dur de se faire une idée de ce qui prend du temps dans ces
fonctions qui utilisent les locales..
Je n'ai jamais essayé d'utiliser le profiler avec des fonctions
templatées. Qu'est-ce que ça donne ?
ah ? je ne sais pas d'où je tiens ça, mais j'étais persuadé que
str("") était forcé de réallouer. (ce qui colle plutot bien aux
mesures de temps d'execution avec g++ 3.3). je rvérifierai la norme
sur ce point ce soir.
Pratiquement tout ce qui concerne l'allocation dans une streambuf dépend
de l'allocation. D'après ce que j'ai vu (coup d'oeil rapide dans
l'implémentation dans g++ 3.3.1), la version non-const de str() se
contente d'appeler assign sur sa chaîne, puis remettre à jour ces
pointeurs.
En revanche, je crois que je me suis trompé en ce qui concerne la
garantie que assign ne réalloue pas. Cette garantie existe bien pour
vector, mais je ne crois pas qu'elle existe pour basic_string.
Tout à fait. Mais tu es bien d'accord avec moi que faire collaborer les
deux, en utilisant la mémoire du basic_string dans stringbuf, c'est une
optimisation intéressante. Or, si on veut garder la possibilité de cette
optimisation, sans trop contraindre l'implémentation de basic_string, il
faut faire gaffe à ne pas trop spécifier la gestion du buffer dans
stringbuf.
Actuellement, tu n'as aucune garantie que toute la chaîne générée se
trouve dans le buffer définit par pbase(), pptr() et epptr().
j'ai écrit mon stringbuf customisé, cette-fois ci sans dériver de
stringbuf, il me reste juste à vérifier comment se comporte
clear_buffer lorsque le buffer est utilisé en out *et* in.
Mais est-ce que ça t'intéresse à faire quelque chose de bidirectionnel ?
Je vois l'intérêt dans une bibliothèque standard, mais quand j'écris un
streambuf pour ma propre utilisation, je ne lui dôte que des
fonctionalités dont j'ai réelement besoin.
Disons : istringstream pour la première utilisation ci-dessus,
ostringstream pour la séconde, et iostringstream pour la troisième. Avec
chaque fois un streambuf différent, pourquoi pas.
C'est un problème surtout chez les vieux (comme moi). Dans le temps (il
y a au moins quinze ans), j'ai appris à éviter les allocations comme la
peste. L'habitude reste, même quand il ne vaut pas la peine.
c'est assez dur de se faire une idée de ce qui prend du temps dans ces
fonctions qui utilisent les locales..
Je n'ai jamais essayé d'utiliser le profiler avec des fonctions
templatées. Qu'est-ce que ça donne ?
ah ? je ne sais pas d'où je tiens ça, mais j'étais persuadé que
str("") était forcé de réallouer. (ce qui colle plutot bien aux
mesures de temps d'execution avec g++ 3.3). je rvérifierai la norme
sur ce point ce soir.
Pratiquement tout ce qui concerne l'allocation dans une streambuf dépend
de l'allocation. D'après ce que j'ai vu (coup d'oeil rapide dans
l'implémentation dans g++ 3.3.1), la version non-const de str() se
contente d'appeler assign sur sa chaîne, puis remettre à jour ces
pointeurs.
En revanche, je crois que je me suis trompé en ce qui concerne la
garantie que assign ne réalloue pas. Cette garantie existe bien pour
vector, mais je ne crois pas qu'elle existe pour basic_string.
Tout à fait. Mais tu es bien d'accord avec moi que faire collaborer les
deux, en utilisant la mémoire du basic_string dans stringbuf, c'est une
optimisation intéressante. Or, si on veut garder la possibilité de cette
optimisation, sans trop contraindre l'implémentation de basic_string, il
faut faire gaffe à ne pas trop spécifier la gestion du buffer dans
stringbuf.
Actuellement, tu n'as aucune garantie que toute la chaîne générée se
trouve dans le buffer définit par pbase(), pptr() et epptr().
j'ai écrit mon stringbuf customisé, cette-fois ci sans dériver de
stringbuf, il me reste juste à vérifier comment se comporte
clear_buffer lorsque le buffer est utilisé en out *et* in.
Mais est-ce que ça t'intéresse à faire quelque chose de bidirectionnel ?
Je vois l'intérêt dans une bibliothèque standard, mais quand j'écris un
streambuf pour ma propre utilisation, je ne lui dôte que des
fonctionalités dont j'ai réelement besoin.
Disons : istringstream pour la première utilisation ci-dessus,
ostringstream pour la séconde, et iostringstream pour la troisième. Avec
chaque fois un streambuf différent, pourquoi pas.
C'est un problème surtout chez les vieux (comme moi). Dans le temps (il
y a au moins quinze ans), j'ai appris à éviter les allocations comme la
peste. L'habitude reste, même quand il ne vaut pas la peine.
c'est assez dur de se faire une idée de ce qui prend du temps dans ces
fonctions qui utilisent les locales..
Je n'ai jamais essayé d'utiliser le profiler avec des fonctions
templatées. Qu'est-ce que ça donne ?
ah ? je ne sais pas d'où je tiens ça, mais j'étais persuadé que
str("") était forcé de réallouer. (ce qui colle plutot bien aux
mesures de temps d'execution avec g++ 3.3). je rvérifierai la norme
sur ce point ce soir.
Pratiquement tout ce qui concerne l'allocation dans une streambuf dépend
de l'allocation. D'après ce que j'ai vu (coup d'oeil rapide dans
l'implémentation dans g++ 3.3.1), la version non-const de str() se
contente d'appeler assign sur sa chaîne, puis remettre à jour ces
pointeurs.
En revanche, je crois que je me suis trompé en ce qui concerne la
garantie que assign ne réalloue pas. Cette garantie existe bien pour
vector, mais je ne crois pas qu'elle existe pour basic_string.
Tout à fait. Mais tu es bien d'accord avec moi que faire collaborer les
deux, en utilisant la mémoire du basic_string dans stringbuf, c'est une
optimisation intéressante. Or, si on veut garder la possibilité de cette
optimisation, sans trop contraindre l'implémentation de basic_string, il
faut faire gaffe à ne pas trop spécifier la gestion du buffer dans
stringbuf.
Actuellement, tu n'as aucune garantie que toute la chaîne générée se
trouve dans le buffer définit par pbase(), pptr() et epptr().
j'ai écrit mon stringbuf customisé, cette-fois ci sans dériver de
stringbuf, il me reste juste à vérifier comment se comporte
clear_buffer lorsque le buffer est utilisé en out *et* in.
Mais est-ce que ça t'intéresse à faire quelque chose de bidirectionnel ?
Je vois l'intérêt dans une bibliothèque standard, mais quand j'écris un
streambuf pour ma propre utilisation, je ne lui dôte que des
fonctionalités dont j'ai réelement besoin.
le Tuesday 23 September 2003 15:01, écrivit :
ah ? je ne sais pas d'où je tiens ça, mais j'étais persuadé que
str("") était forcé de réallouer. (ce qui colle plutot bien aux
mesures de temps d'execution avec g++ 3.3). je rvérifierai la norme
sur ce point ce soir.
Pratiquement tout ce qui concerne l'allocation dans une streambuf
dépend de l'allocation. D'après ce que j'ai vu (coup d'oeil rapide
dans l'implémentation dans g++ 3.3.1), la version non-const de str()
se contente d'appeler assign sur sa chaîne, puis remettre à jour ces
pointeurs.
En revanche, je crois que je me suis trompé en ce qui concerne la
garantie que assign ne réalloue pas. Cette garantie existe bien pour
vector, mais je ne crois pas qu'elle existe pour basic_string.
j'ai retrouvé ce qui m'avait mis dans la tête que str réallouait.
c'est la norme :)
§27.7.1.2
void str(const basic_string<charT,traits,Allocator>& s);
Effects : If the basic_stringbuf's underlying character sequence is
not empty, deallocates it. Then copies the content of s into the
basic_stringbuf underlying character sequence and initializes the
input and output sequences according to the mode stored when creating
the
est-ce qu'il faut donner un sens concret à ce qu'entend la norme par
'allocate', 'deallocate', etc.. ?
si on prend seulement le sens 'as if', l'impélemntation peut faire
bien des choses. Et l'utilisateur ne peut pas savoir qd de la mémoire
est prise / libérée, ce qui au final est tout de même un effet
mesurable.
Tout à fait. Mais tu es bien d'accord avec moi que faire collaborer
les deux, en utilisant la mémoire du basic_string dans stringbuf,
c'est une optimisation intéressante. Or, si on veut garder la
possibilité de cette optimisation, sans trop contraindre
l'implémentation de basic_string, il faut faire gaffe à ne pas trop
spécifier la gestion du buffer dans stringbuf.
de toute façon c'est trop tard, la norme le spécifié déja. le buffer
d'un stream buffer est un 'character array'. un stringbuf n'a pas de
destination finale, toutes les opérations agissent sur rien d'autre
que son array object. la 'underlying character sequence' est forcément
toute entière dans cet array object.
En fait je trouve pas ça plus mal, dans les usages normaux, on mets
pas des millions de chars dans un stringbuf, et c'est aussi bien que
ce soit un buffer linéaire. Enfin, ce serait bien, si on pouvait en
tirer profit !
pour une utilisation spécifique qui mérite un buffer à la deque/rope,
ça mérite de créér son buffer ad-hoc..Actuellement, tu n'as aucune garantie que toute la chaîne générée se
trouve dans le buffer définit par pbase(), pptr() et epptr().
je pense justement que si.
le Tuesday 23 September 2003 15:01, kanze@gabi-soft.fr écrivit :
ah ? je ne sais pas d'où je tiens ça, mais j'étais persuadé que
str("") était forcé de réallouer. (ce qui colle plutot bien aux
mesures de temps d'execution avec g++ 3.3). je rvérifierai la norme
sur ce point ce soir.
Pratiquement tout ce qui concerne l'allocation dans une streambuf
dépend de l'allocation. D'après ce que j'ai vu (coup d'oeil rapide
dans l'implémentation dans g++ 3.3.1), la version non-const de str()
se contente d'appeler assign sur sa chaîne, puis remettre à jour ces
pointeurs.
En revanche, je crois que je me suis trompé en ce qui concerne la
garantie que assign ne réalloue pas. Cette garantie existe bien pour
vector, mais je ne crois pas qu'elle existe pour basic_string.
j'ai retrouvé ce qui m'avait mis dans la tête que str réallouait.
c'est la norme :)
§27.7.1.2
void str(const basic_string<charT,traits,Allocator>& s);
Effects : If the basic_stringbuf's underlying character sequence is
not empty, deallocates it. Then copies the content of s into the
basic_stringbuf underlying character sequence and initializes the
input and output sequences according to the mode stored when creating
the
est-ce qu'il faut donner un sens concret à ce qu'entend la norme par
'allocate', 'deallocate', etc.. ?
si on prend seulement le sens 'as if', l'impélemntation peut faire
bien des choses. Et l'utilisateur ne peut pas savoir qd de la mémoire
est prise / libérée, ce qui au final est tout de même un effet
mesurable.
Tout à fait. Mais tu es bien d'accord avec moi que faire collaborer
les deux, en utilisant la mémoire du basic_string dans stringbuf,
c'est une optimisation intéressante. Or, si on veut garder la
possibilité de cette optimisation, sans trop contraindre
l'implémentation de basic_string, il faut faire gaffe à ne pas trop
spécifier la gestion du buffer dans stringbuf.
de toute façon c'est trop tard, la norme le spécifié déja. le buffer
d'un stream buffer est un 'character array'. un stringbuf n'a pas de
destination finale, toutes les opérations agissent sur rien d'autre
que son array object. la 'underlying character sequence' est forcément
toute entière dans cet array object.
En fait je trouve pas ça plus mal, dans les usages normaux, on mets
pas des millions de chars dans un stringbuf, et c'est aussi bien que
ce soit un buffer linéaire. Enfin, ce serait bien, si on pouvait en
tirer profit !
pour une utilisation spécifique qui mérite un buffer à la deque/rope,
ça mérite de créér son buffer ad-hoc..
Actuellement, tu n'as aucune garantie que toute la chaîne générée se
trouve dans le buffer définit par pbase(), pptr() et epptr().
je pense justement que si.
le Tuesday 23 September 2003 15:01, écrivit :
ah ? je ne sais pas d'où je tiens ça, mais j'étais persuadé que
str("") était forcé de réallouer. (ce qui colle plutot bien aux
mesures de temps d'execution avec g++ 3.3). je rvérifierai la norme
sur ce point ce soir.
Pratiquement tout ce qui concerne l'allocation dans une streambuf
dépend de l'allocation. D'après ce que j'ai vu (coup d'oeil rapide
dans l'implémentation dans g++ 3.3.1), la version non-const de str()
se contente d'appeler assign sur sa chaîne, puis remettre à jour ces
pointeurs.
En revanche, je crois que je me suis trompé en ce qui concerne la
garantie que assign ne réalloue pas. Cette garantie existe bien pour
vector, mais je ne crois pas qu'elle existe pour basic_string.
j'ai retrouvé ce qui m'avait mis dans la tête que str réallouait.
c'est la norme :)
§27.7.1.2
void str(const basic_string<charT,traits,Allocator>& s);
Effects : If the basic_stringbuf's underlying character sequence is
not empty, deallocates it. Then copies the content of s into the
basic_stringbuf underlying character sequence and initializes the
input and output sequences according to the mode stored when creating
the
est-ce qu'il faut donner un sens concret à ce qu'entend la norme par
'allocate', 'deallocate', etc.. ?
si on prend seulement le sens 'as if', l'impélemntation peut faire
bien des choses. Et l'utilisateur ne peut pas savoir qd de la mémoire
est prise / libérée, ce qui au final est tout de même un effet
mesurable.
Tout à fait. Mais tu es bien d'accord avec moi que faire collaborer
les deux, en utilisant la mémoire du basic_string dans stringbuf,
c'est une optimisation intéressante. Or, si on veut garder la
possibilité de cette optimisation, sans trop contraindre
l'implémentation de basic_string, il faut faire gaffe à ne pas trop
spécifier la gestion du buffer dans stringbuf.
de toute façon c'est trop tard, la norme le spécifié déja. le buffer
d'un stream buffer est un 'character array'. un stringbuf n'a pas de
destination finale, toutes les opérations agissent sur rien d'autre
que son array object. la 'underlying character sequence' est forcément
toute entière dans cet array object.
En fait je trouve pas ça plus mal, dans les usages normaux, on mets
pas des millions de chars dans un stringbuf, et c'est aussi bien que
ce soit un buffer linéaire. Enfin, ce serait bien, si on pouvait en
tirer profit !
pour une utilisation spécifique qui mérite un buffer à la deque/rope,
ça mérite de créér son buffer ad-hoc..Actuellement, tu n'as aucune garantie que toute la chaîne générée se
trouve dans le buffer définit par pbase(), pptr() et epptr().
je pense justement que si.