You are here -> Home steckdenis's journals » Quelques jours/semaines de silence

Quelques jours/semaines de silence

Le 7/01/2010 à 17h03 by steckdenis, See the Journals, 10 commentaries

Bonjour :) ,

Vous avez peut-être remarqué que Setup ne bouge pas beaucoup depuis quelques jours, depuis la Noël environ. C'est tout à fait normal et le développement continue tout aussi rapidement. Simplement, ce développement n'est pas encore du code.

Setup dispose de deux «killer features», des fonctionnalités qui font que Logram l'utilise, pas Pacman, pas APT, pas RPM, mais bien Setup et toujours Setup :

  • La base de donnée binaire, dont le format facilite tellement les choses que la partie «gestion des paquets» proprement dite ne fait que 1000 lignes, plus quelques centaines pour remplir cette base, alors que les autres gestionnaires de paquets ont facilement 10 000 lignes ou plus dédiées à cette gestion. Son temps d'accès O(1) est également un très fort attout, lançant la tant attendue «informatique sans délai», promise depuis si longtemps, au fur et à mesure que les ordinateurs accélèrent.
  • Le solveur de dépendances, utilisant un système d'arbres et de branches. Aidé par la base de donnée binaire, il résoud les dépendances en consommant très peu de mémoire, très rapidement, et fournit la liste des solutions possibles.

Dans quelques jours, ou dans quelques semaines, une nouvelle fonctionnalité s'ajoutera à la liste : les delta packages.

Les Paquets Deltas

Ce type de paquet contient les différences entre la version A et la version A+1 d'un paquet. Il permet à l'utilisateur de ne pas avoir à télécharger un énorme fichier, et permet à Logram de mettre à jour les paquets bien plus souvent, en générant moins de traffic sur le web, donc en utilisant au mieux cette bande passante (téléchargement des LiveCD plus rapides, meilleure navigation sur le site web, etc).

Le format de ces paquets «delta» est déjà fixé, et tout est déjà bien avancé. Ces paquets seront une simple archive .tar.xz, d'extension .dpk (Delta PacKage), et contiendront un arbre Linux (/bin, /usr, etc). Cet arbre sera remplit de fichiers deltas ou normaux :

  • Si un fichier n'a pas été modifié entre la version A et la version A+1 du paquet, alors il ne se trouve pas dans l'archive.
  • Si un fichier a été ajouté entre A et A+1, son contenu se trouve dans l'archive
  • Si un fichier a été modifié, alors seule la différence est stockée
  • Si un fichier a été supprimé, rien ne se trouve dans l'archive.

Un fichier deltapackage.list se trouvera à la racine de cette archive, et contiendra quelques métadonnées, comme la liste des fichiers (tel fichier est nouveau, tel fichier est modifié, tel fichier est supprimé). Ainsi, on pourra savoir avec précision quels sont les changements entre deux versions, quels fichiers patcher, etc.

Toute la difficulté, l'algorithme de diff

Le fichier de type «différences entre deux fichiers» est le plus compliqué. Il existe pas mal de programmes permettant de créer ces patches, et de les appliquer, mais ils ne sont pas tous aussi efficaces qu'ils ne le devraient.

Le plus efficace, pour l'instant, est bsdiff, qui génère de très petits patches. Malheureusement, l'application du patch est lente et consomme beaucoup de mémoire.

Courgette, créé par Google, ne peut servir que pour les éxécutables et nécessite un ré-assemblage. Immensément trop lent et trop peu portable pour une distribution comme Logram.

xdelta et les autres ne sont pas assez efficaces, même s'ils sont plus rapides que bsdiff.

Les quelques recherches sur le sujet que j'ai faites sont très prometteuses, car voici le menu :

  • Format de diff compact et bien compressable (un diff non-compressé peut être gros, mais un petit tour de XZ le réduit largement)
  • Application du patch hyper rapide. bsdiff patche un fichier en O(n+m), ou n est la taille de l'ancien fichier, et m la taille du nouveau. Mon algo applique le patch en O(d), où d est la taille du patch. Bien plus rapide. Il consomme aussi O(1) octets en mémoire, c'est à dire toujours la même chose quelque soit la taille du patch, alors que bsdiff nécessite n+m+O(1) octets, à nouveau avec n et m la taille des fichiers.
  • Les algos de diffs utilisés ont un petit problème avec les fichiers contenant beaucoup de différences. En effet, ils utilisent un système d'insertion et de suppression. Setup utilisera des insertions, suppressions et opérations mathématiques. Ainsi, au lieu d'avoir par exemple -3+abc pour changer bca en abc, ce qui prend 5 octets, le patch contiendra :xyz, où x sont des octets tel que b+x = a, c+y = b et a+z = c. C'est possible car l'arithmétique de nos ordinateurs fonctionne par excès (255+1 = 0). Je sais que c'est une bonne technique car elle se trouve dans la thèse du créateur de bsdiff, ici, en bas de la page
  • Utilisation d'un certain procédé que je ne dévoilerai pas ici avant quelques tests et, si c'est révolutionnaire, le nécessaire pour éviter qu'un brevet ne soit déposé dessus. En effet, le web a des oreilles :-° .

