Retour à l'accueil

ART Arbres Préfixes : Optimisation de la Mémoire et du Cache

L'article analyse les problèmes des arbres préfixes standards (825 MB mémoire, 12 misses de cache) et les optimisations via les arbres radix et ART (18 MB, 3 misses). Fournit des benchmarks, des codes de nœuds et des scénarios d'application pour l'autocomplétion et le routage.

ART : arbres préfixes 45 fois plus compacts que standard
Advertisement 728x90

Optimisation des arbres de préfixes : de 825 Mo à 18 Mo pour la complétion automatique

Un arbre de préfixes (trie) pour rechercher 50 000 commandes consommait 825 Mo de mémoire et était 8 fois plus lent qu'une table de hachage (4,8 millions de cycles contre 600 000). Chaque nœud faisait 2 064 octets avec un tableau de 256 pointeurs, provoquant fréquemment des pertes de cache lors des recherches.

Le benchmark perf stat a révélé 125 000 pertes de cache contre seulement 18 000 pour la table de hachage. La recherche d'une chaîne de 5 caractères déclenchait 5 pertes de cache car chaque nœud ne tenait pas dans une seule ligne de cache.

Structure des nœuds et utilisation mémoire

Implémentation standard :

Google AdInline article slot
typedef struct trie_node {
    struct trie_node *children[256];  // 2048 octets
    void *value;                      // 8 octets
    bool is_end;                      // 1 octet
    // Total : 2064 octets
} trie_node_t;

Pour un jeu de données de 50 000 commandes (longueur moyenne de 8 caractères), environ 400 000 nœuds étaient nécessaires — soit 825 Mo contre seulement 1,2 Mo pour une table de hachage, soit une différence de 687 fois.

Arbres compacts : compression des chaînes

Solution : arbres compacts (tries de Patricia), où les chaînes de nœuds à enfant unique sont compressées en préfixes. Les nœuds stockent des chaînes de préfixe au lieu de caractères individuels.

typedef struct radix_node {
    char *prefix;
    int prefix_len;
    struct radix_node *children[256];
    void *value;
} radix_node_t;

Algorithme de recherche :

Google AdInline article slot
void* radix_search(radix_node_t *node, const char *key) {
    while (node) {
        int i = 0;
        while (i < node->prefix_len && key[i] == node->prefix[i]) {
            i++;
        }
        if (i < node->prefix_len) return NULL;
        if (key[i] == '\0') return node->value;
        node = node->children[(unsigned char)key[i]];
        key += i + 1;
    }
    return NULL;
}

Résultat : la mémoire est tombée à 330 Mo, les cycles à 2,4 millions, et les pertes de cache à 6,8 — une accélération de 2 fois.

Arbres radicaux adaptatifs (ART)

Le problème subsistait : 98 % des tableaux de 256 pointeurs étaient vides. Solution : nœuds adaptatifs basés sur le nombre d'enfants.

  • Node4 (1–4 enfants) : 40 octets
  • Node16 (5–16 enfants) : 152 octets
  • Node48 (17–48 enfants) : 640 octets
  • Node256 (49+ enfants) : 2048 octets

Stratégie d'expansion : Node4 → Node16 → Node48 → Node256 lorsque les limites sont dépassées.

Google AdInline article slot

Taille moyenne du nœud : 40–152 octets contre 2048 — économie de mémoire de 10 à 50 fois.

Résultats des benchmarks

Comparaison sur 1 million de recherches sur 50 000 commandes :

| Structure | Mémoire | Cycles | Pertes de cache | Accélération |

|-----------|---------|--------|----------------|--------------|

| Trie | 825 Mo | 4 800 | 12,5 | 1× |

| Radix | 330 Mo | 2 400 | 6,8 | 2× |

| ART | 18 Mo | 1 200 | 3,2 | 4× |

| Hash | 1,2 Mo | 600 | 1,8 | 8× |

L'ART est 45 fois plus compact qu'un trie standard ; les nœuds Node4/Node16 tiennent dans 1 à 2 lignes de cache.

Cas d'utilisation des arbres de préfixes

Utilisez l'ART quand vous avez besoin d'opérations de préfixe :

  • Complétion automatique : parcours du sous-arbre en O(k + m), où m est le nombre de correspondances. Les tables de hachage exigent un balayage complet.
  • Routeur IP : correspondance du préfixe le plus long par bits d'adresse.
  • Vérification orthographique : recherche via distance d'édition utilisant les préfixes.
  • Tableaux creux : comme utilisé dans le noyau Linux (cache de pages, IDR, XArray).

Évitez pour les recherches exactes, les petits jeux de données (< 1 000 éléments) ou les chaînes sans préfixes communs.

Arbres compacts dans le noyau Linux

Le noyau utilise radix_tree_node avec 64 emplacements (6 bits par niveau). Pour un index 32 bits, cela fait 6 niveaux — environ 6 pertes de cache contre 32 pour un arbre binaire équilibré.

Avantages :

  • Support des opérations sur plage
  • Complexité prédicable O(log₆₄ n)
  • Efficace pour les données creuses

Points clés

  • Les tries standards utilisent 687 fois plus de mémoire à cause des tableaux de 256 pointeurs
  • L'ART réduit la mémoire à 18 Mo et les pertes de cache à 3,2 grâce à des nœuds adaptatifs
  • Utilisez pour les requêtes de préfixe ; les tables de hachage gagnent aux recherches exactes
  • Les arbres compacts compressent les chaînes, offrant une accélération de 2 fois
  • Le noyau Linux les utilise pour les tableaux creux et l'allocation d'ID

— Editorial Team

Advertisement 728x90

Lire ensuite