dimanche 4 septembre 2016

Nested namespace definition

Entrée remarquée dans c++17 de la fonctionnalité de définition des espaces de noms imbriqués. C'est à dire qu'au lien de mettre des séries de "namespace a { namespace b { namespace c { ...", l'on peut directement écrire "namespace a::b::c { ...". Ça ne va pas changer la vie de qui que ce soit, mais pourquoi pas. Comme d'hab, le petit programme d'exemple:

#include <iostream>

namespace a::b::c // magie!!!!
{
  void f()
  {
    std::cout << "Ouh là, c'est profond!" << std::endl;
  }
}

int main()
{
  a::b::c::f();
  
  return 0;
}

vendredi 2 septembre 2016

Folding expressions

Youpie, gcc 6.1 a débarqué dans Debian! C'est une excellente occasion pour se plonger dans les nouveautés du standard. Parlons donc d'une fonctionnalité que j'avais complètement zappé, les "folding expression". L'idée est de fournir du sucre syntaxique pour permettre d'appliquer certains opérateurs à l'ensemble des paramètres variadiques d'une fonction. Dit comme ça, cela semble assez abstrait, mais puisque comme disait Napoléon, un petit bout de code vaut mieux que de grands discours, voici un exemple.

L'idée est donc d'écrire une fonction qui peut prendre un nombre quelconque d'arguments, et en retourner la somme. Avec une "folding expression", rien de plus simple! La syntaxe est un peu absconse, "(... [operator] args)" (ne pas oublier les parenthèses!), mais ça fait le boulot.

#include <iostream>

template<typename ...ARGS>
int variadicAdd(ARGS... args)
{
  return (... + args);
}

int main()
{
  std::cout << variadicAdd(3, 2, 7, 8, 4, 5) << std::endl;
  return 0;
}

Comme vous voyez, c'est tout bête. L'on aurait pu coder la même fonctionnalité avec des appels récursifs, mais c'est quand même beaucoup plus succinct comme ça. L'on peut "replier" les expressions avec tout un tas d'opérateurs, et j'attends avec impatience de voir ce que l'on va inventer avec cette construction.

dimanche 3 juillet 2016

Short String Optimisation

Enfin, ayant pu mettre la main sur la version 5.2 de gcc, m'empressais-je d'expérimenter un petit peu avec les short string optimisation (SSO), cette nouvelle implémentation de la classe std::string qui permet une grande amélioration des performances et de la mémoire pour la gestion des chaînes de caractères courtes. Historiquement, gcc implémentait std::string avec une autre optimisation, le "copy on write", qui est plus intéressant pour les grandes chaînes de caractères, mais de nouvelles contraintes dans le standard C++11 interdisent cette optimisation, et gcc doit donc passer à SSO.

Le principe est diablement intelligent: la classe std::string est typiquement implémentée avec un pointeur, vers l'espace mémoire alloué sur le tas et qui contient la chaîne en elle-même, et un entier correspondant à la taille de cette chaîne. L'idée est donc de dire que si la chaîne est suffisamment petite, plutôt que de l'allouer sur le tas, autant utiliser directement les octets du pointeur et de l'entier, en considérant le pointeur et l'entier comme un tableau de caractères.

Détails d'implémentation mis à part, cela nous donne effectivement un maximum de 15 octets (en 64 bits) sans aucune allocation. Et, sur certains programmes, cela fait une différence très significative.

Sur mon programme de test, qui est un générateur de code, c'est à dire un programme qui par définition passe sa vie à allouer des chaînes, l'on a diminué de moitié le nombre d'allocations, et diminué de moitié le temps d'exécution. Sur des programmes moins spécialisés, la différence est moins spectaculaire, mais cela reste très intéressant.

Alors, le souci est que cela casse la compatibilité binaire, et qu'il faut donc recompiler son programme toutes ses dépendances, ce qui, dans un environnement corporate avec des logiciels tiers propriétaires, peut poser des soucis. Mais si l'on peut, alors cela vaut vraiment le coup.

dimanche 24 avril 2016

RAM

Petit souci de PC, il y a quelques jours. La machine était contente sous Linux, mais le Windows était complètement pourri, refus de démarrer, erreur différente à chaque fois, restauration sans effet... Je m'apprêtais à blaster la partition et à réinstaller Windows au propre, lorsque l'entrée memtest86+ dans le menu de boot attira mon regard. Et en effet, c'était bien juste une barrette défectueuse... Après localisation et expulsion de la fautive, la machine avait repris du poil de la bête, et je me suis économisé une réinstallation complète.

dimanche 10 janvier 2016

Dnssec chez vous

