Files
obikmer/phylo_archi.md
T
Eric Coissac b1f54b7d2f Add cache-optimized batch retrieval and sub-matrix methods
Introduces batch retrieval and sub-matrix extraction methods across vector, view, reader, and matrix types. These implementations optimize cache locality by sorting requested indices for sequential memory access before applying an inverse permutation to restore original order. Includes allocation-free variants that populate caller-provided buffers. Updates architecture documentation to define sibling annex persistence in iteration order and clarify pipeline separation.
2026-08-16 14:01:35 +02:00

5.2 KiB

Nous avons un vrai problème architectural dans la partie phylo, basée sur les sibling.

La structure de l'index est en quatre parties, à l'intérieur d'un layer:

  • Un fichier de superkmers qui permet d'itinérer sur les kmers gérées par le layer.
  • Un MPFH qui permet de convertir un kmer en un numéro de slot compact.
  • Un tableau d'évidence, qui peut être exact ou probabiliste. Ce qui permet de vérifier lorsque l'on interroge le MPFH avec un kmer, si ce kmer est contenu dans le layer.
    • Il est donc une erreur conceptuelle grave d'utiliser cette évidence pour retrouver un kmer à partir de son numéro de slot. Toute tentative en ce sens est vouée à l’échec sur une évidence non exacte.
  • Un tableau de présence ou de comptage par génome. Ranger colonne first : une colonne correspond à un génome, et indexé par numéro de slot

Concernant le MPFH.

  • Le rôle premier du MPFH est de convertir un kmer en un numéro de slot.
  • On peut, en rôle secondaire, en combinant le MPFH et le tableau d'évidence, s'en servir pour mettre en place un test d'appartenance au layer pour un kmer.

L'API du Layer<D> expose également des méthodes de mapping brut kmer → slot via le MPHF, sans vérification d'appartenance :

  • index(&self, kmer: CanonicalKmer) -> usize — retourne le numéro de slot pour un kmer. C'est un mapping pur, équivalent à MphfOnly::index.
  • index_batch(&self, kmers: &[CanonicalKmer]) -> Vec<usize> — retourne un vecteur de slots pour un slice de kmers.

Ces méthodes sont distinctes de query() et find() qui incluent la vérification d'appartenance.

Lorsque l'on itère sur le fichier de superkmer, l'ordre des kmer est déterministe, mais non corrélé avec les numéros de slot correspondants. Il existe donc un numéro d'ordre dans les kmer contenues dans le fichier de superkmer.

L'API du Layer<D> expose maintenant quatre itérateurs sur les kmers du layer :

  • iter_kmers(&self) -> KmerIter<'_> — itérateur nommé sur CanonicalKmer, construit à partir de unitigs.bin. Plusieurs instances peuvent coexister concurremment tant que le layer vit.
  • enumerate_kmers(&self) -> Enumerate<KmerIter<'_>> — variante indexée retournant (usize, CanonicalKmer), où l'index 0 correspond au premier kmer du fichier de superkmers. Construit par composition sur iter_kmers() sans duplication de code d'itération.
  • iter_kmers_batch(&self, n: usize) -> KmerBatchIter<'_> — itérateur par batch retournant Vec<CanonicalKmer> de taille n. Le dernier batch peut être tronqué.
  • enumerate_kmers_batch(&self, n: usize) -> Enumerate<KmerBatchIter<'_>> — variante indexée retournant (usize, Vec<CanonicalKmer>), où l'index correspond au premier kmer du batch. Construit par composition sur iter_kmers_batch().

L'itération est implémentée via des structs KmerIter et KmerBatchIter qui encapsulent l'itérateur interne de UnitigFileReader::iter_indexed_canonical_kmers(). Les structs sont publics et peuvent être stockés, transmis ou combinés avec d'autres adaptateurs d'itérateur.

Performance — itérateurs de batch

Les itérateurs iter_kmers_batch et enumerate_kmers_batch allouent un nouveau Vec à chaque batch. Pour des tailles de batch importantes ou des chemins critiques, cela peut générer une pression malloc significative.

À concevoir : un système de pool de vecteurs avec réallocation automatique dès qu'un buffer n'est plus référencé, pour réutiliser les allocations entre batches et réduire le nombre d'appels système. Ce pool pourrait être intégré à KmerBatchIter ou proposé comme un adaptateur d'itérateur générique.

Accès aux matrices de présence/comptage

Les types de vecteurs persistants exposent maintenant des méthodes de lookup batch optimisées pour l'accès séquentiel au mmap :

  • PersistentCompactIntVec::get_batch(slots) -> Vec<u32> et fill_batch(slots, out)
  • PersistentBitVec::get_batch(slots) -> Vec<bool> et fill_batch(slots, out)
  • IntSliceView::get_batch(slots) -> Vec<u32> et fill_batch(slots, out)
  • BitSliceView::get_batch(slots) -> Vec<bool> et fill_batch(slots, out)

Toutes ces méthodes trient les slots en interne pour un accès mmap séquentiel, puis réordonnent les résultats selon l'ordre d'origine. fill_batch évite l'allocation en remplissant un buffer fourni par le caller.

Les matrices persistantes exposent sub_matrix(slots) -> Vec<Vec<T>> et fill_sub_matrix(slots, out) :

  • PersistentCompactIntMatrix::sub_matrix(slots) — retourne Vec<Vec<u32>> column-first
  • PersistentCompactIntMatrix::fill_sub_matrix(slots, out: &mut [Vec<u32>]) — remplit des buffers fournis, réutilise les allocations existantes
  • PersistentBitMatrix::sub_matrix(slots) — retourne Vec<Vec<bool>> column-first
  • PersistentBitMatrix::fill_sub_matrix(slots, out: &mut [Vec<bool>]) — remplit des buffers fournis, réutilise les allocations existantes

Le scan est colonne-first pour respecter la layout mémoire. fill_sub_matrix trie les slots une seule fois, puis appelle fill_batch_sorted sur chaque colonne pour éviter le tri redondant. Ces méthodes sont également disponibles sur Layer<PersistentCompactIntMatrix> et Layer<PersistentBitMatrix>.