Grâce à ce dernier procédé, que je ne révèle pas (quelques jours de Googlage vous le donnent), au format utilisé, et un peu de chance, Setup risque bien de produire non-pas des diffs plus petits que la concurrence, mais bien les plus petits diffs possibles. L'algo n'est pas trop trop compliqué, mais utilise uniquement des opérations rapides, et exactes (alors que les autres éliminent des données quand elles ne leur plaisent pas).

Setup Diff + XZ risque bien d'être très léger. Pour le moment, un diff de 17 octets permet de passer de la première de ces chaînes à la deuxième :

  • Bonjour, je suis un tres joli fichier, vraiment super joli :D !
  • Bonjour, je suis un joli fichier, vraiment super super joli ^^' !!

Ok, c'est léger, mais bon, c'est un diff fait main aussi :-° (oui, l'appliqueur est déjà fait).

Donc, en clair, ça va prendre pas mal de temps. J'ai déjà trouvé quels algos je vais utiliser, comment les utiliser, mais je dois encore arranger certains soucis techniques, pour que ce soit suffisemment rapide pour gérer une dizaine de diffs par paquets (histoire qu'avec un diff, on puisse passer de la version A à A+3, si jamais on a pris du retard dans les mises à jour).

Commentaries

Author Message
linkdd
# le 10/01/2010 à 13h05
Logram, c'est la liberté, la liberté d'enlever KDE
Avatar
Group : Packageur

Les delta-packages. Quelle idée... hum je ne trouve pas le mot, pas grave on va dire "génial". Ainsi je pourrais faire mes mises à jour sans devoir stopper mes téléchargements car ils ralentissent tout :-°

Continu ses petites optimisations qui fera de Setup un gestionnaire de paquet hors norme qui sera utilisé par ton geek préféré ;)

Au passage, désolé de pas pouvoir participer plus, en ce moment je me dégeekise ^^

Il vaut mieux se taire et passer pour un con que l'ouvrir et ne laisser aucun doute

steckdenis
# le 10/01/2010 à 13h21
Ça marche !
Avatar
Group : Administrateur

Au passage, désolé de pas pouvoir participer plus, en ce moment je me dégeekise ^^

Hum hum hum :-° . Aurais-tu trouvé une certaine affinité avec une certaine personne du sexe opposé, et te forçant à plus sortir que coder ?

Ou alors c'est la neige qui tombe chez toi et qui te donne super envie de faire de la luge dehors ?

Merci en tous cas de trouver Setup intéressant. L'algo de diff avance, j'ai un truc qui marche en Python, je le passe en C tout de suite (C++ = trop lent, mais y'aura tout de même un peu de Qt dedans là où ça optimise, comme changer des mallocs en utilisation de QVector qui malloc les enregistrements par groupes).

PS: Au passage, ça existe dans presque tous les gestionnaires de paquets de vrais distributions, comme les Delta RPM, le truc que Debian essaie d'utiliser mais n'y arrive pas, même Pacman le gère :-° .

Vous ne comprenez pas ce que je dis ?

steckdenis
# le 11/01/2010 à 18h24
Ça marche !
Avatar
Group : Administrateur

Bonjour :) ,

J'ai quelques nouvelles : ça avance. Une étape importante du développement du point non dévoillé vient d'avoir été sauvée, ça marche et c'est rapide (enfin, relativement : 2 fois plus lent que bsdiff, mais c'est du C non-optimisé, ce sera plus rapide en C++ avec les classes genre QList qui évitent un certain nombre de petits mallocs).

Normalement, je pourrai vous dévoiler l'algo après-demain, ou plus tard. Sortie dans Setup en production pour dans deux semaines en même temps.

Ensuite, consolidation de Setup (fichiers .setup_bak quand l'utilisateur a modifié un fichier qu'on souhaite installer par exemple). Puis, Setup bêta1, et ainsi de suite, jusqu'aux -rc, puis enfin Setup 0.1 FINAL :D !

Vous ne comprenez pas ce que je dis ?

steckdenis
# le 20/01/2010 à 16h28
Ça marche !
Avatar
Group : Administrateur