Cela faisait un certain temps que j'avais installé un résolveur DNS sur ma machine, tout d'abord parce que le DNS de mon fournisseur d'accès à Internet avait la fâcheuse tendance à planter, et ensuite parce que ça le fait d'aller interroger directement les serveurs racine.

Il y a quelques jours, ayant lu un article sur Dnssec, je me suis donc demandé si mon installation utilisait ce système qui permet de sécuriser la résolution de nom en s'assurant que personne de bidouille l'info, typiquement pour nous router vers un site de malware plutôt que le site recherché. Une rapide recherche m'amena vers un site de test qui me démontra que non. Retroussons nous donc les manches!

Déjà, il faut vérifier que votre résolveur fonctionne avec dnssec. Un petit "dig +dnssec" pour résoudre un nom qui supporte ce standard nous informe que c'est bon:

$ dig +dnssec http://www.dnssec-deployment.org/

; <<>> DiG 9.9.5-12.1-Debian <<>> +dnssec http://www.dnssec-deployment.org/
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 62323
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 0, AUTHORITY: 6, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 4096
;; QUESTION SECTION:
;http://www.dnssec-deployment.org/. IN  A

;; AUTHORITY SECTION:
.                       10797   IN      SOA     a.root-servers.net. nstld.verisign-grs.com. 2016011000 1800 900 604800 86400
.                       10797   IN      RRSIG   SOA 8 0 86400 20160120050000 20160110040000 54549 . MTlQxI92KqWfL5GsAk6eBL3T9KHsQIt3lWkC9jmiyYBl7+qCcOJUZOvG 4oEN1JizSsEhpp76oM0Fg9svRHryV5oZZr7Od2CHbI9jfuiYYPhvO16E o6EkPwoXZcnm6Y6JgLXKd/UOQK+J+WhlyqFP8swSgynv/FwvaRIFgkf/ cNw=
.                       10797   IN      RRSIG   NSEC 8 0 86400 20160120050000 20160110040000 54549 . uhX0FXBilUV7ZCP3sC/tPYVA5Srlu8MknbmGKZLLUf5FzRDH0tCk+HZb wy/2MGTnISsFnRftRgw4mR5tmBW6jgeYsR4PS46360GAqT1h5mvBAmHv gEE+5/g1kVsSi/MDJ075VduVHD+yMwCS5KZ/ynywsq7uj93gescPj+0V TbE=
.                       10797   IN      NSEC    aaa. NS SOA RRSIG NSEC DNSKEY
org.                    10797   IN      RRSIG   NSEC 8 1 86400 20160120050000 20160110040000 54549 . hPeKkCWTQbKtMBSDf/sGOnX1CHBXez4kuG2ulIc94USFgWR6Oz+R7OIs qCzjursXz+79hYd3HFrYFYX0KlWj08zpZHtTB4zz9TRoH1ep3ARoPWkR iyPDEkLgFyGlYnqQD3VuiIHQ478BUlgQ8vmKE/5REMsTXKIJxIG+kO95 YSs=
org.                    10797   IN      NSEC    organic. NS DS RRSIG NSEC

;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Sun Jan 10 15:40:46 GMT 2016
;; MSG SIZE  rcvd: 669

La présence de lignes RRSIG indique que la requête est revenue avec la signature demandée.

Maintenant, il suffit de configurer son résolveur pour que la résolution se fasse correctement. Avec bind9 sur Debian, c'est:

  • Ouvrir le fichier /etc/bind/named.conf.options
  • S'assurer que les 2 options dnssec-enable et dnssec-validation sont présentes
  • Mettre dnssec-enable à "yes" et dnssec-validation à "auto"

Mon fichier ressemble à cela:

