GaMa a écrit 448 commentaires

  • # Sans lib, avec visualisation et deuxième partie non bourrine

    Posté par  (site web personnel) . En réponse au message Avent du Code, jour 12. Évalué à 2.

    J'ai beaucoup aimé l'exo du jour.

    Comme beaucoup (tout le monde), un petit algo de parcours de graph pour trouver le chemin le plus court.

    J'ai fait une première version "naïve" qui priorisait pas les chemin et donc avançait par "inondation" (D'où le nom de fonction flood_map). J'avais un "front" (le point de départ au début) et à chaque passe je calculais un nouveau front (les cases accessibles à partir du front actuel). Dès qu'on arrive à l'arrivée, on sait combien il faut d'étapes.
    Ça marche très bien, et ça a l'avantage d'être la valeur exacte.

    Et je me suis amuser à faire du A*, et donc explorer une seule case du front (au lieu de toutes) et de choisir la case à privilégier en fonction d'un heuristique.
    C'est beaucoup plus efficace en terme de performance (nb de case étudiées) mais ça ne donne pas forcement le résultat le plus optimal selon l'heuristique utilisé. (Heureusement que j'avais la première version)

    Pour la partie deux, l'astuce c'est de partir de la fin et de s'arrêter dès qu'on trouve une case 'a'. Ça évite de faire du brute force à partant de toutes les cases a.

    Voici la version A* :
    (Vous pouvez le lancer avec votre input pour avoir une petite animation)

    #!/usr/bin/python3
    
    from enum import Enum
    from time import sleep
    from math import sqrt
    import sys
    
    def yield_input():
        import sys
        with open(sys.argv[1]) as f:
            for l in f:
                l = l.strip()
                yield l
    
    class Seen(Enum):
        NONE = 0
        CURRENT = 1
        SEEN = 2
    
    current_color = None
    
    def print_color(color, out):
        global current_color
        if current_color != color:
            current_color = color
            print(color, end="")
        print(out, end="")
    
    class Cell:
        def __init__(self, height, coord):
            self.height = height
            self.coord = coord
            self.seen = Seen.NONE
            self.path_length = 0
            self.previous = None
            self.is_on_the_way = False
    
        # Yield other if we can go there from self
        def yield_check_up(self, other, what=Seen.NONE):
            #print(f"{other.seen} {other.height} {self.height}")
            if other.seen == what and other.height <= self.height + 1:
                other.path_length = self.path_length+1
                other.seen = Seen.CURRENT
                yield other
    
        def yield_check_down(self, other, what=Seen.NONE):
            #print(f"{other.seen} {other.height} {self.height}")
            if other.seen == what and other.height >= self.height - 1:
                other.path_length = self.path_length+1
                other.seen = Seen.CURRENT
                yield other
    
        def print(self):
            global current_color
            if self.seen == Seen.NONE:
                color = "\033[40m"
            elif self.seen == Seen.CURRENT:
                color = "\033[46m"
            else:
                color = "\033[44m"
            if self.is_on_the_way:
                color = "\033[41m"
            char = chr(ord('a')+self.height)
            print_color(color, char)
    
    def move_up(coord):
        return coord[0]-1, coord[1]
    
    def move_down(coord):
        return coord[0]+1, coord[1]
    
    def move_left(coord):
        return coord[0], coord[1]-1
    
    def move_right(coord):
        return coord[0], coord[1]+1
    
    
    class Map:
        def __init__(self):
            self.map = []
            for i, line in enumerate(yield_input()):
                map_line = []
                for j, h in enumerate(line):
                    if h == "S":
                        self.start = (i, j)
                        height = 0
                    elif h == "E":
                        self.end = (i, j)
                        height = 25
                    else:
                        height = ord(h) - ord('a')
                    map_line.append(Cell(height, (i,j)))
                self.map.append(map_line)
                self.nb_lines = len(self.map)
                self.nb_columns = len(self.map[0])
            print(self.nb_lines, self.nb_columns)
    
        def get(self, coord):
            return self.map[coord[0]][coord[1]]
    
        def clear(self):
            for line in self.map:
                for cell in line:
                    cell.seen = Seen.NONE
                    cell.path_length = 0
                    cell.previous = None
                    cell.is_on_the_way = False
    
        # Direction is up or down
        def see_arround(self, cell, direction, what=Seen.NONE):
            attr = f"yield_check_{direction}"
            coord = cell.coord
            if coord[0]> 0:
                yield from getattr(cell, attr)(self.get(move_up(coord)), what)
            if coord[0] < self.nb_lines-1:
                yield from getattr(cell, attr)(self.get(move_down(coord)), what)
            if coord[1] > 0:
                yield from getattr(cell, attr)(self.get(move_left(coord)), what)
            if coord[1] < self.nb_columns-1:
                yield from getattr(cell, attr)(self.get(move_right(coord)), what)
    
        def flood_map(self, start, scoring, check, direction):
            to_see = [self.get(start)]
            round = 0
            while True:
                cell, to_see = to_see[0], to_see[1:]
                if check(cell):
                    return cell, round
                cell.seen = Seen.SEEN
                for next_cell in list(self.see_arround(cell, direction)):
                    next_cell.previous = cell
                    to_see.append(next_cell)
                to_see.sort(key=scoring)
                round += 1
                self.print(round)
    
        def find_a_way_to_summit(self):
            # Scoring is important.
            # A* found quickly A solution, but it may not the best one.
    
            # This scoring is fast but not exact:
            # Result:422, round:1460
            scoring = lambda c: c.path_length + abs(c.coord[0]-self.end[0])**2 + abs(c.coord[1]-self.end[1])**2
    
            # This scoring give more importance to path_length and give the good result
            # Result:412, round:3722
    #        scoring = lambda c: c.path_length + sqrt(abs(c.coord[0]-self.end[0])**2 + abs(c.coord[1]-self.end[1])**2)
            return self.flood_map(self.start, scoring, lambda cell: cell.coord == self.end, "up")
    
        def find_path(self, end):
            current = end
            while current:
                #0print(current.coord)
                current.is_on_the_way = True
                current = current.previous
                self.print(5)
    
        def find_shorter_path_to_summit(self):
            # This scoring is fast but not exact:
            #Result:410, round: 2276
            scoring = lambda c: c.height
    
            # This scoring give more importance to path_length and give the good result
            # Result:402, round:2400
    #        scoring = lambda c: c.path_length + c.height
            return self.flood_map(self.end, scoring, lambda cell: cell.height == 0, "down")
    
        def print(self, round):
            global current_color
            # clear the terminal
            print("\033c", end="")
            current_color = None
            print(round)
            for line in self.map:
                for cell in line:
                    cell.print()
                print()
            sleep(0.005)
    
    
    
    def round1(map):
        cell, nb_round = map.find_a_way_to_summit()
        map.find_path(cell)
        map.print(cell.path_length)
        return cell.path_length, nb_round
    
    def round2(map):
        cell, nb_round = map.find_shorter_path_to_summit()
        map.find_path(cell)
        map.print(cell.path_length)
        return cell.path_length, nb_round
    
    
    def main():
        map = Map()
        result_1 = round1(map)
        map.clear()
        result_2 = round2(map)
        print("Round 1 :", result_1)
        print("Round 2 :", result_2)
        print(map.nb_lines, map.nb_columns)
    
    
    if __name__ == "__main__":
        main()

    Matthieu Gautier|irc:starmad

  • # Plus simple

    Posté par  (site web personnel) . En réponse au message Avent du Code, jour 10. Évalué à 3.

    J'ai trouvé ce jour assez facile.
    J'ai pas modélisé un CPU complet comme Tanguy, c'est un peu l'enclume pour écraser la mouche.
    L'astuce pour rester simple, c'est de remplacer une instruction add (sur deux cycles) par une instruction noop et une instruction add (sur un cycle). Après ça roule tout seul.

    #!/usr/bin/python3
    
    def yield_input():
        import sys
        with open(sys.argv[1]) as f:
            for l in f:
                l = l.strip()
                yield l
    
    def yield_command():
        for line in yield_input():
            if line == "noop":
                yield None
            if line.startswith("addx"):
                v = int(line[5:])
                yield None
                yield v
    
    def round_1():
        X = 1
        result = 0
        for cycle, command in enumerate(yield_command(), 1):
            if cycle in (20, 60, 100, 140, 180, 220):
                result += cycle*X
            if command is not None:
                X += command
    
        print("Round 1 :", result)
    
    def round_2():
        X = 1
        result = 0
        for cycle, command in enumerate(yield_command()):
            current_pos = cycle%40
            char = "#" if abs(current_pos-X) <= 1 else " "
            end = "\n" if current_pos == 39 else ""
            print(char, end=end)
            if command is not None:
                X += command
    
    round_1()
    round_2()

    Matthieu Gautier|irc:starmad

  • # Ma solution

    Posté par  (site web personnel) . En réponse au message Avent du Code, jour 9. Évalué à 3.

    Et voici ma solution. Et sans énumérer tous les cas de mouvement de suivi<

    #!/usr/bin/python3
    
    def yield_input():
        import sys
        with open(sys.argv[1]) as f:
            for l in f:
                l = l.strip()
                yield l
    
    class Coord:
        def __init__(self):
            self.x = 0
            self.y = 0
    
        def up(self):
            self.x += 1
    
        def down(self):
            self.x -= 1
    
        def right(self):
            self.y += 1
    
        def left(self):
            self.y -= 1
    
        def follow(self, other):
            x_dist = abs(self.x-other.x)
            y_dist = abs(self.y-other.y)
            if x_dist <= 1 and y_dist <= 1:
                return
            if x_dist == 0:
                self.y += 1 if other.y>self.y else -1
            elif y_dist == 0:
                self.x += 1 if other.x>self.x else -1
            else:
                self.y += 1 if other.y>self.y else -1
                self.x += 1 if other.x>self.x else -1
    
        def as_tuple(self):
            return (self.x, self.y)
    
    class Rope:
        def __init__(self, length):
            self.tails_positions = set()
            self.knots = []
            for _ in range(length):
                self.knots.append(Coord())
            self.tails_positions.add(self.knots[-1].as_tuple())
    
        def up(self):
            self.knots[0].up()
            self.follow()
    
        def down(self):
            self.knots[0].down()
            self.follow()
    
        def right(self):
            self.knots[0].right()
            self.follow()
    
        def left(self):
            self.knots[0].left()
            self.follow()
    
        def follow(self):
            for i, knot in enumerate(self.knots[1:]):
                knot.follow(self.knots[i])
            self.tails_positions.add(self.knots[-1].as_tuple())
    
    command_map = {
        'U': "up",
        'D': "down",
        'R': "right",
        'L': "left",
    }
    
    def main():
        rope1 = Rope(2)
        rope2 = Rope(10)
    
        for command in yield_input():
            direction, number = command.split()
            for _ in range(int(number)):
                getattr(rope1, command_map[direction])()
                getattr(rope2, command_map[direction])()
    
        print("Round 1 :", len(rope1.tails_positions))
        print("Round 2 :", len(rope2.tails_positions))
    
    
    if __name__ == "__main__":
        main()

    Matthieu Gautier|irc:starmad

  • [^] # Re: Utilisateur fidèle

    Posté par  (site web personnel) . En réponse à la dépêche Fedora Linux 37 est sortie. Évalué à 6.

    C'est probablement du à la suppression de la taille 67% pour les icônes dans nautilus. https://discourse.gnome.org/t/icon-view-sizes-in-nautilus-43/11240/58

    Tu devais utiliser la taille 67% qui était suffisamment zoomée pour afficher 2 infos et pas trop pour la taille des icônes.

    Matthieu Gautier|irc:starmad

  • [^] # Re: cotar

    Posté par  (site web personnel) . En réponse à la dépêche Jubako et Arx, un conteneur universel et son format d’archive. Évalué à 6.

    Je connaissait pas. Mais ça répond à un besoin différent.

    Le but de cotar, c'est de payer moins chez amazon S3 qui facture (entre autres) au nombre de fichiers.
    Le but est de tout mettre dans un tar (et pas un tar.gz) et d'avoir effectivement un table de hash en local (ou téléchargée sur le moment) pour savoir dans quel range il faut lire le fichier dans le tar.

    Mais ça marche que sur des tar non compressé. Et t'as pas de "discovery", la table de hash ne contient pas le path des fichiers.

    C'est utile pour gruger un peu amazon S3, mais ça a peu d'utilité en local où tu veux compresser tes données.

    Matthieu Gautier|irc:starmad

  • [^] # Re: Une base de données ?

    Posté par  (site web personnel) . En réponse à la dépêche Jubako et Arx, un conteneur universel et son format d’archive. Évalué à 3.

    Est-ce normal que le service mette plus de deux jours pour me répondre…?

    De manière surprenante, on a eu plein de requêtes depuis que j'ai mis le lien ici :)
    On avait un grosse centaine de tâches en attente alors que d'habitude on est à moins de 10.
    Et comme on a un seul worker, faut prendre son mal en patience.

    Ça devrait être un peu plus normal maintenant.

    Matthieu Gautier|irc:starmad

  • [^] # Re: memforget et fuite mémoire

    Posté par  (site web personnel) . En réponse au journal cTypes + Rust = approfondir une relation d'amour et d'eau (fraîche) . Évalué à 3.

    Premier cas :

    struct MonObjet {
      champ: UnAutreObjet // possession "stricte"
    }

    MonObjet est propriétaire de champ. Donc à la destruction de MonObject, champ sera détruit aussi.
    Et rust garantie qui rien de pointera dessus parce qu'il n'autorise pas de références qui vivent plus longtemps que MonObjet. Sauf si tu fais du unsafe (ce qui est le cas ici), rust ne sait pas. C'est à toi de savoir comment python et rust fonctionne pour rien casser

    Pour

    struct MonObjet {
        champ: &UnAutreObjet // multiples emprunts non-mutables possibles 
    }

    Déjà, c'est faux. C'est :

    struct MonObjet<'lifetime_autreobject> {
         champ: &'lifetime_autreobject UnAutreObjet
    }

    Et ça veut dire que MonObjet, qui emprunte (a une référence vers) UnAutreObjet, ne peut pas vivre plus longtemps que UnAutreObjet. (Même règle que plus haut, mais dans l'autre sens).
    Si python garde un pointeur sur UnAutreObjet, le problème n'arrivera pas quand MonObjet est détruit mais quand UnAutreObjet (ou son propriétaire) sera détruit.

    Dans l'exemple, ma compréhension du phénomène est la suivante : le passage dans liberer_valeurretour supprime le tableau car j'ai déclaré côté Rust, la pleine possession de celui-ci.

    Tout à fait.

    pub struct ValeurRetour {
        pub a: c_int,
        pub contenu: Vec<c_int>
    }

    Tu déclare une structure qui est propriétaire du vecteur qui est propriétaire des entiers.

    Avec let valeur_retour = Box::new(ValeurRetour{...}); tu crées une ValeurRetour dans une box et valeur_retour est propriétaire de la box (et donc ValeurRetour)
    Avec Box::into_raw( valeur_retour ), tu quittes la propriété (au profit de personne), ta box est vidé et tu récupère le pointeur sur valeur_retour.
    Avec Box::from_raw(ptr), tu reprends la propriété. À la destruction de la box, le valeur est détruite (ainsi que le contenu).

    Entre temps, niveau python tu as deux options :

    class ValeurRetour(Structure): 
        _fields_ = [ 
          ( "a", c_int ),
          ( "contenu", POINTER(c_int) )
        ] 
    malib.test_array_retour_complexe.restype = POINTER( ValeurRetour )
    
    # Premier cas (le bon)
    def good():
        tableau_de_rust = malib.test_array_retour_complexe(5) 
        a = r3.contents.a 
        v = r3.contents.contenu  # Un pointeur vers la mémoire interne de `vec`
        print( "[PYTHON] ===>", a, [ v[i] for i in range(0,a) ] )
        # À la destruction de `tableau_de_rust`, on va appeler `__del__` qui va désallouer
        # la mémoire dans rust. On a certes encore v qui pointe vers quelque chose, mais on
        # peut plus l'utiliser, donc osef.
    
    # Le mauvais cas
    def bad():
        tableau_de_rust = malib.test_array_retour_complexe(5) 
        a = r3.contents.a 
        v = r3.contents.contenu  # Un pointeur vers la mémoire interne de `vec`
        print( "[PYTHON] ===>", a, [ v[i] for i in range(0,a) ] )
        return v
        # À la destruction de `tableau_de_rust`, on va appeler `__del__` qui va désallouer
        # la mémoire dans rust.
        # Par contre on a v qui pointe encore vers la mémoire interne désallouée et ça va merder quand on va l'utiliser

    Matthieu Gautier|irc:starmad

  • [^] # Re: Zim

    Posté par  (site web personnel) . En réponse au message Est-ce faisable en .epub ? ou dans un autre format ?. Évalué à 4. Dernière modification le 12 novembre 2022 à 16:26.

    Il est vrai qu'on a pas vraiment de logiciel simple pour lire un zim, on est plutôt sur des logiciels qui permettent de gérer une collection de zims.

    Ça n'est pas possible d'avoir simplement un lecteur type evince ou okular? le serveur http pourrait être démarré en arrière-plan, automatiquement sans que l'utilisateur s'en préoccupe.

    Ça devrait pas être compliqué à faire. Un logiciel avec une webview et un serveur interne ultra simpliste. Yapluska :)

    Matthieu Gautier|irc:starmad

  • [^] # Re: Zim

    Posté par  (site web personnel) . En réponse au message Est-ce faisable en .epub ? ou dans un autre format ?. Évalué à 4.

    Les service workers (SW) c'est une spec du web pour que les sites puissent mettre en place un pseudo proxy dans le browser.

    Pour que le SW fonctionne il faut que le site soit en https, sauf si tu accèdes au serveur en local (c'est considéré comme du debug).

    Ton adresse 192.168.122.1 c'est ton adresse "publique" (dans ton réseau privé) donc il te dis qu'il faut https.
    Si tu passes par 'localhost' (ou 127.0.0.1) ça devrait être bon.
    (Pas besoin de changer la commande kiwix-serve, juste l'adresse dans le navigateur)

    Matthieu Gautier|irc:starmad

  • [^] # Re: Zim

    Posté par  (site web personnel) . En réponse au message Est-ce faisable en .epub ? ou dans un autre format ?. Évalué à 2.

    Bon, ton wav fait 44,89 Mo à lui tout seul. Donc non, c'est pas trop gros 45Mo finalement.

    Matthieu Gautier|irc:starmad

  • [^] # Re: Zim

    Posté par  (site web personnel) . En réponse au message Est-ce faisable en .epub ? ou dans un autre format ?. Évalué à 3.

    J'ai fait un zim de ta page avec youzim.it. Tu peux le télécharger ici : https://youzim.it/53cb3ba90e63be3a92367636
    Il pèse 45Mo (ce qui est bien trop à mon sens mais c'est un autre problème)

    Il est basé sur une solution qui nécessite un service worker, donc il ne fonctionne pas dans kiwix-desktop. Mais tu peux lancer un serveur local (soit directement avec kiwix-serve soit à partir de kiwix-desktop) et le lire avec ton browser. Ça a l'air de bien fonctionner.

    (Les service workers nécessitent soit d'être sur un localhost soit sur du https, c'est pas nous, c'est les specs. À voir comment tu déploies)

    Matthieu Gautier|irc:starmad

  • [^] # Re: memforget et fuite mémoire

    Posté par  (site web personnel) . En réponse au journal cTypes + Rust = approfondir une relation d'amour et d'eau (fraîche) . Évalué à 4.

    Ça me semble mieux :)

    J'émets cependant une réserve sur le __del__. Ça peut poser quelques soucis avec le garbage collecteur de python (https://stackoverflow.com/questions/1481488/what-is-the-del-method-and-how-do-i-call-it)

    En l’occurrence, ça peut bloquer le gc pour chercher des cycles à détruire. Et en fait, tu n'as aucune garantie que __del__ soit appelé par python (il y a de fortes chances que oui, mais c'est pas garantie).

    Si je devais faire la même chose, je partirais plus sur un wrapper (EnveloppeValeurRetourLibDistante) écrite en c++ avec un destructeur en c++ et qui serait wrappé en python (et tout ça probablement avec cython). Mais on sort un peu du sujet d'origine (utiliser ctypes). [*]

    Et une autre réserve. Ça marche parce que tu copies les valeurs du tableau rust dans un tableau python. (D’ailleurs, tu pourrais libérer la mémoire juste après ça). Mais dans un cas plus complexe (par exemple une structure qui à des pointers sur d'autres zone mémoire) il faut faire attention à ne pas garder un pointer vers une de ces zones et libérer la structure en même temps. Sinon, rust risque de libérer la mémoire alors que python continue de pointer dessus.

    En fait, si je devais faire la même chose, je ne le ferais probablement pas et j'utiliserais des libs déjà existantes faites pour ça. La gestion mémoire est un sujet complexe (c'est pas pour rien qu'on a inventer des langages haut niveau qui la gère pour nous) et la gérer correctement dans une couche glue entre deux langages qui n'ont pas les mêmes logiques de gestion mémoire n'est pas une mince affaire.


    [*] Peut être qu'un jour je m'y amuserais, vous aurez droit à un journal si c'est le cas.

    Matthieu Gautier|irc:starmad

  • [^] # Re: Ressemble beaucoup à Frost

    Posté par  (site web personnel) . En réponse à la dépêche Jubako et Arx, un conteneur universel et son format d’archive. Évalué à 5.

    je ne sais pas en Rust comment faire

    Rust lui-même ne sait pas faire :)
    Il faut initialiser la structure sur la stack et ensuite la déplacer dans le heap (ou ailleur). Rust évolue dans ce sens mais c'est pas encore ça. (https://github.com/rust-lang/rust/issues/63291)

    Évidemment, le prix à payer, c'est la portabilité, c'est à dire que sur les machines big endian (est-ce que ça existe encore ces choses ?) ne peuvent pas lire une archive LE, car il faut swapper les entiers avant de les utiliser. Mais pour une solution de backup, vu que tu restaures l'archive sur la même machine que tu sauvegardes, l'endianness ne change pas, ce n'est pas un problème.

    Il y a effectivement peu de machines big endian. Mais je ne suis pas près à parier que ça sera toujours le cas. En tous cas, pas pour un format de conteneur qui se veut portable et durable. Apple à déjà changer l'endianness de ses machines et les processeurs arm peuvent fonctionner dans les deux modes. On est pas à l’abri d'un changement d'endianness pour l'iphone 42

    Du coup, jubako "charge" des octets en mémoire (avec du mmap) et parse ces octets pour en récupérer des valeurs et reconstruire mes structures (et inversement à l'écriture). C'est un peu moins efficace car je dois parser chaque valeur individuellement (même si ça se résume à un byteswap) plutôt que directement charger ma structure.
    Mais ça me permet de gérer l'endianness et de me passer du padding au passage.

    J'ai également une fonction de purge qui élimine les révisions les plus anciennes (probablement la fonction la plus dure à coder pour un système de backup qui déduplique)

    J'imagine bien. J'ai pas hâte de me pencher sur la question :)


    En tous cas, ton approche avec Frost est super intéressante.
    Au lieu de faire des backups "incrémentales" immuables d'un coté et gérer les différents backups (suppression des anciens fichiers) d'un autre, tu as un seul format mutable qui intègres l’ensemble des N révisions.

    Je suis partagé entre "c'est une super idée, il faut effectivement avoir un système cohérent" et mon approche vieille école "c'est n'importe quoi, on ne modifie pas un backup une fois créé".

    Matthieu Gautier|irc:starmad

  • [^] # Re: Ressemble beaucoup à Frost

    Posté par  (site web personnel) . En réponse à la dépêche Jubako et Arx, un conteneur universel et son format d’archive. Évalué à 8.

    il faut absolument regarder du côté des structures mmapables (le gain de performance est délirant)

    C'est quoi que tu appelles structures mmapables ? Jubako mmap déjà pas mal de chose. Est-ce que c'est des structures avec des particularités (et lesquelles) ou c'est "juste" que l'implémentation doit faire du mmap plutôt que du read dans le fichier ?

    Je me répond à moi même. J'ai vu que dans ton code, tu mmap des régions de ton fichier et tu construit directement (avec un inplace new) tes structures sur les régions mmappées (donc directement dans le fichier).

    Du coup, j'ai une autre question : Comment tu gères l'endianness et la portabilité entre différentes plateformes  ?

    Matthieu Gautier|irc:starmad

  • [^] # Re: Ressemble beaucoup à Frost

    Posté par  (site web personnel) . En réponse à la dépêche Jubako et Arx, un conteneur universel et son format d’archive. Évalué à 4.

    Trop bien ! Je savais que j'avais pas trouvé tout les formats d'archivage, j'en ai un de plus à analyser :)

    Tu as l'air d'avoir déjà fait pas mal de choses qui sont dans ma todo list (déduplication, chiffrement, parité, …) Je vais regarder ce que tu as fait, voir te piquer des idées (l'épaule des géants, tout ça)

    il faut absolument regarder du côté des structures mmapables (le gain de performance est délirant)

    C'est quoi que tu appelles structures mmapables ? Jubako mmap déjà pas mal de chose. Est-ce que c'est des structures avec des particularités (et lesquelles) ou c'est "juste" que l'implémentation doit faire du mmap plutôt que du read dans le fichier ?

    du stockage de différences (je détecte les différences via un rolling checksum à points de division fixe, ce qui permet de s'adapter à de nombreux formats, comme le texte ou les formats binaires). Ainsi, tu peux découper tes content pack en fonction du contenu (et en virant donc tous ce qui se déduplique facilement).

    Oui, c'est quelque chose que j'avais déjà identifié en travaillant sur les diff entre les zims. C'est dans ma todo pour faire des diffs efficaces.

    Dans mon cas, je recalcule toujours la différence entre la version actuelle et la dernière version et je modifie la dernière version pour arriver à la version actuelle, ce qui est beaucoup plus efficace que le contraire.

    Je suis pas sur de comprendre. Tu fais un diff "inversé" comme dans les packs git (si tu connais) ? La version "pleine" est toujours la dernière version (la plus couramment lue), et le diff permet de retrouver les anciennes versions.

    Si c'est le cas, ça veut dire que tes backups passés sont modifiés en fonction des données actuelles ?

    Matthieu Gautier|irc:starmad

  • [^] # Re: memforget et fuite mémoire

    Posté par  (site web personnel) . En réponse au journal cTypes + Rust = approfondir une relation d'amour et d'eau (fraîche) . Évalué à 4. Dernière modification le 07 novembre 2022 à 14:39.

    Si j'en crois la doc, Python récupère le pointeur et créé via cTypes, tout le nécessaire pour gérer son cycle de vie. Donc non, il n'y a pas de risque.

    C'est quelle doc ?

    Python n'a aucun moyen de savoir comment il doit désallouer la mémoire, ni même si il doit la désallouer.

    Si tu wrap du code c++ et que tu retournes un pointeur sur un class, il faut appeler le
    destructeur. Python ne le connais pas. Pareil au niveau de Rust, il y a peut-être des drop à appeler.
    Voir même, il ne faut pas détruire la donnée. Image que ta box est dans un stockage de ta lib rust et que tu veux juste retourner un pointer sur cette valeur tout en gardant sa gestion mémoire dans rust : tu peux en récupérer un pointer en "coerçant" la référence (https://doc.rust-lang.org/std/primitive.pointer.html#common-ways-to-create-raw-pointers)
    mais python ne doit pas supprimer la mémoire. Donc Python ne doit pas le faire par défaut.

    Si tu veux que le code python supprime la mémoire, il faut repasser le pointer à rust pour qu'il le supprime comme il faut, probablement avec une fonction de ce type (si tu as utiliser Box::into_raw, à adapter aux cas d'usages) :

    #[no_mangle]
    #[allow(improper_ctypes_definitions)]
    pub unsafe extern "C" fn free_box( ptr: *mut c_int ) {
        drop(Box::from_raw(ptr));
    } 

    Tu peux lancer ton programme avec valgrind pour voir si il y a des fuites mémoire.

    Matthieu Gautier|irc:starmad

  • [^] # Re: Zim

    Posté par  (site web personnel) . En réponse au message Est-ce faisable en .epub ? ou dans un autre format ?. Évalué à 2.

    Tu peux essayé de transformer ton site php en site statique
    (en gros en mettant les réponses aux appel XHR dans des fichiers et en changeant les requêtes pour faire des appels sur les fichiers plutôt que du XHR sur le serveur).

    youzim.it fait globalement ça pour toi, mais ça nécessite un service worker dans le navigateur pour changer les urls au "runtime". (service worker inclus dans le zim, mais tu es potentiellement limité à un navigateur/environnement qui support les services worker)

    Matthieu Gautier|irc:starmad

  • [^] # Re: Une base de données ?

    Posté par  (site web personnel) . En réponse à la dépêche Jubako et Arx, un conteneur universel et son format d’archive. Évalué à 6. Dernière modification le 07 novembre 2022 à 13:25.

    Mais j’ai vite déchanté : mon premier problème concerne la difficulté de créer la dite archive. Impossible de trouver un outil à la ligne de commande facilement installable, donc j’ai finis par me rabattre, à contre-cœur, sur un site « Zim It » (dont je ne retrouve plus l’adresse…) qui m’a permis, étant donné l’URL, de télécharger l’archive Zim correspondante.

    Ce n'est pas évident du tout de refaire des sites locaux à partir de site "en-ligne". On a des outils spécialisés pour les gros sites qu'on fourni (mwoffliner pour tout les mediawiki, sotoki pour stackoverflow, …). Mais ils sont vraiment spécifique au format du site.
    Et ils ne sont pas forcement sobre en ressources et en temps.

    Le service zimit est là : https://youzim.it/

    Qu’est-ce que j’ai raté pour créer l’archive facilement, en tant que commun des mortel·le·s ?

    Pas vraiment, non. Mais si t'es content·e de la version offline créée avec wget -R, tu peux la mettre dans un zim avec zimwriterfs.

    Est-ce que la taille de l’archive est dû au site utilisé, ou bien est-ce un « problème » connu ? (pour référence, le site c’est https://www.libraryofjuggling.com)

    Non, c'est pas connu, et c'est surprenant. Si t'as le temps, tu peux créer une issue sur libzim avec toute les infos, ça nous aiderait beaucoup.

    Après, on stocke une base de données xapian dans le zim pour faire de la recherche fulltext, c'est peut-être elle qui gonfle l'archive.

    Suis-je vraiment le type d’utilisateur·ice envisagé par Zim ?

    Oui :)
    On est plus orienté utilisateurs finaux qui vont juste consulté les archives mais c'est principalement du à la difficulté de scrapper des sites de manière générique (d'où la création de youzim.it). Dans l'idée on s'adresse aussi au gens qui veulent créer leurs archives.

    Si oui, comment peut-on améliorer cette première expérience avec Zim ?

    Déjà, en remontant l'information. Ça nous permettra au moins de savoir où il faut s'améliorer.
    Ensuite, c'est un projet libre. Nous sommes grandement ouvert aux contributions, n'hésitez pas à venir nous parler et à corriger les problèmes.

    Matthieu Gautier|irc:starmad

  • [^] # Re: Dar

    Posté par  (site web personnel) . En réponse à la dépêche Jubako et Arx, un conteneur universel et son format d’archive. Évalué à 2.

    Je connaissais pas du tout dar. Je vais regarder.

    À première vue, Dar est quand même bien orienté FS. Jubako se veut plus généraliste.
    J'ai encore des comparaisons à faire :)

    Matthieu Gautier|irc:starmad

  • [^] # Re: Bravo !

    Posté par  (site web personnel) . En réponse à la dépêche Jubako et Arx, un conteneur universel et son format d’archive. Évalué à 3. Dernière modification le 07 novembre 2022 à 12:14.

    Jubako n'est pas un système de fichier. Certes il y a Arx qui implémente une archive de fichiers et donc ressemble un peu à un FS readonly, mais ce n'est qu'un cas d'usage de Jubako parmi d'autres.

    • Pour un stockage style kiwix, tu as besoins de l'url de la ressource web et son mimetype, sans système d’arborescence.
    • Pour un fichier d'archive, il faut une arborescence et le stockage de toutes les métadonnées d'un fs.
    • Pour un outils de backup, tu as potentiellement moins d'info à stocker (les dates de création/modification ?) ou plus (un checksum du contenu).
    • Pour un outils d'archivage, tu es probablement peu intéressé par les droits du fs, l'owner/group… mais tu veux d'autres info (catégorisation, …)
    • Pour un distribution de logiciel type rpm/deb, tu te fous des métadonnées, tu veux savoir où installer des fichiers. Par contre tu veux des métadonnées de plus haut niveau (description, version, dépendances, …)
    • Un système comme textbundle n'a pas à être obligatoirement structuré avec un zip.
    • J'ai entendu parlé d'usage où on diffusait des mise à jour de programme télé aux décodeurs des particuliers avec des bdd sqlites. Jubako pourrait être utilisé dans ce cas (je sais pas si ça serait plus adapté ou non, mais c'est un cas d'usage qui sort du classique archive de fichiers)

    UnionFs, SquashFS, le système de Docker ou Knoppix sont orientés FS. C'est assez générique et tout le monde y est habitué ("tout est fichier" de Unix) mais Jubako sort de ce paradigme et te permet de faire autrement. Arx nous ramène à ça mais je l'ai juste implémenté en premier parce que c'était assez facile et avec des résultats facilement comparables à l'existant. Un avantage de Jubako c'est sa modularité.

    Une autre différences, c'est de découplage entre la partie "index" (directory dans la terminologie Jubako) et le contenu. Par exemple pour un système de backup, tu pourrais envoyer tout le contenu sur un serveur distant et garder en local que le directory. Lors des sauvegarde incrémentale, tu as accès, en local, à la liste des fichiers déjà sauvegarder et leur checksums. Tu pourrais faire ton backup totalement en offline, sans avoir toute les sauvegarder en local (et leur occupation disque) et quand même faire de l'incrémental. Tu peux bien sûr faire la même chose avec un json que tu gardes en local, mais Jubako te fournit tout de base.


    J'ai évité d'implémenter un équivalent à zim en premier cas d'usage justement pour pas être comparé seulement à zim et limité jubako à un successeur du format zim. Ne comparez pas que Arx avec l'existant :)


    J'ai quand même fait un comparatif avec squashfs, ça manque effectivement à la dépêche. J'ai refait les mêmes tests que dans la dépêche (linux complet) avec squasfs. Par défaut squasfs utilise les 16 cœurs de ma machine mais arx est monothreadé. Donc il y a deux séries de tests, un avec squashfs 16 cœurs (pour voir des performances "optimales") et 1 cœur (pour comparer correctement)

    Taille Création Extraction Listing Dump Dump / entry Mount diff
    Source 1326 MB 880ms
    squashfs 177 MB 1m16s 0s730ms 30ms 1m02s 2ms 2,8
    squashfs monothread 177 MB 4m38s 1s070ms 30ms 1m02s 2ms 2,8
    Arx 140 MB 4m45s 1s47ms 45ms 1m28s 4ms 4s2

    Globalement :
    - arx compresse mieux
    - Est plus lent que squashfs mais reste dans des ordres de grandeurs comparables.

    Et il faut se souvenir que squashfs est implémenté dans le kernel (Je sais pas quelle partie est réellement dans le kernel et dans l'userpace pour le listing des fichiers mais pour ce qui est du mount, c'est totalement dans le kernel). Ça m'aurait surpris que arx soit plus rapide qu'un module kernel.

    C'est aussi une différence de Jubako avec les autres systèmes. Jubako c'est un lib, elle se veut indépendante de l'OS. À terme, Jubako sera disponible sous Windows et Mac aussi (voir même dans le web en compilant jubako en wasm). Squashfs, t'es limité à linux.

    Matthieu Gautier|irc:starmad

  • # memforget et fuite mémoire

    Posté par  (site web personnel) . En réponse au journal cTypes + Rust = approfondir une relation d'amour et d'eau (fraîche) . Évalué à 3.

    // au regard de Rust, 's' est ma chaîne c_char et 'p' est le pointeur
    // si "je n'oublie pas" 's' avant de quitter la fonction, Rust va désallouer la mémoire
    // le pointeur 'p' renvoyé serait donc invalide
    // 'std::mem::forget' nous permet de forcer cet "oubli de désallocation" lors de la compilation
    std::mem::forget(s);

    N'y a-t-il pas une fuite mémoire là ?
    Certes tu es obligé de dire à rust de ne pas desalouer la variable pour la passer à python, mais il faut bien la libérer à un moment. Sur un cas de test comme ça c'est pas bien grave, mais comment tu gères ça en vrai sur un programme réel ?

    Matthieu Gautier|irc:starmad

  • [^] # Re: Une base de données ?

    Posté par  (site web personnel) . En réponse à la dépêche Jubako et Arx, un conteneur universel et son format d’archive. Évalué à 4. Dernière modification le 06 novembre 2022 à 19:33.

    J'ai pas trop regarder du coté de sqlite. Mais à première vue je dirais déjà que c'est pas fait pour la même chose. Sqlite est plutôt orienté pour du stockage de "petites" données (métadonnées), même si il sait stocker des grosses données. Jubako est plutôt orienté "grosses" donnée (contenu de fichier par exemple, même si il sait stocker des métadonnées aussi.
    De la même manière sqlite est orienté pour faire des requêtes complexes. C'est pas le cas de Jubako.

    Après il y a aussi la question de la compression. Compresser un base de donnée sqlite revient au même problème que tar, il faut tout décompresser pour accéder à la base. Ou alors
    il faut stocker le contenu de chaque "fichier" compressé individuellement mais là on serait dans le cas de zip et donc avec des taux de compression moindre.
    Jubako (comme zim) est au niveau intermédiaire et compresse les données par "cluster", ce qui permet d'avoir un meilleur taux de compression que zip mais un accès plus "random" que tar.

    Matthieu Gautier|irc:starmad

  • [^] # Re: Bravo !

    Posté par  (site web personnel) . En réponse à la dépêche Jubako et Arx, un conteneur universel et son format d’archive. Évalué à 4.

    C'est à vous que je pensais ;)

    Matthieu Gautier|irc:starmad

  • [^] # Re: Bravo !

    Posté par  (site web personnel) . En réponse à la dépêche Jubako et Arx, un conteneur universel et son format d’archive. Évalué à 3.

    Tout à fait. Et aussi à unionfs et overlayfs et …

    Dans l'idée, tu pourrais réimplémenter docker en utilisant une solution basé sur Jubako
    (Je dis pas que c'est une bonne idée par contre).

    Matthieu Gautier|irc:starmad

  • [^] # Re: Bravo !

    Posté par  (site web personnel) . En réponse à la dépêche Jubako et Arx, un conteneur universel et son format d’archive. Évalué à 4.

    Merci. Il faut aussi remercier les relecteur·trice·s qui ont grandement amélioré ma première version.

    Matthieu Gautier|irc:starmad