En informatique, un algorithme du cache-agnostique(ou algorithme oblivieux au cache, ou encore algorithme indépendant du cache ) est conçu pour exploiter le cache du processeur sans que la taille du cache (ou la longueur des lignes de cache, etc.) soit un paramètre explicite. Un algorithme indépendant du cache optimal est un algorithme qui utilise le cache de manière optimale (au sens asymptotique, en négligeant les facteurs constants). Ainsi, un algorithme indépendant du cache est conçu pour fonctionner correctement, sans modification, sur plusieurs machines dotées de caches de tailles différentes, ou au sein d'une hiérarchie de mémoire comprenant plusieurs niveaux de cache de tailles variables. Les algorithmes indépendants du cache s'opposent au déroulement explicite des boucles, qui divise explicitement un problème en blocs de taille optimale pour un cache donné.

Les algorithmes optimaux indépendants du cache-agnostique sont connus pour la multiplication matricielle, la transposition matricielle, le tri et plusieurs autres problèmes. Certains algorithmes plus généraux, comme la FFT de Cooley-Tukey, sont optimaux sans contrainte de cache pour certains choix de paramètres. Étant donné que ces algorithmes ne sont optimaux que de manière asymptotique (en ne tenant pas compte des facteurs constants), un réglage plus poussé, spécifique à chaque machine, peut être nécessaire pour obtenir des performances proches de l'optimal en termes absolus. L'objectif des algorithmes indépendants du cache est de réduire considérablement ce réglage.

Un algorithme indépendants du cache fonctionne généralement selon un algorithme récursif de type « diviser pour régner ou dichotomique », où le problème est divisé en sous-problèmes de plus en plus petits. Nous finissons par obtenir une taille de sous-problème qui s'adapte à la mémoire cache, quelle que soit la taille de celle-ci. Par exemple, on obtient une multiplication matricielle optimale, indépendante de la mémoire cache, en divisant de manière récursive chaque matrice en quatre sous-matrices à multiplier, puis en effectuant la multiplication de ces sous-matrices à l'aide d'une recherche en profondeur. Lors de l'optimisation pour une machine spécifique, on peut utiliser un algorithme hybride qui utilise un pavage de boucle optimisé pour les tailles de cache spécifiques au niveau inférieur, mais qui utilise par ailleurs l'algorithme insensible au cache.

Histoire

modifier

L'idée (et le nom) des algorithmes indépendants du cache a été conçue par Charles E. Leiserson dès 1996 et publiée pour la première fois par Harald Prokop dans sa thèse de maîtrise au Massachusetts Institute of Technology en 1999[1]. De nombreuses études antérieures ont analysé des problèmes spécifiques ; ceux-ci sont examinés en détail dans Frigo et al. 1999. Parmi les premiers exemples cités, on peut citer Singleton 1969 pour une transformée de Fourier rapide récursive, des idées similaires dans Aggarwal et al. 1987, Frigo 1996 pour la multiplication matricielle et la décomposition LU, et Todd Veldhuizen 1996 pour les algorithmes matriciels dans la bibliothèque Blitz++ .

Modèle de cache idéalisé

modifier

Généralement, on peut rendre un programme plus sensible au cache[2] :

  • Localité temporelle, c'est-à-dire lorsque l'algorithme accède plusieurs fois aux mêmes zones de mémoire;
  • Localité spatiale, dans laquelle les accès successifs à la mémoire se produisent à des adresses mémoire adjacentes ou proches.

Les algorithmes indépendants du cache sont généralement analysés à l'aide d'un modèle idéalisé du cache, parfois appelé « modèle du cache-agnostique ». Ce modèle est beaucoup plus facile à analyser que les caractéristiques d'un cache réel (qui présente une associativité complexe, des politiques de remplacement, etc.), mais il est souvent démontré qu'il se situe à un facteur constant près des performances d'un cache plus réaliste. Il diffère du modèle de mémoire externe car les algorithmes indépendants du cache ne connaissent ni la taille des blocs ni la taille du cache.