Re tout le monde :) .

Normalement, publication du procédé ce week-end, toutes les étapes nécessaires à la création du diff marchent.

Hier, un algo O(n^n) ( :euh: ) a réussi à produire le plus petit diff possible entre MISSISSIPPI et MISOUSSISSITTI : C2 02 4F 55 53 C4 82 04 04 00.

Court non ? :) . Bon allez, voici ce que ça veut dire :

  • Saute 3 octets
  • Insère «OUS»
  • Saute 5 octets
  • change PP en TT (opération arithmétique)

Malheureusement, c'est super lent.

J'ai alors trouvé un autre algo, qui est simple et normalement rapide. Je vous donnerai des nouvelles.

Vous ne comprenez pas ce que je dis ?

danman
# le 20/01/2010 à 19h30
Heureux d'être là
Group : Member

lent pour le créer ou pour l'appliquer ?

très bon travail =), j'ai hâte de virer pacman pour mettre setup =) .

steckdenis
# le 20/01/2010 à 20h16
Ça marche !
Avatar
Group : Administrateur

À créer. L'application est quasi instantanée : O(n), ou n est le nombre d'instructions dans le patch (donc plus petit que la taille du patch).

Le code de l'application (dans le sens "appliquer le patch", l'appli C entière fait 1000 lignes au moins) :

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
 while (1)
{
    // Lire la commande
    ret = read(patchfd, &c, 1);
    pos++;

    if (ret == 0)
    {
        // Fin de fichier
        break;
    }
    else if (ret < 0)
    {
        close(patchfd);
        close(filefd);
        err("Impossible de lire la commande");
    }

    // Commande
    command = (c >> 6);
    arg = (c & 0x3F);
    arg++;          // Le 0 n'est pas utilisé (ça ne sert à rien), 
                    // mais codé, alors on diffère pour l'utiliser quand-même

    // Éxécuter la commande
    switch (command)
    {
        case OP_INSERT: /* Insertion */
            // Arg = nombre d'octets du patch à écrire dans la sortie
            ret = read(patchfd, buffer, arg);
            pos += arg;

            CHK_RET_READ

            ret = write(1, buffer, arg);

            CHK_RET_WRITE
            break;

        case OP_REMOVE: /* Suppression */
            // Arg = nombre d'octets de filefd à sauter, avec seek()
            if (lseek(filefd, arg, SEEK_CUR) == -1)
            {
                close(patchfd);
                close(filefd);
                err("Impossible d'avancer dans le fichier source");
            }
            break;

        case OP_OP: /* Opération arithmétique */
            // Arg = nombre d'octets à additionner entre eux
            for (counter=0; counter<arg; counter++)
            {
                // Lire un octet du patch dans c
                ret = read(patchfd, &c, 1);
                pos++;

                if (ret < 1)
                {
                    close(patchfd);
                    close(filefd);
                    err("Impossible de lire un octet dans le patch");
                }

                // Lire un octet du fichier dans c2
                ret = read(filefd, &c2, 1);

                if (ret < 1)
                {
                    close(patchfd);
                    close(filefd);
                    err("Impossible de lire un octet dans le fichier d'entrée");
                }

                // Additionner les deux dans un uint
                summ = (unsigned int)c;
                summ += (unsigned int)c2;

                // Écrire l'octet de poids faible dans la sortie
                ret = write(1, &summ, 1);

                if (ret < 1)
                {
                    close(patchfd);
                    close(filefd);
                    err("Impossible d'écrire un octet dans la sortie");
                }
            }
            break;

        case OP_SKIP: /* Avancer dans le fichier */
            // Arg = nombre d'octets de filefd à écrire dans stdout
            ret = read(filefd, buffer, arg);

            CHK_RET_READ

            ret = write(1, buffer, arg);

            CHK_RET_WRITE
            break;
    }
}

Simple non ? Normalement, vu comment c'est codé, il doit y avoir moyen d'appliquer 50Mio de patch par seconde, donc vraiment très très rapide :) .

Vous ne comprenez pas ce que je dis ?

danman
# le 22/01/2010 à 20h32
Heureux d'être là
Group : Member

toi qui est si perfectionniste, je te reconnais pas :-° .

il parait que c'est plus rapide de faire ++i; que i++; et d'utiliser des if else au lieu des switchs

ça fait gagner quelques millionièmes mais il y a un début à tout xD .

steckdenis
# le 22/01/2010 à 22h32
Ça marche !
Avatar
Group : Administrateur

Exactement :D ... en C++ :-° .

Le code que j'ai posté vient de l'appli proof-of-concept en C ANSI pur, hors de Setup. Là, le i++ est conseillé, car il n'y a pas des histoires de operator++() appelés comme en C++.

