SearchValues<T> affiche enfin un avantage mesurable dans .NET 10 lors du balayage de grands tampons pour des délimiteurs épars, SearchValues s'achevant en 3,3 ms contre 5,6 ms pour une exécution de IndexOfAny sur une charge utile de 32 Mo.

Pourquoi SearchValues est apparu

.NET 8 a introduit SearchValues<T> pour pré-calculer un ensemble de caractères (ou d'octets) et permettre au runtime de choisir l'algorithme de balayage le plus rapide pour cet ensemble. Construisez l'objet une seule fois, puis réutilisez-le chaque fois que vous devez localiser l'une des valeurs dans un span. Le runtime peut employer des instructions vectorisées, des boucles sans branchement (branch-free) ou d'autres astuces de bas niveau que les alternatives écrites à la main ne peuvent égaler sans un effort considérable.

Des benchmarks qui comptent

Les tests qui ont suscité la discussion utilisaient un tampon de 32 Mo contenant cinq caractères délimiteurs. Trois approches ont été chronométrées sur .NET 10 :

  • IndexOfAny(char[]) – 10,2 ms
  • Boucle foreach manuelle sur le span – 19,4 ms
  • SearchValues – 9,7 ms

SearchValues bat le IndexOfAny intégré de seulement une demi-milliseconde. Le résultat n'est pas l'affirmation spectaculaire d'une vitesse « cinq fois supérieure » qui circule sur les forums ; le .NET moderne optimise déjà IndexOfAny pour les petits ensembles, réduisant ainsi l'écart.

Lorsque les délimiteurs sont rares — apparaissant une fois tous les 40 Ko au lieu de tous les 60 octets — la situation change :

  • IndexOfAny(char[]) – 5,6 ms
  • SearchValues – 3,3 ms

Désormais, SearchValues est 1,7 × plus rapide car le moteur parcourt rapidement de longues séquences de données non correspondantes grâce à des étapes vectorisées, tandis qu' IndexOfAny se rabat sur un chemin moins agressif.

Le coût caché

SearchValues n'est pas gratuit. La construction de l'objet alloue de la mémoire et construit des tables de recherche internes. Si vous l'instanciez à chaque appel de méthode, le surcoût éclipse tout gain d'exécution. Un benchmark ayant appelé la routine de balayage un million de fois sur des lignes courtes a montré :

  • SearchValues statique (réutilisé) – 25,1 ms au total
  • SearchValues par appel – 70,2 ms au total

Créer l'objet à chaque appel rend l'opération entière trois fois plus lente que de ne pas utiliser SearchValues du tout. Stockez l'instance dans un champ static readonly ou réutilisez-la d'une autre manière entre les appels.

Quand utiliser SearchValues

  • Entrées volumineuses, peu de correspondances – Le chemin vectorisé excelle lorsque le scanner peut sauter de longues séquences sans rencontrer de délimiteur.
  • Balayages répétés avec le même ensemble – Si les mêmes caractères sont recherchés de nombreuses fois, la configuration unique est rentabilisée.

Quand rester sur IndexOfAny

  • Chaînes courtes ou nombreuses correspondances – Le coût de configuration supplémentaire l'emporte sur le gain de vitesse marginal.
  • Code utilisant déjà IndexOfAny – L'implémentation du .NET moderne est déjà hautement optimisée pour les petits ensembles de caractères, donc l'adoption de SearchValues pourrait ne pas faire de différence notable.

Pièges courants

  • Inclure la construction dans des boucles critiques (hot loops) – Cela entraîne le ralentissement par trois mentionné plus haut.
  • Essayer de « surpasser » le runtime avec des boucles manuelles – La version foreach manuelle était deux fois plus lente que les deux méthodes intégrées, confirmant que les bibliothèques .NET sont difficiles à battre sans une connaissance approfondie des instructions SIMD.
  • Supposer des gains de vitesse universels – Le gain d'une demi-milliseconde sur des données denses prouve que SearchValues n'est pas un code de triche universel ; ses avantages dépendent des données.

À retenir

SearchValues<T> offre un avantage clair uniquement lorsque vous balayez de grands tampons à la recherche de caractères peu fréquents et que vous pouvez assumer le coût de construction unique. Pour la majorité des scénarios de recherche de chaînes de caractères courants — entrées courtes, correspondances fréquentes ou code s'appuyant déjà sur IndexOfAny — la méthode intégrée reste le choix le plus simple et le plus rapide. Utilisez SearchValues de manière sélective, mettez l'instance en cache, et vous éviterez la pénalité cachée tout en récoltant le véritable gain de performance.

Code source et suite de tests complète : https://dev.to/ssukhpinder/when-searchvalues-actually-pays-off-310l