{"id":1136,"date":"2011-09-17T14:00:05","date_gmt":"2011-09-17T13:00:05","guid":{"rendered":"http:\/\/www.blaess.fr\/christophe\/?p=1136"},"modified":"2018-11-29T02:57:20","modified_gmt":"2018-11-29T01:57:20","slug":"efficacite-des-ipc-les-files-de-messages-posix","status":"publish","type":"post","link":"https:\/\/www.blaess.fr\/christophe\/2011\/09\/17\/efficacite-des-ipc-les-files-de-messages-posix\/","title":{"rendered":"Efficacit\u00e9 des IPC&nbsp;: les files de messages Posix"},"content":{"rendered":"<p style=\"text-align: justify;\">Un client m&rsquo;a demand\u00e9 r\u00e9cemment de le conseiller sur le choix d&rsquo;un m\u00e9canisme de communication entre processus pour transf\u00e9rer rapidement des donn\u00e9es entre deux applications. Il existe plusieurs syst\u00e8mes d&rsquo;IPC (<em>Inter Process Communication<\/em>), chacun avec ses avantages et inconv\u00e9nients, et j&rsquo;ai eu envie de les comparer pour d\u00e9terminer le plus rapide, en fonction du type de donn\u00e9es \u00e0 transf\u00e9rer. Ce premier article est consacr\u00e9 aux <em>message queues<\/em>, les files de messages.<\/p>\n<p>\n<!--more-->\n<\/p>\n<h1>Posix message queues<\/h1>\n<p style=\"text-align: justify;\">Dans l&rsquo;API standard Unix, il existe deux impl\u00e9mentations pour les files de message&nbsp;: les <em>message queue<\/em> Syst\u00e8me V et les <em>message queues<\/em> Posix. Les premi\u00e8res sont apparues dans les syst\u00e8mes Unix des ann\u00e9es 80. Elles \u00e9taient efficaces mais disposaient d&rsquo;une interface de programmation un peu bancale&nbsp;: il fallait r\u00e9server dans le bloc de donn\u00e9es \u00e0 envoyer quatre octets pour stocker la priorit\u00e9 du message. Ceci n&rsquo;est pas tr\u00e8s d\u00e9rangeant, mais pouvait n\u00e9cessiter quand m\u00eame une recopie syst\u00e9matique du message s&rsquo;il \u00e9tait produit par une biblioth\u00e8que ind\u00e9pendante par exemple. En outre, comme l&rsquo;ensemble des IPC Syst\u00e8me V, ces <em>message queues<\/em> ne s&rsquo;int\u00e9graient pas dans le concept g\u00e9n\u00e9ral des descripteurs de fichiers Unix.<\/p>\n<p style=\"text-align: justify;\">Dans les ann\u00e9es 90, avec la volont\u00e9 d&rsquo;uniformiser les interfaces de programmation des syst\u00e8mes Unix par l&rsquo;interm\u00e9diaire de la norme Posix, sont apparues de nouvelles files de message, dont l&rsquo;interface est plus simple, et dont les descripteurs sont plus proches de ceux des fichiers. Les <em>message queues<\/em> Posix n&rsquo;ont \u00e9t\u00e9 int\u00e9gr\u00e9es qu&rsquo;assez tardivement dans Linux (dans la version 2.6.10 si ma m\u00e9moire est bonne).<\/p>\n<p style=\"text-align: justify;\">Les appels-syst\u00e8me qui nous concernent sont&nbsp;:<\/p>\n<pre>#include &lt;mqueue.h&gt;\n\nmqd_t   <strong>mq_open<\/strong>    (const char * <em>nom<\/em>, int <em>flags<\/em>, mode_t <em>mode<\/em>, struct mq_attr * <em>attr<\/em>);\nint     <strong>mq_send<\/strong>    (mqd_t <em>mq<\/em>, const char * <em>msg<\/em>, size_t <em>lg<\/em>, unsigned int <em>prio<\/em>);\nssize_t <strong>mq_receive<\/strong> (mqd_t <em>mq<\/em>, char * <em>msg<\/em>, size_t <em>lg<\/em>, unsigned int * <em>prio<\/em>);\nint     <strong>mq_close<\/strong>   (mqd_t <em>mq<\/em>);\nint     <strong>mq_unlink<\/strong>  (const char * <em>nom<\/em>);<\/pre>\n<p style=\"text-align: justify;\">On peut se reporter aux pages de manuels respectives de ces fonctions pour avoir plus de d\u00e9tails.<\/p>\n<h2>Transfert \u00e0 faible d\u00e9bit<\/h2>\n<p style=\"text-align: justify;\">Voici un petit programme qui va nous permettre de mesurer le temps de transfert d&rsquo;un message d&rsquo;un processus \u00e0 un autre, pour des messages relativement courts (8 octets), avec un faible d\u00e9bit.<\/p>\n<p style=\"text-align: justify;\">Nous cr\u00e9eons deux processus, un \u00e9metteur qui envoie toutes les secondes dans la file un message contenant l&rsquo;heure actuelle (secondes + microsecondes) et un r\u00e9cepteur qui compare la valeur re\u00e7ue avec l&rsquo;heure qu&rsquo;il vient de lire lui-m\u00eame. Notez que sur la plupart des machines actuelles, la lecture de l&rsquo;heure \u00e0 l&rsquo;aide de <code>gettimeofday()<\/code> dure environ une micro-seconde. Le d\u00e9bit \u00e9tant faible (un message par seconde), nous pouvons afficher directement la diff\u00e9rence (en micro-secondes).<\/p>\n<p style=\"text-align: justify;\">Pour que le fonctionnement soit optimal, il conviendra de placer l&rsquo;\u00e9metteur et le r\u00e9cepteur sur deux c\u0153urs ou deux processeurs diff\u00e9rents.<\/p>\n<p style=\"text-align: justify;\">Voici le premier \u00e9metteur qui envoie r\u00e9guli\u00e8rement l&rsquo;heure dans la file de messages. Les fichiers-source ainsi que le fichier <code>Makefile<\/code> se trouvent dans <a title=\"http:\/\/www.blaess.fr\/christophe\/files\/article-2011-09-17\/Efficacite-message-queues-Posix.tar.bz2\" href=\"http:\/\/www.blaess.fr\/christophe\/files\/article-2011-09-17\/Efficacite-message-queues-Posix.tar.bz2\">cette archive<\/a>.<\/p>\n<pre><strong>emetteur-01.c<\/strong>:\n#include &lt;fcntl.h&gt;\n#include &lt;mqueue.h&gt;\n#include &lt;stdio.h&gt;\n#include &lt;stdlib.h&gt;\n#include &lt;unistd.h&gt;\n#include &lt;sys\/time.h&gt;\n\nint main(int argc,char * argv[])\n{\n    mqd_t mq;\n    struct timeval heure;\n\n    if (argc != 2) {\n        fprintf(stderr, \"usage: %s nom_file_message\\n\", argv[0]);\n        exit(EXIT_FAILURE);\n    }\n\n    mq = <strong>mq_open<\/strong>(argv[1], O_WRONLY | O_CREAT, 0600, NULL);\n    if (mq == (mqd_t) -1) {\n        perror(argv[1]);\n        exit(EXIT_FAILURE);\n    }\n    while (1) {\n        <strong>gettimeofday<\/strong>(&amp; heure, NULL);\n        <strong>mq_send<\/strong>(mq, (char *) &amp; heure, sizeof(heure), 1);\n        sleep(1);\n    }\n    return EXIT_SUCCESS;\n}<\/pre>\n<p style=\"text-align: justify;\">Et voici le premier r\u00e9cepteur. On notera que dans l&rsquo;API des <em>message queues<\/em> Posix, la fonction <code>mq_receive()<\/code> r\u00e9clame un buffer capable de contenir (au moins) le plus grand message susceptible d&rsquo;\u00eatre v\u00e9hicul\u00e9e par la file. Cette valeur est obtenue avec <code>mq_getattr()<\/code>.<\/p>\n<pre><strong>recepteur-01.c:<\/strong>\n#include &lt;fcntl.h&gt;\n#include &lt;mqueue.h&gt;\n#include &lt;stdio.h&gt;\n#include &lt;stdlib.h&gt;\n#include &lt;unistd.h&gt;\n#include &lt;sys\/time.h&gt;\n\nint main(int argc,char * argv[])\n{\n    mqd_t mq;\n    int taille;\n    char * buffer;\n    long int duree;\n    struct mq_attr attr;\n    struct timeval heure;\n    struct timeval * recue;\n\n    if (argc != 2) {\n        fprintf(stderr, \"usage: %s nom_file_message\\n\", argv[0]);\n        exit(EXIT_FAILURE);\n    }\n\n    mq = <strong>mq_open<\/strong>(argv[1], O_RDONLY | O_CREAT, 0600, NULL);\n    if (mq == (mqd_t) -1) {\n        perror(argv[1]);\n        exit(EXIT_FAILURE);\n    }\n\n    if (<strong>mq_getattr<\/strong>(mq, &amp; attr) != 0) {\n        perror(\"mq_getattr\");\n        exit(EXIT_FAILURE);\n    }\n    taille = attr.mq_msgsize;\n    buffer = malloc(taille);\n\n    if (buffer == NULL) {\n        perror(\"malloc\");\n        exit(EXIT_FAILURE);\n    }\n\n    recue = (struct timeval *) buffer;\n    while (1) {\n        <strong>mq_receive<\/strong>(mq, buffer, taille, NULL);\n        gettimeofday(&amp; heure, NULL);\n        duree  = heure.tv_sec - recue-&gt;tv_sec;\n        duree *= 1000000;\n        duree += heure.tv_usec - recue-&gt;tv_usec;\n        fprintf(stdout, \"%ld usec\\n\", duree);\n    }\n    return EXIT_SUCCESS;\n}<\/pre>\n<p style=\"text-align: justify;\">Pour l&rsquo;ex\u00e9cution, je lance sur un premier terminal&nbsp;:<\/p>\n<pre style=\"padding-left: 30px;\">$ <strong>taskset -c 0 .\/recepteur-01 \/essai<\/strong><\/pre>\n<p style=\"text-align: justify;\">Notez que le nom de la file de message (<code>essai<\/code>) doit obligatoirement \u00eatre pr\u00e9c\u00e9d\u00e9 d&rsquo;un slash &lsquo;<code>\/<\/code>&lsquo;.<\/p>\n<p style=\"text-align: justify;\">Puis sur un second terminal&nbsp;:<\/p>\n<pre style=\"padding-left: 30px;\">$ <strong>taskset -c 1 .\/emetteur-01 \/essai<\/strong><\/pre>\n<p style=\"text-align: justify;\">Les r\u00e9sultats s&rsquo;affichent dans le premier terminal&nbsp;:<\/p>\n<pre style=\"padding-left: 30px;\">$ <strong>taskset -c 0 .\/recepteur-01 \/essai<\/strong>\n16 usec\n14 usec\n14 usec\n15 usec\n13 usec\n12 usec\n13 usec\n15 usec\n14 usec\n<strong><em>(Contr\u00f4le-C)<\/em><\/strong>\n$<\/pre>\n<p style=\"text-align: justify;\">Sur ce syst\u00e8me &#8211; <em>Intel Core 2 Quad<\/em> avec Linux 2.6.38 g\u00e9n\u00e9rique Ubuntu &#8211; le temps de passage d&rsquo;un message sporadique est donc d&rsquo;une quinzaine de micro-secondes. Essayons \u00e0 pr\u00e9sent avec un d\u00e9bit plus \u00e9lev\u00e9.<\/p>\n<h2>Transfert d&rsquo;un message \u00e0 haut d\u00e9bit<\/h2>\n<p style=\"text-align: justify;\">Nous allons \u00e0 pr\u00e9sent envoyer des messages aussi vite que possible. Pour cela, nous retirons simplement le sommeil d&rsquo;une seconde de l&rsquo;exemple pr\u00e9c\u00e9dent&nbsp;:<\/p>\n<pre><strong>emetteur-02.c<\/strong>:\n#include &lt;fcntl.h&gt;\n#include &lt;mqueue.h&gt;\n#include &lt;stdio.h&gt;\n#include &lt;stdlib.h&gt;\n#include &lt;unistd.h&gt;\n#include &lt;sys\/time.h&gt;\n\nint main(int argc,char * argv[])\n{\n    mqd_t mq;\n    struct timeval heure;\n\n    if (argc != 2) {\n        fprintf(stderr, \"usage: %s nom_file_message\\n\", argv[0]);\n        exit(EXIT_FAILURE);\n    }\n\n    mq = <strong>mq_open<\/strong>(argv[1], O_WRONLY | O_CREAT, 0600, NULL);\n    if (mq == (mqd_t) -1) {\n        perror(argv[1]);\n        exit(EXIT_FAILURE);\n    }\n    while (1) {\n        <strong>gettimeofday<\/strong>(&amp; heure, NULL);\n        <strong>mq_send<\/strong>(mq, (char *) &amp; heure, sizeof(heure), 1);\n    }\n    return EXIT_SUCCESS;\n}<\/pre>\n<p style=\"text-align: justify;\">Pour la r\u00e9ception, le probl\u00e8me est plus compliqu\u00e9. Nous ne pouvons pas nous permettre de faire un <code>fprintf()<\/code> \u00e0 chaque message, car cela perturberait l&rsquo;ex\u00e9cution du programme. J&rsquo;ai donc d\u00e9cid\u00e9 d&rsquo;afficher r\u00e9guli\u00e8rement les dur\u00e9es minimale, maximale et moyenne sur une p\u00e9riode donn\u00e9e, de l&rsquo;ordre d&rsquo;une seconde (calcul\u00e9e avec les r\u00e9sultats pr\u00e9c\u00e9dents).<\/p>\n<pre><strong>recepteur-02.c:<\/strong>\n#include &lt;fcntl.h&gt;\n#include &lt;mqueue.h&gt;\n#include &lt;stdio.h&gt;\n#include &lt;stdlib.h&gt;\n#include &lt;unistd.h&gt;\n#include &lt;sys\/time.h&gt;\n\nint main(int argc,char * argv[])\n{\n    mqd_t mq;\n    int taille;\n    char * buffer;\n    struct mq_attr attr;\n    struct timeval heure;\n    struct timeval * recue;\n\n    int nb_messages;\n    long int duree;\n    long int duree_max;\n    long int duree_min;\n    long int somme_durees;\n\n    if (argc != 2) {\n        fprintf(stderr, \"usage: %s nom_file_message\\n\", argv[0]);\n        exit(EXIT_FAILURE);\n    }\n\n    mq = <strong>mq_open<\/strong>(argv[1], O_RDONLY | O_CREAT, 0600, NULL);\n    if (mq == (mqd_t) -1) {\n        perror(argv[1]);\n        exit(EXIT_FAILURE);\n    }\n\n    if (<strong>mq_getattr<\/strong>(mq, &amp; attr) != 0) {\n        perror(\"mq_getattr\");\n        exit(EXIT_FAILURE);\n    }\n\n    taille = attr.mq_msgsize;\n    buffer = malloc(taille);\n    if (buffer == NULL) {\n        perror(\"malloc\");\n        exit(EXIT_FAILURE);\n    }\n\n    recue = (struct timeval *) buffer;\n    while (1) {\n        nb_messages = 0;\n        duree_max = 0;\n        duree_min = -1;\n        somme_durees = 0;\n        do {\n            <strong>mq_receive<\/strong>(mq, buffer, taille, NULL);\n            gettimeofday(&amp; heure, NULL);\n            duree  = heure.tv_sec - recue-&gt;tv_sec;\n            duree *= 1000000;\n            duree += heure.tv_usec - recue-&gt;tv_usec;\n            if (nb_messages &gt; 0) { \/\/ Ignorer le premier message (retarde)\n                if (duree_max &lt; duree)\n                    duree_max = duree;\n                if ((duree_min == -1) || (duree_min &gt; duree))\n                    duree_min = duree;\n                somme_durees += duree;\n            }\n            nb_messages ++;\n        } while (nb_messages &lt; 100000); \/\/ arbitraire, de l'ordre de la seconde\n        fprintf(stdout, \"min =%3ld   max =%3ld moy=%5.1f\\n\",\n            duree_min, duree_max, ((float) somme_durees) \/ (nb_messages - 1));\n    }\n    return EXIT_SUCCESS;\n}<\/pre>\n<p style=\"text-align: justify;\">Comme pr\u00e9c\u00e9demment, je lance les deux processus sur deux c\u0153urs distincts. Voici les r\u00e9sultats&nbsp;:<\/p>\n<pre style=\"padding-left: 30px;\">$ <strong>taskset -c 0 .\/recepteur-02 \/essai<\/strong>\nmin =  1   max = 78 moy=  7.8\nmin =  1   max =102 moy=  6.9\nmin =  1   max = 65 moy=  7.5\nmin =  1   max = 61 moy=  7.4\nmin =  1   max = 70 moy=  5.5\nmin =  1   max = 64 moy=  6.6\nmin =  1   max = 63 moy=  5.7\nmin =  1   max = 75 moy=  5.5\nmin =  1   max = 84 moy=  5.6\nmin =  1   max = 71 moy=  6.8\nmin =  1   max = 68 moy=  6.3\nmin =  1   max =688 moy=  6.8\nmin =  1   max = 69 moy=  5.7\nmin =  1   max = 69 moy=  7.7\nmin =  1   max = 68 moy=  6.7\nmin =  2   max = 69 moy=  7.8\nmin =  2   max =102 moy=  6.7\nmin =  1   max = 70 moy=  5.6\nmin =  1   max = 73 moy=  6.3\nmin =  1   max = 63 moy=  6.2\nmin =  1   max = 71 moy=  4.8\nmin =  1   max = 87 moy=  5.5\nmin =  3   max = 74 moy=  6.4\nmin =  1   max = 71 moy=  4.1\nmin =  1   max = 71 moy=  8.1\nmin =  1   max = 80 moy=  5.8\nmin =  1   max = 76 moy=  7.2\nmin =  1   max =177 moy=  7.9\nmin =  1   max = 55 moy=  5.4\nmin =  2   max = 74 moy=  8.5\nmin =  4   max = 74 moy=  8.2\nmin =  3   max = 77 moy=  6.4\n^C\n$<\/pre>\n<p style=\"text-align: justify;\">En fait, le temps moyen lorsqu&rsquo;on envoie des messages fr\u00e9quents est plut\u00f4t de l&rsquo;ordre de 8 micro-secondes alors qu&rsquo;avec des messages rares, il \u00e9tait de 15 micro-secondes environ. Il y a naturellement de temps \u00e0 autres des temps de transferts plus long \u00e0 cause de l&rsquo;activit\u00e9 du syst\u00e8me. Si l&rsquo;on ordonnance en temps-r\u00e9el les deux t\u00e2ches, les dur\u00e9es maximales sont plus stables. Il faut toutefois obtenir les droits <em>root<\/em>.<\/p>\n<pre style=\"padding-left: 30px;\"># <strong>chrt -f 40 taskset -c 1 .\/emetteur-02 \/essai<\/strong><\/pre>\n<p>et sur l&rsquo;autre terminal<\/p>\n<pre style=\"padding-left: 30px;\">#  <strong>chrt -f 40 taskset -c 0 .\/recepteur-02 \/essai<\/strong>\nmin =  1   max = 80 moy=  6.1\nmin =  1   max = 67 moy=  6.3\nmin =  1   max = 64 moy=  5.3\nmin =  1   max = 70 moy=  8.2\nmin =  3   max = 67 moy=  8.8\nmin =  2   max = 62 moy=  5.7\nmin =  2   max = 62 moy=  7.8\nmin =  1   max = 59 moy=  7.5\nmin =  1   max = 63 moy=  6.6\nmin =  2   max = 63 moy=  6.4\nmin =  2   max = 66 moy=  7.2\nmin =  1   max = 58 moy=  5.9\nmin =  1   max = 55 moy=  6.4\nmin =  2   max = 65 moy=  8.5\nmin =  2   max = 71 moy=  9.0\nmin =  2   max = 81 moy=  8.5\nmin =  1   max = 63 moy=  7.5\nmin =  1   max = 66 moy=  8.1\nmin =  3   max = 69 moy=  8.7\nmin =  2   max = 64 moy=  7.2\nmin =  1   max = 62 moy=  5.7\nmin =  1   max = 63 moy=  7.2\nmin =  1   max = 65 moy=  8.2\nmin =  2   max = 64 moy=  7.2\nmin =  1   max = 68 moy=  6.0\nmin =  1   max = 69 moy=  6.3\nmin =  1   max = 60 moy=  6.5\nmin =  1   max = 67 moy=  7.3\nmin =  1   max = 62 moy=  7.1\nmin =  2   max = 62 moy=  8.6\nmin =  1   max = 57 moy=  6.7\nmin =  2   max = 72 moy=  8.7\nmin =  1   max = 85 moy=  7.9\nmin =  1   max = 72 moy=  7.1\nmin =  1   max = 58 moy=  5.0\nmin =  1   max = 64 moy=  5.8\nmin =  2   max = 73 moy=  7.4\nmin =  1   max = 55 moy=  7.1\nmin =  1   max = 62 moy=  7.3\nmin =  1   max = 66 moy=  8.3\nmin =  1   max = 58 moy=  5.2\nmin =  5   max = 75 moy=  9.3\nmin =  3   max = 68 moy=  9.1<\/pre>\n<p style=\"text-align: justify;\">Nos messages sont tr\u00e8s courts, deux entiers de 32 bits, soient 8 octets en tout. J&rsquo;ai fait quelques exp\u00e9riences en augmentant leur taille (jusqu&rsquo;\u00e0 64 ko), sans constater de diff\u00e9rence sensible de temps de passage d&rsquo;un message.<\/p>\n<h1>Conclusion<\/h1>\n<p style=\"text-align: justify;\">Nous pouvons donc estimer que sur ce syst\u00e8me le temps de transfert d&rsquo;un message d&rsquo;un processus \u00e0 l&rsquo;autre par les files Posix est d&rsquo;environ 8 micro-secondes en moyenne, avec des pics de quelques dizaines de micro-secondes. Nous comparerons ces valeurs avec d&rsquo;autres m\u00e9canismes IPC dans les prochains articles.<\/p>","protected":false},"excerpt":{"rendered":"<p>Un client m&rsquo;a demand&eacute; r&eacute;cemment de le conseiller sur le choix d&rsquo;un m&eacute;canisme de communication entre processus pour transf&eacute;rer rapidement des donn&eacute;es entre deux applications. Il existe plusieurs syst&egrave;mes d&rsquo;IPC (Inter Process Communication), chacun avec ses avantages et inconv&eacute;nients, et j&rsquo;ai eu envie de les comparer pour d&eacute;terminer le plus rapide, en fonction du type [&hellip;]<\/p>","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8,14],"tags":[],"class_list":["post-1136","post","type-post","status-publish","format-standard","hentry","category-linux-2","category-temps-reel"],"_links":{"self":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/1136","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/comments?post=1136"}],"version-history":[{"count":1,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/1136\/revisions"}],"predecessor-version":[{"id":5427,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/1136\/revisions\/5427"}],"wp:attachment":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/media?parent=1136"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/categories?post=1136"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/tags?post=1136"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}