Et pour les switchs, c'est une simple question de practicité. Généralement, j'en utilise aussi en C++, car ils sont bien plus rapides (le compilo sait qu'on compare avec des constantes, donc optimise ça avec des tables, génères des warnings quand on oublie une valeur d'une enum et qu'on n'a pas de default, etc). Par contre, ils n'acceptent justement que des constantes, et peu de machins sont constants en C++. En effet,

1
if (a == "machin") { }

n'est absolument pas constant, car si a est une QString, ou même std::string, ce n'est pas une bête comparaison ("cmp + je" en assembleur), mais bien operator==() qui est appelé. Et ça, le switch n'aime pas.

Il y a aussi que les switchs sont moins lisibles à mon gout, donc dans un chemin de code qui est déjà lent, le milliardième de seconde gagné ne vaut pas un niveau d'indentation en plus (tu ne trouveras pas un switch dans un if dans un while dans un if dans un for dans une fonction dans une classe dans un namespace :-° ).

Et toc :-° !

Editing

Vous ne comprenez pas ce que je dis ?

danman
# le 22/01/2010 à 23h09
Heureux d'être là
Group : Member

:-° heureusement j'ai dit "il parait" :magician: .

steckdenis
# le 4/02/2010 à 20h04
Ça marche !
Avatar
Group : Administrateur

Hello à tous ceux qui peuvent suivre ce sujet :) .

Bonne nouvelle, je touche enfin à mon but. Après pas mal de recherches, j'ai trouvé un algorithme extrêmement rapide ( O(N) quasiment :D ) qui permet de générer des patches 50% plus petits que bsdiff :D .

Autrement dit, je suis hyper heureux.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ setup diff fl.old fl.new
�OUSă
$ setup diff fl.old.bak fl.new.bak 
�D�super ă$▒�!.
$ time setup diff metadata.old metadata.new > metadata.patch

real    0m0.015s
user    0m0.007s
sys     0m0.007s
$ time bsdiff metadata.old metadata.new metadata.bspatch

real    0m0.046s
user    0m0.003s
sys     0m0.003s
$ ls -l metadata.{bs,}patch
-rw-r--r-- 1 steckdenis users 197 04.02.2010 19:29 metadata.bspatch
-rw-r--r-- 1 steckdenis users 191 04.02.2010 19:28 metadata.patch
$ #Maintenant, on compresse les deux
$ xz metadata.bspatch 
$ xz metadata.patch
$ ls -l *.xz
-rw-r--r-- 1 steckdenis users 248 04.02.2010 19:29 metadata.bspatch.xz
-rw-r--r-- 1 steckdenis users 104 04.02.2010 19:28 metadata.patch.xz

On voit donc que pour passer des métadonnées entre initng~0.6.99+git20091106~1 et ~3, Setup crée un patch fortement compressible de 191 octets alors que bsdiff en crée un de 197 octets.

On voit donc que les deux sont quasiment optimaux. Seule différence, le format de Setup est non-compressé, ce qui veut qu'il sera très bien compressé dans les paquets (oui, dans un paquet .delta, chaque fichier sera dans son delta, et non un delta pour le tout, c'est bien plus rapide à créer). Comme bsdiff se repose sur BZip2, un ensemble de fichiers .bspatch sera largement moins bien compressé qu'un ensemble de fichiers .spatch :) .

Donc, quand on prend le bsdiff le plus petit, c'est à dire non-compressé, et le patch Setup le plus petit, on se rend compte qu'entre 197 octets et 104 octets, il y a un gain de 48%.

Mieux, si je compresse le patche de Setup avec gzip, j'obtiens un fichier de 55 octets :O ! C'est un gain de 72% ! Bon, sur de gros patches, XZ sera peut-être plus efficace. Un autre test, avec "xz -F raw", qui permet de s'afranchir des en-têtes, me donne un fichier de 45 octets.

Bref, mon algo de diff est bien le plus performant, et de largement très loin, sur cet exemple (un fichier de texte avec peu de modifications). Je dois encore voir s'il bat bsdiff sur de gros fichiers binaires. Avant cela, je dois corriger un bug qui lui fait consommer plein de mémoire (environ 1,5 Gio pour un fichier de 400Kio, quand-même :-° ).

Normalement, maintenant c'est la bonne. Publication de l'algo ce week-end si tout va bien. J'ai déjà créé la branche "delta" dans le dépôt Git, mais il n'y a encore rien dedans :-° .

Patience, patience. La 3eme killer feature de Setup arrive :) .

Vous ne comprenez pas ce que je dis ?