options {
        directory "/var/cache/bind";
        dnssec-enable yes;
        dnssec-validation auto;
[...]

Notez que dnssec-validation doit être à "auto" et non à "yes", ce qui va lui permettre d'utiliser le fichier de signature présent avec la distribution. Avec l'option "yes", il faut en fournir un à la mimine.

Maintenant, redémarrez bind9. Un "dig www.dnssec-failed.org" devrait vous renvoyer un beau "status: SERVFAIL". Essayez également de visualiser le site depuis un navigateur, il devrait vous dire que le serveur n'a pu être trouvé. Yeah!

Petite astuce: assurez vous que le résolveur de noms de votre fournisseur est complètement ignoré, et pas simplement mis après votre résolveur local. Il m'a fallu enlever "domain-name-server" de mon dhclient.conf que la résolution DHCP ne charge pas le serveur DNS de mon fournisseur. Dans le doute, regardez votre resolv.conf.

samedi 19 septembre 2015

std::make_shared et allocations

J'avais rapidement et indirectement parlé de std::make_shared ici, et en particulier l'optimisation qui consiste à allouer la structure de contrôle avec l'objet lui même, économisant ainsi une allocation. Prouvons donc que la bibliothèque C++ standard fournie avec g++ le fait effectivement. Prenons un programme qui alloue 100 pointeurs partagés:

#include <memory>
#include <vector>

int main()
{
  std::vector<std::shared_ptr<int> > v;

  for(int i = 0; i < 100; ++i)
  {
    //v.push_back(std::make_shared<int>(5));
    v.push_back(std::shared_ptr<int>(new int(5)));
  }
  
  return 0;
}

Faisons tourner la chose avec valgrind. Quand la première ligne à l'intérieur de la boucle est commentée, c'est à dire que l'on alloue un entier, puis qu'on le passe à un pointeur partagé, valgrind nous indique ceci:

==4484== HEAP SUMMARY:
==4484==     in use at exit: 72,704 bytes in 1 blocks
==4484==   total heap usage: 209 allocs, 208 frees, 79,584 bytes allocated

Le chiffre important, ici, c'est les 209 allocations: 100 allocations d'entiers, 100 allocations de structure de contrôle, et j'aurais envie de dire 8 allocations pour le vecteur et 1 allocation en dehors de notre contrôle. Maintenant, commentons la deuxième ligne et décommentons la première. Valgrind nous dit alors:

==4491== HEAP SUMMARY:
==4491==     in use at exit: 72,704 bytes in 1 blocks
==4491==   total heap usage: 109 allocs, 108 frees, 79,184 bytes allocated

Yay! Nous avons effectivement une seule allocation dans la boucle au lieu de 2, le make_shared a donc bien alloué la structure de contrôle avec l'objet. À noter que la taille totale allouée est légèrement plus basse, peut-être grâce à des histoires d'alignement?

vendredi 21 août 2015

Gcc et link time optimisation

Comme promis, je vous parle un peu de cette récente fonctionnalité de gcc, qui permet au compilateur d'optimiser à travers les unités de compilation. Le compilateur de Microsoft avait cette fonctionnalité depuis bien longtemps, et gcc était donc un petit peu à la traine, mais à chaque release, de nouvelles améliorations sont apportées à ce qui pourrait bien donner un sérieux coup de fouet aux performances (à l'exécution, parce qu'en revanche, les temps de compilation vont exploser...).

L'idée est que plutôt que de compiler chaque unité de compilation vers du code machine, le compilo s'arrête à une représentation moins bas niveau. C'est cette représentation qui est passée à l'éditeur de liens, qui peut alors refaire une passe d'optimisation en ayant l'ensemble des unités de compilation disponibles. La plus évidente des optimisations possibles est d'inliner les fonctions d'une unité de compilation vers une autre unité de compilation.

Pour comprendre ce qui se passe, je vous propose deux programmes. Voici le premier :

// p1.c
int f(int a, int b)
{
  return a + b;
}

int main()
{
  return f(3, 5);
}

Voici le deuxième, divisé en 2 fichiers

// p2.c
int f(int a, int b);

int main()
{
  return f(3, 5);
}

// p2l.c
int f(int a, int b)
{
  return a + b;
}
Compilons le premier programme avec l'optimisation à fond : "gcc -O3 p1.c -o p1". Puis désassemblons le programme avec "objdump -d -S p1". La partie intéressante est :
00000000004003c0 <main>:
  4003c0:       b8 08 00 00 00          mov    $0x8,%eax
  4003c5:       c3                      retq   

Sans surprises, le compilo a bien inliné le tout, et renvoie simplement la valeur calculée 8.

Maintenant, le deuxième programme: "g++ -O3 p2.c p2l.c -o p2". Le code désassemblé montre bien le passage des deux paramètres 5 et 3 suivi d'un appel de fonction, puisque la fonction "main" ne peut pas voir le contenu de la fonction f.

0000000000400480 <main>:
  400480:       be 05 00 00 00          mov    $0x5,%esi
  400485:       bf 03 00 00 00          mov    $0x3,%edi
  40048a:       e9 01 01 00 00          jmpq   400590 <_Z1fii>

0000000000400590 <_Z1fii>:
  400590:       8d 04 37                lea    (%rdi,%rsi,1),%eax
  400593:       c3                      retq   
  400594:       66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
  40059b:       00 00 00 
  40059e:       66 90                   xchg   %ax,%ax

Et maintenant, voyons avec la lto: g++ -flto -O3 p2.c p2l.c -o p2_lto. Ah ah! Nous retrouvons quelque chose d'identique au tout premier programme. Le compilateur a en effet été capable d'inliner la fonction f dans main, quand bien même sa définition n'était pas accessible. Ça marche!

0000000000400480 <main>:
  400480:       b8 08 00 00 00          mov    $0x8,%eax
  400485:       c3                      retq