Pages

samedi 28 juin 2014

Réduire l'empreinte mémoire d'une agglomération de types

Un petit article pour parler d'optimisation mémoire (si on peut appeler ça comme ça) avec comme exemple la structure de donnée utilisée par std::unique_ptr.

Petit rappel, unique_ptr prend 2 paramètres en template: T et Deleter (qui est par défaut std::default_delete<T>).

Naïvement, l'implémentation serait:

template<T, Deleter = std::default_delete<T>>
class unique_ptr {
  T * m_pointer;
  Deleter m_deleter;

  //...
};

Rien d'extraordinaire.

Cependant, même si le Deleter est une classe sans attribut, sa taille est de 1 octet.

À partir d'ici je considère que Deleter est toujours la valeur par défaut, ce qui donne:

  • sizeof(T*) == 4
  • sizeof(Deleter) == 1
  • sizeof(unique_ptr<T>) == 8

Ouille, méchant padding alors que seule 4 octets sont vraiment utilisés.

La solution ? Une classe qui contient le pointeur et hérite de Deleter. Les attributs de la classe dérivée vont se mettre après ceux de Deleter, et s'il n'en a pas, les attributs se positionnent au début de la classe.

Cette optimisation se nomme Empty Base Class Optimization (EBCO). Merci Guillaume.

template<T, Deleter = std::default_delete<T>>
class unique_ptr {
  struct Pointer : Deleter {
    T * m_pointer;
    //...
  };
  //...
};
  • sizeof(unique_ptr<T>) == 4

Mieux, non ?

Et si le Deleter est une référence ? Bien entendu, l'héritage ne fonctionne pas, il faut se rabattre sur la première forme (celle naïve ^^).
Avec des traits et un code volumineux cela est "facile".

En allant encore plus loin, on peut utiliser un Deleter personnalisé final.
(Mais qui utilise des classes finales... ?)

À ce moment, évidemment, même remarque que les références, l'héritage ne compile pas et l'utilisation de la première forme s'impose.
Avec un trait is_final c'est possible, mais ce dernier n'existe pas et son implémentation est impossible... On est coincé !

Vraiment ? Non, car d'irréductibles développeurs de compilos ont créé __is_final, une fonction non-standard utilisée notamment par std::tuple.
De là à dire que toutes les implémentations l'utilisent... Il n'y a qu'un pas que je suis prêt à franchir :).
(En C++14 le trait std::is_final devient standard.)

Au final, l'utilisation de std::tuple permet de s'affranchir de ces difficultés tout en optimisant l'espace mémoire1.

template<T, Deleter = std::default_delete<T>>
class unique_ptr {
  std::tuple<T*, Deleter> m_t;
  //...
};

Il y a toutefois un léger problème avec std::tuple. Si un élément doit être initialisé, alors ils doivent tous l'être. Cela peut causer quelques soucis si un des types n'a ni copy-ctor, ni move-ctor.

1 À condition de mettre les types dans l'ordre croissant d'alignement afin de réduire le padding lorsqu'il y en a plus de 2 (falcon::optimal_tuple).

lundi 9 juin 2014

256 couleurs et plus dans la console

Note: ajout à la fin concernant le TrueColor.

J'ai récemment appris l'existence de 256 couleurs dans la console. À chaque fois que je cherche des infos sur les couleurs je tombe toujours sur les trucs basiques.

Mais récemment j'ai vu un peu trop de couleurs sur un screen. Ce qui m'a dirigé sur un dépôt contenant un $LS_COLORS particulièrement fourni.

En fait, il s'avère qu'en rajoutant extended dans la recherche "color shell" on puisse trouver quelque(s) ressource(s). J'aurais bien voulu y penser la semaine dernière, ça m'aurait évité de comprendre par tâtonnement...

Comme le lien ci-dessus est très bien expliqué je ne fais ici qu'un condensé. Je passe également sous silence les 8 et 16 couleurs.

Utilisation

<Esc>[FormatColorm

Le caractère <Esc> est obtenu avec les séquences suivantes:

  • \e
  • \033
  • \x1B

(Pour bash et echo, utiliser l'option -e pour interpréter les séquences backslashés.)

FormatColor correspond à la couleur de texte, de fond ou l'effet. Plusieurs peuvent être mis en les séparant par des ';'. L'ordre n'a pas d'importance.

CouleurTexteFond
Noir 30 40
Rouge 31 41
Vert 32 42
Jaune 33 43
Bleu 34 44
Magenta 35 45
Cyan 36 46
Gris clair 37 47

Les couleurs précédentes peuvent être configurées sur certains shells.

EffetCodeCode annulation
normal0
gras121
italique323
souligné424
clignotant525
inversé727
Couleur supplémentaire en 16 couleurs (si supporté)
CouleurTexteFond
Gris foncé 90 40
Rouge clair 91 101
Vert clair 92 102
Jaune clair 93 103
Bleu clair 94 104
Magenta clair 95 105
Cyan clair 96 106
Blanc 97 107

Couleur étendue avec X allant de 0 à 255 inclus:

  • Texte: 38;5;X
  • Fond: 48;5;X

Par contre, outre le fait que les couleurs étendues ne fonctionnent pas partout, elles peuvent allègrement pourrir le rendu. Pour retrouver un rendu normal: `tput reset`. À essayer sur les tty, c'est marrant ^^.

Le bout de code ci-dessous permet de visualiser la palette de couleur.

for m in 38 48 ; do
  n=0
  for l in {0..31} ; do
    for c in {0..7} ; do
      echo -ne "\033[$m;5;$n;1m$n\e[0m\t"
      ((++n))
    done
    echo
  done
done

TrueColor

La console supporte aussi les couleurs hexadécimal.

  • \e[48;2;$r;$g;${b}m pour le fond
  • \e[38;2;$r;$g;${b}m pour le texte

$r, $g et $b sont des valeurs allants de 0 à 255. Encore une fois, les effets comme gras, italique, etc, peuvent être ajoutés (echo -e '\e[38;2;205;110;0;3mplop' pour un texte couleur cuivre et italique).