Plus précisément, le modèle indépendants au cache est une machine abstraite (c'est-à-dire un modèle théorique de calcul ). Il est similaire au modèle de machine RAM qui remplace le ruban infini de la machine de Turing par un tableau infini. Chaque emplacement du tableau est accessible dans   Le temps, similaire à la mémoire vive d'un ordinateur, est utilisé. Contrairement au modèle basé sur la mémoire vive (RAM), il intègre également un cache : un deuxième niveau de stockage situé entre la mémoire vive (RAM) et le processeur. Les autres différences entre les deux modèles sont énumérées ci-dessous. Dans le modèle sans cache:

 
La cache à gauche contient   blocs de taille   chacun, pour un total de M objets. La mémoire externe à droite est illimitée.
  • La mémoire est divisée en blocs de   objets chacun.
  • Les opérations de chargement ou de stockage entre la mémoire principale et un registre du processeur peuvent désormais être effectuées à partir du cache.
  • Si une charge ou un stockage ne peut pas être traité à partir du cache, on parle d'un échec de cache.
  • Un défaut de cache entraîne le chargement d'un bloc depuis la mémoire principale vers le cache. Autrement dit, si le processeur tente d'accéder au mot   et   est la ligne contenant  , alors   est chargé dans le cache. Si le cache était déjà plein, une ligne sera également supprimée (voir la politique de remplacement ci-dessous).
  • La cache contient   objets, où  . Ceci est également connu sous le nom d'hypothèse de cache haut.
  • Le cache est entièrement associatif : chaque ligne peut être chargée à n'importe quel emplacement du cache[3].
  • La stratégie de remplacement est optimale. En d'autres termes, on suppose que le cache dispose de la séquence complète des accès mémoire pendant l'exécution de l'algorithme. S'il doit évacuer une ligne à l'instant 𝑡, il examine la séquence des requêtes futures et évacue la ligne dont le premier accès est le plus éloigné dans le temps. Ce comportement peut être reproduit en pratique avec la politique LURU (Least Recently Used), dont les performances sont très proches de celles de la stratégie de remplacement optimale hors ligne, à un facteur constant près [4]

Pour mesurer la complexité d'un algorithme s'exécutant dans le modèle indépendant du cache, on mesure le nombre de défauts de cache qu'il subit. Le modèle prenant en compte le fait que l'accès aux éléments du cache est beaucoup plus rapide que l'accès à la mémoire principale, le temps d'exécution de l'algorithme est uniquement déterminé par le nombre de transferts de mémoire entre le cache et la mémoire principale. Ceci est similaire au modèle de mémoire externe, qui présente toutes les caractéristiques mentionnées ci-dessus, mais les algorithmes indépendants du cache sont indépendants des paramètres du cache.   et   L’avantage d’un tel algorithme est que ce qui est efficace sur une machine ne tenant pas compte du cache l’est probablement aussi sur de nombreuses machines réelles, sans qu’il soit nécessaire de l’optimiser en fonction des paramètres spécifiques de chaque machine. Pour de nombreux problèmes, un algorithme optimal ne tenant pas compte du cache sera également optimal pour une machine disposant de plus de deux niveaux de hiérarchie de mémoire[4].

Exemples

modifier
 
Illustration de l'ordre en lignes et en colonnes

L'algorithme le plus simple, indépendants du cache, présenté dans Frigo et al., est une transposition de matrice hors place ( des algorithmes sur place ont également été conçus pour la transposition, mais ils sont beaucoup plus complexes pour les matrices non carrées). Étant donné un tableau A de dimensions m × n et un tableau B de dimensions n × m, nous souhaitons stocker la transposée de A dans B La solution naïve parcourt un tableau par lignes et l'autre par colonnes. Il en résulte que, lorsque les matrices sont grandes, une erreur de cache se produit à chaque étape du parcours par colonnes. Le nombre total d'erreurs de cache est  .

 
Principe d'un algorithme de transposition de matrices sans prise en compte du cache, utilisant une approche de type « diviser pour régner ». Le schéma illustre l'étape récursive ( ab ) consistant à diviser la matrice et à transposer chaque partie individuellement.

L'algorithme insensible au cache possède une complexité de travail optimale   et la complexité optimale du cache   L'idée de base est de réduire la transposée de deux grandes matrices à la transposée de petites (sous-)matrices. Pour ce faire, on divise les matrices en deux selon leur plus grande dimension jusqu'à n'avoir plus qu'à transposer une matrice dont la taille tient dans le cache. Comme la taille du cache est inconnue de l'algorithme, les matrices continueront d'être divisées récursivement même après ce point, mais ces subdivisions supplémentaires seront stockées dans le cache. Une fois que les dimensions m et n sont suffisamment petites pour qu'un tableau d'entrée de taille   et un tableau de sortie de taille   Pour tenir dans le cache, les parcours par lignes et par colonnes donnent tous deux le même résultat.   travail et   défauts de cache. En utilisant cette approche diviser pour régner, nous pouvons atteindre le même niveau de complexité pour la matrice globale.

(En principe, on pourrait continuer à diviser les matrices jusqu'à atteindre un cas de base de taille 1 × 1, mais en pratique, on utilise un cas de base plus grand (par exemple 16 × 16) afin d'amortir la surcharge des appels de sous-routines récursives.)

La majorité des algorithmes indépendants du cache s'appuient sur une approche de type « diviser pour régner ». Ils décomposent le problème de manière à ce qu'il tienne finalement dans le cache, quelle que soit sa taille, et interrompent la récursion à une taille réduite, déterminée par la surcharge liée aux appels de fonction et à d'autres optimisations similaires sans rapport avec le cache. Ils utilisent ensuite un modèle d'accès au cache efficace pour fusionner les résultats de ces petits problèmes résolus.

Comme le tri externe dans le modèle de mémoire externe, le tri indépendant du cache est possible sous deux formes : le tri en entonnoir, qui ressemble au tri fusion ; et le tri par distribution indépendant du cache, qui ressemble au tri rapide. À l’instar de leurs homologues en mémoire externe, les deux atteignent un temps d’exécution de  , qui correspond à une borne inférieure et est donc asymptotiquement optimale.

Aspect pratique

modifier

Une comparaison empirique de deux algorithmes basés sur la mémoire vive (RAM), d'un algorithme prenant en compte le cache et de deux algorithmes ne tenant pas compte du cache, mettant en œuvre des files d'attente prioritaires a révélé que :

  • Les algorithmes ne tenant pas compte du cache ont obtenu des performances inférieures à celles des algorithmes basés sur la RAM et des algorithmes prenant en compte le cache lorsque les données tiennent dans la mémoire principale.
  • L'algorithme prenant en compte le cache ne semblait pas significativement plus complexe à mettre en œuvre que les algorithmes ne tenant pas compte du cache, et a offert les meilleures performances dans tous les cas testés dans l'étude.
  • Les algorithmes ne tenant pas compte du cache ont surpassé les algorithmes basés sur la RAM lorsque la taille des données dépassait celle de la mémoire principale.

Une autre étude a comparé les tables de hachage (basées sur la RAM ou ne tenant pas compte du cache), les arbres B (tenant compte du cache) et une structure de données insensible au cache appelée « ensemble de Bender ». En termes de temps d'exécution et d'utilisation de la mémoire, la table de hachage s'est avérée la plus performante, suivie de l'arbre B, l'ensemble de Bender étant le moins performant dans tous les cas. L'utilisation de la mémoire pour tous les tests n'a pas dépassé la mémoire principale. Les tables de hachage ont été décrites comme faciles à implémenter, tandis que l'ensemble de Bender « nécessitait un effort plus important pour être implémenté correctement »[5].

Voir aussi

modifier
  • tri de distribution insensible au cache
  • Algorithme de mémoire externe
  • Tri en entonnoir

Références

modifier
(en) Cet article est partiellement ou en totalité issu de l’article de Wikipédia en anglais intitulé « Cache-oblivious algorithm » (voir la liste des auteurs).
  1. Harald Prokop Cache-Oblivious Algorithms Cache-Oblivious Algorithms, MASSACHUSETTS INSTITUTE OF TECHNOLOGY., 24 novembre 2019 (lire en ligne). Masters thesis, MIT. 1999.
  2. Nikolas Askitis et Justin Zobel, String Processing and Information Retrieval, vol. 3772, Springer, coll. « Lecture Notes in Computer Science », 2005 (ISBN 978-3-540-29740-6, DOI 10.1007/11575832_1), « Enhanced Byte Codes with Restricted Prefix Properties », p. 93
  3. Kumar, Cache-Oblivious Algorithms, Springer Verlag, 193–212 p. (CiteSeerx 10.1.1.150.5426)
  4. a et b M. Frigo et C. E. Leiserson « Cache-oblivious algorithms » (1999) (lire en ligne)
    Proc. IEEE Symp. on Foundations of Computer Science (FOCS)
  5. Verver, « Evaluation of a Cache-Oblivious Data Structure », 23 juin 2008 (consulté le 3 janvier 2022)

📚 Artikel Terkait di Wikipedia

Charles E. Leiserson

conçu la notion d'algorithme cache-oblivious (en), des algorithmes qui n'optimisent pas la taille du cache ou la longueur des lignes du cache et qui pourtant

Réseau de tri

Michael T. Goodrich, « Zig-zag Sort: A Simple Deterministic Data-Oblivious Sorting Algorithm Running in O(n log n) Time », 2014. Ian Parberry, « A Computer

Chiffrement symétrique cherchable

d'abord par Song, Wagner et Perrig bien que des travaux antérieurs sur Oblivious RAM par Goldreich et Ostrovsky puissent être utilisés en théorie pour

Cloud de FPGA

en temps réel et éco-énergétiques. Dans l'implémentation de certains algorithmes utilisés pour l' Apprentissage automatique tel que Long Short-Term Memory