J'ai utilisé et modifié cette bibliothèque https://github.com/sile/patricia_tree
Une chose qui m'a un peu dérangé est la quantité d'insécurité utilisée dans node.rs, en particulier, il est défini comme un simple pointeur vers un emplacement du tas. En faisant le premier benchmark listé sur la page readme (entrées wikipedia), le PatriciaSet utilise ~700mb (le PatriciaSet contient juste un Node à sa racine).
pub struct Node<V> {
// layout:
// all these fields accessed with ptr.offset
// - flags: u8
// - label_len: u8
// - label: [u8; label_len]
// - value: Option<V>
// - child: Option<Node<V>>
// - sibling: Option<Node<V>>
ptr: *mut u8,
_value: PhantomData<V>,
}
et utilise malloc
pour l'attribution :
let ptr = unsafe { libc::malloc(block_size) } as *mut u8;
On m'a dit que cette mémoire n'est pas alignée correctement, alors j'ai essayé d'ajouter la nouvelle api d'allocation et d'utiliser Layout/alloc, ce qui n'est toujours pas aligné correctement, mais semble juste "fonctionner". full pr
let layout = Layout::array::<u8>(block_size).expect("Failed to get layout");
let ptr = unsafe { alloc::alloc(layout) as *mut u8 };
Ce changement unique, qui détient également le layout
dans le bloc de mémoire pointé par ptr
a fait augmenter la consommation de mémoire de 40 % lors des tests de performance pour les très grands arbres. Le type de mise en page n'a que 2 mots de large, c'était donc inattendu. Pour les mêmes tests, la consommation est plus proche de ~1000mb (par rapport aux 700 précédents).
Dans une autre tentative, j'ai essayé d'enlever la plupart de l'insécurité et d'aller avec quelque chose d'un peu plus rouillé. pr complet ici
pub struct Node<V> {
value: Option<V>,
child: Option<*mut Node<V>>,
sibling: Option<*mut Node<V>>,
label: SmallVec<[u8; 10]>,
_value: PhantomData<V>,
}
créer le nœud d'une manière que l'on peut attendre de rust
let child = child.map(|c| Box::into_raw(Box::new(c)));
let sibling = sibling.map(|c| Box::into_raw(Box::new(c)));
Node {
value,
child,
sibling,
label: SmallVec::from_slice(label),
_value: PhantomData,
}
En termes de performances, elle est à peu près équivalente à la bibliothèque originale non modifiée, mais sa consommation de mémoire ne semble pas être beaucoup plus élevée que l'insertion de chaque élément dans un HashSet, utilisant environ ~1700mb pour le premier benchmark.
La structure de données finale qui utilise le nœud est un trie compressé ou un "arbre de Patricia". Aucun autre code n'a été modifié, à l'exception de la structure de l'objet Node
et les implémentations de certaines de ses méthodes qui découlent idiomatiquement de ces changements.
J'espérais que quelqu'un pourrait m'indiquer ce qui cause exactement une différence aussi radicale dans la consommation de mémoire entre ces implémentations. Dans mon esprit, elles devraient être à peu près équivalentes. Elles allouent toutes environ le même nombre de champs avec à peu près les mêmes largeurs. La première, non sécurisée, est capable de stocker une longueur d'étiquette dynamique en ligne, ce qui pourrait être une raison. Mais smallvec devrait être capable de faire quelque chose de similaire avec des tailles d'étiquettes plus petites (utiliser uniquement Vec était encore pire).
Je cherche des suggestions ou de l'aide pour expliquer pourquoi les résultats finaux sont si différents. Si vous êtes curieux, le code pour exécuter ces tests est aquí bien qu'il soit réparti entre les auteurs originaux et mon propre dépôt.
Les outils permettant d'étudier les différences entre les deux sont également les bienvenus !