E / S de fichier améliorées avec mappage de mémoire – Real Python

By | août 24, 2020

Python pas cher

Le Zen de Python a beaucoup de sagesse à offrir. Une idée particulièrement utile est qu '«il devrait y avoir une – et de préférence une seule – façon évidente de le faire.» Pourtant, il existe plusieurs façons de faire la plupart des choses en Python, et souvent pour une bonne raison. Par exemple, il existe plusieurs façons de lire un fichier en Python, y compris le rarement utilisé mmap module

Python mmap fournit une entrée et une sortie de fichier mappé en mémoire (E / S). Il vous permet de profiter des fonctionnalités du système d'exploitation de niveau inférieur pour lire les fichiers comme s'il s'agissait d'une grande chaîne ou d'un tableau. Cela peut améliorer considérablement les performances du code qui nécessite beaucoup d'E / S sur les fichiers.

Dans ce didacticiel, vous apprendrez:

  • Quelles sortes de mémoire d'ordinateur exister
  • Quels problèmes vous pouvez résoudre avec mmap
  • Comment utiliser le mappage mémoire pour lire plus rapidement de gros fichiers
  • Comment changer un partie d'un fichier sans réécrire le fichier entier
  • Comment utiliser mmap à partager l'information entre plusieurs processus

Comprendre la mémoire de l'ordinateur

Cartographie de la mémoire est une technique qui utilise des API de système d'exploitation de niveau inférieur pour charger un fichier directement dans la mémoire de l'ordinateur. Cela peut considérablement améliorer les performances d'E / S des fichiers dans votre programme. Pour mieux comprendre comment le mappage de mémoire améliore les performances, ainsi que comment et quand vous pouvez utiliser le mmap pour profiter de ces avantages en termes de performances, il est utile d’en apprendre un peu plus sur la mémoire de l’ordinateur.

La mémoire de l'ordinateur est un sujet important et complexe, mais ce didacticiel se concentre uniquement sur ce que vous devez savoir pour utiliser le mmap module efficacement. Pour les besoins de ce didacticiel, le terme Mémoire fait référence à la mémoire vive, ou RAM.

Il existe plusieurs types de mémoire informatique:

  1. Physique
  2. Virtuel
  3. partagé

Chaque type de mémoire peut entrer en jeu lorsque vous utilisez le mappage de mémoire, alors passons en revue chacun d'eux à un niveau élevé.

Mémoire physique

Mémoire physique est le type de mémoire le moins compliqué à comprendre, car il fait souvent partie du marketing associé à votre ordinateur. (Vous vous souvenez peut-être que lorsque vous avez acheté votre ordinateur, il a annoncé quelque chose comme 8 gigaoctets de RAM.) La mémoire physique est généralement fournie sur des cartes connectées à la carte mère de votre ordinateur.

La mémoire physique est la quantité de mémoire volatile que vos programmes peuvent utiliser lors de l’exécution. La mémoire physique ne doit pas être confondue avec le stockage, tel que votre disque dur ou disque SSD.

Mémoire virtuelle

La mémoire virtuelle est un moyen de gérer la gestion de la mémoire. Le système d'exploitation utilise la mémoire virtuelle pour donner l'impression que vous avez plus de mémoire que vous, ce qui vous permet de moins vous soucier de la quantité de mémoire disponible pour vos programmes à un moment donné. Dans les coulisses, votre système d'exploitation utilise des parties de votre stockage non volatile, comme votre disque SSD, pour simuler de la RAM supplémentaire.

Pour ce faire, votre système d'exploitation doit maintenir un mappage entre la mémoire physique et la mémoire virtuelle. Chaque système d'exploitation utilise son propre algorithme sophistiqué pour mapper les adresses de mémoire virtuelle sur les adresses physiques à l'aide d'une structure de données appelée table de pages.

Heureusement, la plupart de cette complication est cachée dans vos programmes. Vous n'avez pas besoin de comprendre les tables de pages ou le mappage logique-physique pour écrire du code d'E / S performant en Python. Cependant, connaître un peu la mémoire vous permet de mieux comprendre ce que l'ordinateur et les bibliothèques prennent en charge pour vous.

mmap utilise la mémoire virtuelle pour donner l'impression que vous avez chargé un très gros fichier en mémoire, même si le contenu du fichier est trop volumineux pour tenir dans votre mémoire physique.

La memoire partagée

La mémoire partagée est une autre technique fournie par votre système d'exploitation qui permet à plusieurs programmes d'accéder aux mêmes données simultanément. La mémoire partagée peut être un moyen très efficace de gérer les données dans un programme qui utilise la concurrence.

Python mmap utilise la mémoire partagée pour partager efficacement de grandes quantités de données entre plusieurs processus, threads et tâches Python qui se produisent simultanément.

Creuser plus profondément dans les E / S de fichier

Maintenant que vous avez une vue d'ensemble des différents types de mémoire, il est temps de comprendre ce qu'est le mappage mémoire et les problèmes qu'il résout. Le mappage de la mémoire est un autre moyen d'effectuer des E / S de fichier qui peut améliorer les performances et l'efficacité de la mémoire.

Afin d’apprécier pleinement ce que fait le mappage de la mémoire, il est utile d’envisager les E / S de fichier régulières dans une perspective de niveau inférieur. Beaucoup de choses se passent dans les coulisses lors de la lecture d'un fichier:

  1. Transfert contrôle au noyau ou au code du système d'exploitation principal avec des appels système
  2. Interagir avec le disque physique sur lequel réside le fichier
  3. Copier les données dans différents tampons entre l'espace utilisateur et l'espace noyau

Considérez le code suivant, qui effectue des E / S de fichier Python régulières:

def regular_io(nom de fichier):
    avec ouvert(nom de fichier, mode="r", codage="utf8") comme file_obj:
        texte = file_obj.lis()
        impression(texte)

Ce code lit l'intégralité du fichier dans la mémoire physique, s'il y en a suffisamment de disponible au moment de l'exécution, et l'imprime à l'écran.

Ce type d'E / S de fichier est quelque chose que vous avez peut-être appris au début de votre parcours Python. Le code n’est ni très dense ni compliqué. Cependant, ce qui se passe sous couvert d'appels de fonction comme lis() est très compliqué. N'oubliez pas que Python est un langage de programmation de haut niveau, une grande partie de la complexité peut être cachée au programmeur.

Appels système

En réalité, l'appel à lis() signale au système d'exploitation de faire beaucoup de travail sophistiqué. Heureusement, les systèmes d'exploitation offrent un moyen d'abstraire les détails spécifiques de chaque périphérique matériel de vos programmes avec des appels système. Chaque système d'exploitation implémentera cette fonctionnalité différemment, mais à tout le moins, lis() doit effectuer plusieurs appels système pour récupérer les données du fichier.

Tous les accès avec le matériel physique doivent avoir lieu dans un environnement protégé appelé espace noyau. Les appels système sont l'API fournie par le système d'exploitation pour permettre à votre programme de passer de l'espace utilisateur à l'espace noyau, où les détails de bas niveau du matériel physique sont gérés.

Dans le cas de lis(), plusieurs appels système sont nécessaires pour que le système d'exploitation interagisse avec le périphérique de stockage physique et renvoie les données.

Encore une fois, vous n’avez pas besoin d’une connaissance approfondie des détails des appels système et de l’architecture informatique pour comprendre le mappage de la mémoire. La chose la plus importante à retenir est que les appels système sont relativement coûteux en termes de calcul, donc moins vous faites d'appels système, plus votre code s'exécutera probablement rapidement.

En plus des appels système, l'appel à lis() implique également de nombreuses copies potentiellement inutiles de données entre plusieurs tampons de données avant que les données ne reviennent à votre programme.

En règle générale, tout se passe si vite que ce n’est pas perceptible. Mais toutes ces couches ajoutent latence et peut ralentir votre programme. C'est là que le mappage mémoire entre en jeu.

Optimisations du mappage de mémoire

Une façon d'éviter cette surcharge consiste à utiliser un fichier mappé en mémoire. Vous pouvez imaginer le mappage de mémoire comme un processus dans lequel les opérations de lecture et d'écriture sautent de nombreuses couches mentionnées ci-dessus et mappent les données demandées directement dans la mémoire physique.

Une approche d'E / S de fichier mappé en mémoire sacrifie l'utilisation de la mémoire au profit de la vitesse, ce que l'on appelle classiquement le compromis espace-temps. Cependant, le mappage de mémoire n'a pas besoin d'utiliser plus de mémoire que l'approche conventionnelle. Le système d'exploitation est très intelligent. Il chargera paresseusement les données comme demandé, de la même manière que les générateurs Python fonctionnent.

De plus, grâce à la mémoire virtuelle, vous pouvez charger un fichier plus volumineux que votre mémoire physique. Cependant, vous ne verrez pas les énormes améliorations des performances du mappage de la mémoire lorsqu'il n'y a pas assez de mémoire physique pour votre fichier, car le système d'exploitation utilisera un support de stockage physique plus lent comme un disque SSD pour imiter la mémoire physique qui lui manque .

Lire un fichier mappé en mémoire avec Python mmap

Maintenant, avec toute cette théorie à l'écart, vous vous demandez peut-être: «Comment utiliser Python mmap créer un fichier mappé en mémoire? »

Voici l'équivalent du mappage mémoire du code d'E / S de fichier que vous avez vu auparavant:

importer mmap

def mmap_io(nom de fichier):
    avec ouvert(nom de fichier, mode="r", codage="utf8") comme file_obj:
        avec mmap.mmap(file_obj.fileno(), longueur=0, accès=mmap.ACCESS_READ) comme mmap_obj:
            texte = mmap_obj.lis()
            impression(texte)

Ce code lit un fichier entier en mémoire sous forme de chaîne et l'affiche à l'écran, tout comme le faisait l'approche précédente avec les E / S de fichier normales.

En bref, en utilisant mmap est assez similaire à la manière traditionnelle de lire un fichier, avec quelques petits changements:

  1. Ouverture du fichier avec ouvert() ne suffit pas. Vous devez également utiliser mmap.mmap () pour signaler au système d'exploitation que vous souhaitez mapper le fichier dans la RAM.

  2. Vous devez vous assurer que le mode que vous utilisez avec ouvert() est compatible avec mmap.mmap (). Le mode par défaut pour ouvert() est pour la lecture, mais le mode par défaut pour mmap.mmap () est pour lire et l'écriture. Vous devrez donc être explicite lors de l’ouverture du fichier.

  3. Vous devez effectuer toutes les lectures et écritures en utilisant le mmap objet au lieu de l'objet fichier standard renvoyé par ouvert().

Implications sur les performances

L'approche de mappage de mémoire est légèrement plus compliquée que les E / S de fichier classiques car elle nécessite la création d'un autre objet. Cependant, ce petit changement peut entraîner de gros avantages en termes de performances lors de la lecture d'un fichier de quelques mégaoctets seulement. Voici une comparaison de la lecture du texte brut du célèbre roman L'histoire de Don Quichotte, soit environ 2,4 mégaoctets:

>>>

>>> importer timeit
>>> timeit.répéter(
...     "regular_io (nom de fichier)",
...     répéter=3,
...     nombre=1,
...     installer="from __main__ import regular_io, filename")
[0.02022400000000002, 0.01988580000000001, 0.020257300000000006]
>>> timeit.répéter(
...     "mmap_io (nom de fichier)",
...     répéter=3,
...     nombre=1,
...     installer="from __main__ import mmap_io, nom de fichier")
[0.006156499999999981, 0.004843099999999989, 0.004868600000000001]

Cela mesure le temps nécessaire pour lire un fichier entier de 2,4 mégaoctets en utilisant les E / S de fichier standard et les E / S de fichier mappées en mémoire. Comme vous pouvez le voir, l'approche mappée en mémoire prend environ 0,005 seconde contre près de 0,02 seconde pour l'approche normale. Cette amélioration des performances peut être encore plus importante lors de la lecture d'un fichier plus volumineux.

L'API fournie par Python mmap objet fichier est très similaire à l’objet fichier traditionnel à l’exception d’une superpuissance supplémentaire: Python mmap l'objet fichier peut être découpé comme des objets chaîne!

mmap Création d'objets

Il y a peu de subtilités dans la création du mmap objet qui mérite d'être examiné de plus près:

mmap.mmap(file_obj.fileno(), longueur=0, accès=mmap.ACCESS_READ)

mmap nécessite un descripteur de fichier, qui provient du fileno () méthode d'un objet fichier régulier. Un descripteur de fichier est un identifiant interne, généralement un entier, que le système d'exploitation utilise pour suivre les fichiers ouverts.

Le deuxième argument de mmap est longueur = 0. Il s'agit de la longueur en octets de la carte mémoire. 0 est une valeur spéciale indiquant que le système doit créer une carte mémoire suffisamment grande pour contenir l'intégralité du fichier.

le accès L'argument indique au système d'exploitation comment vous allez interagir avec la mémoire mappée. Les options sont ACCESS_READ, ACCESS_WRITE, ACCESS_COPY, et ACCESS_DEFAULT. Ceux-ci sont quelque peu similaires aux mode arguments pour le intégré ouvert():

  • ACCESS_READ crée une carte mémoire en lecture seule.
  • ACCESS_DEFAULT par défaut, le mode spécifié dans l'option prot argument, qui est utilisé pour la protection de la mémoire.
  • ACCESS_WRITE et ACCESS_COPY sont les deux modes d'écriture, que vous découvrirez ci-dessous.

Le descripteur de fichier, longueur, et accès Les arguments représentent le strict minimum dont vous avez besoin pour créer un fichier mappé en mémoire qui fonctionnera sur les systèmes d'exploitation tels que Windows, Linux et macOS. Le code ci-dessus est multiplateforme, ce qui signifie qu'il lira le fichier via l'interface de mappage de mémoire sur tous les systèmes d'exploitation sans avoir besoin de savoir sur quel système d'exploitation le code s'exécute.

Un autre argument utile est décalage, qui peut être une technique d'économie de mémoire. Cela instruit le mmap pour créer une mappe mémoire à partir d'un décalage spécifié dans le fichier.

mmap Objets en tant que chaînes

Comme mentionné précédemment, le mappage de mémoire charge de manière transparente le contenu du fichier en mémoire sous forme de chaîne. Ainsi, une fois que vous ouvrez le fichier, vous pouvez effectuer de nombreuses opérations que vous utilisez avec des chaînes, telles que le découpage:

importer mmap

def mmap_io(nom de fichier):
    avec ouvert(nom de fichier, mode="r", codage="utf8") comme file_obj:
        avec mmap.mmap(file_obj.fileno(), longueur=0, accès=mmap.ACCESS_READ) comme mmap_obj:
            impression(mmap_obj[[[[dix:20])

Ce code imprime dix caractères de mmap_obj à l'écran et lit également ces dix caractères dans la mémoire physique. Encore une fois, les données sont lues paresseusement.

La tranche ne fait pas avancer la position du fichier interne. Donc, si tu devais appeler lis() après une tranche, vous liriez toujours depuis le début du fichier.

Rechercher un fichier mappé en mémoire

En plus de trancher, le mmap module autorise d'autres comportements de type chaîne tels que l'utilisation trouver() et rfind () pour rechercher un fichier pour un texte spécifique. Par exemple, voici deux approches pour trouver la première occurrence de " les " dans un fichier:

importer mmap

def regular_io_find(nom de fichier):
    avec ouvert(nom de fichier, mode="r", codage="utf-8") comme file_obj:
        texte = file_obj.lis()
        impression(texte.trouver(" les "))

def mmap_io_find(nom de fichier):
    avec ouvert(nom de fichier, mode="r", codage="utf-8") comme file_obj:
        avec mmap.mmap(file_obj.fileno(), longueur=0, accès=mmap.ACCESS_READ) comme mmap_obj:
            impression(mmap_obj.trouver(b" les "))

Ces deux fonctions recherchent toutes deux dans un fichier la première occurrence de " les " La principale différence entre eux est que le premier utilise trouver() sur un objet string, alors que le second utilise trouver() sur un objet fichier mappé en mémoire.

Voici la différence de performances:

>>>

>>> importer timeit
>>> timeit.répéter(
...     "regular_io_find (filename)",
...     répéter=3,
...     nombre=1,
...     installer="from __main__ import regular_io_find, filename")
[0.01919180000000001, 0.01940510000000001, 0.019157700000000027]
>>> timeit.répéter(
...     "mmap_io_find (nom de fichier)",
...     répéter=3,
...     nombre=1,
...     installer="from __main__ import mmap_io_find, nom de fichier")
[0.0009397999999999906, 0.0018005999999999855, 0.000826699999999958]

C’est une différence de plusieurs ordres de grandeur! Encore une fois, vos résultats peuvent différer en fonction de votre système d'exploitation.

Les fichiers mappés en mémoire peuvent également être utilisés directement avec des expressions régulières. Prenons l'exemple suivant qui recherche et imprime tous les mots de cinq lettres:

importer 
importer mmap

def mmap_io_re(nom de fichier):
    cinq_lettre_mot = .compiler(rb" b[a-zA-Z]5 b ")

    avec ouvert(nom de fichier, mode="r", codage="utf-8") comme file_obj:
        avec mmap.mmap(file_obj.fileno(), longueur=0, accès=mmap.ACCESS_READ) comme mmap_obj:
            pour mot dans cinq_lettre_mot.Trouver tout(mmap_obj):
                impression(mot)

Ce code lit le fichier entier et imprime chaque mot contenant exactement cinq lettres. Gardez à l'esprit que les fichiers mappés en mémoire fonctionnent avec des chaînes d'octets, de sorte que les expressions régulières doivent également utiliser des chaînes d'octets.

Voici le code équivalent utilisant les E / S de fichier standard:

importer 

def regular_io_re(nom de fichier):
    cinq_lettre_mot = .compiler(r" b[a-zA-Z]5 b ")

    avec ouvert(nom de fichier, mode="r", codage="utf-8") comme file_obj:
        pour mot dans cinq_lettre_mot.Trouver tout(file_obj.lis()):
            impression(mot)

Ce code imprime également tous les mots à cinq caractères du fichier, mais il utilise le mécanisme d'E / S de fichier traditionnel au lieu des fichiers mappés en mémoire. Comme précédemment, les performances diffèrent entre les deux approches:

>>>

>>> importer timeit
>>> timeit.répéter(
...     "regular_io_re (nom de fichier)",
...     répéter=3,
...     nombre=1,
...     installer="from __main__ import regular_io_re, filename")
[0.10474110000000003, 0.10358619999999996, 0.10347820000000002]
>>> timeit.répéter(
...     "mmap_io_re (nom de fichier)",
...     répéter=3,
...     nombre=1,
...     installer="from __main__ import mmap_io_re, nom de fichier")
[0.0740976000000001, 0.07362639999999998, 0.07380980000000004]

L'approche mappée en mémoire est toujours un ordre de grandeur plus rapide.

Objets mappés en mémoire sous forme de fichiers

Un fichier mappé en mémoire est une chaîne partielle et un fichier partiel, donc mmap vous permet également d'effectuer des opérations de fichiers courantes telles que chercher(), dire(), et readline (). Ces fonctions fonctionnent exactement comme leurs homologues objet fichier standard.

Par exemple, voici comment rechercher un emplacement particulier dans un fichier, puis effectuer une recherche sur un mot:

importer mmap

def mmap_io_find_and_seek(nom de fichier):
    avec ouvert(nom de fichier, mode="r", codage="utf-8") comme file_obj:
        avec mmap.mmap(file_obj.fileno(), longueur=0, accès=mmap.ACCESS_READ) comme mmap_obj:
            mmap_obj.chercher(10 000)
            mmap_obj.trouver(b" les ")

Ce code cherchera à l'emplacement 10 000 dans le fichier, puis recherchez l'emplacement de la première occurrence de " les ".

chercher() fonctionne exactement de la même manière sur les fichiers mappés en mémoire que sur les fichiers normaux:

def regular_io_find_and_seek(nom de fichier):
    avec ouvert(nom de fichier, mode="r", codage="utf-8") comme file_obj:
        file_obj.chercher(10 000)
        texte = file_obj.lis()
        texte.trouver(" les ")

Le code des deux approches est très similaire. Voyons comment leurs performances se comparent:

>>>

>>> importer timeit
>>> timeit.répéter(
...     "regular_io_find_and_seek (filename)",
...     répéter=3,
...     nombre=1,
...     installer="from __main__ import regular_io_find_and_seek, filename")
[0.019396099999999916, 0.01936059999999995, 0.019192100000000045]
>>> timeit.répéter(
...     "mmap_io_find_and_seek (nom de fichier)",
...     répéter=3,
...     nombre=1,
...     installer="from __main__ import mmap_io_find_and_seek, filename")
[0.000925100000000012, 0.000788299999999964, 0.0007854999999999945]

Encore une fois, après seulement quelques petites modifications du code, votre approche mappée en mémoire est beaucoup plus rapide.

Écriture d'un fichier mappé en mémoire avec Python mmap

Le mappage de mémoire est le plus utile pour lire des fichiers, mais vous pouvez également l'utiliser pour écrire des fichiers. le mmap L'API pour l'écriture de fichiers est très similaire aux E / S de fichiers classiques, à quelques différences près.

Voici un exemple d’écriture de texte dans un fichier mappé en mémoire:

importer mmap

def mmap_io_write(nom de fichier, texte):
    avec ouvert(nom de fichier, mode="w", codage="utf-8") comme file_obj:
        avec mmap.mmap(file_obj.fileno(), longueur=0, accès=mmap.ACCESS_WRITE) comme mmap_obj:
            mmap_obj.écrire(texte)

Ce code écrit du texte dans un fichier mappé en mémoire. Cependant, cela soulèvera un ValueError exception si le fichier est vide au moment de la création du mmap objet.

Python mmap le module n’autorise pas le mappage de mémoire d’un fichier vide. Ceci est raisonnable car, conceptuellement, un fichier mappé en mémoire vide n'est qu'un tampon de mémoire, donc aucun objet de mappage mémoire n'est nécessaire.

En règle générale, le mappage de mémoire est utilisé en mode lecture ou lecture / écriture. Par exemple, le code suivant montre comment lire rapidement un fichier et en modifier uniquement une partie:

importer mmap

def mmap_io_write(nom de fichier):
    avec ouvert(nom de fichier, mode="r +") comme file_obj:
        avec mmap.mmap(file_obj.fileno(), longueur=0, accès=mmap.ACCESS_WRITE) comme mmap_obj:
            mmap_obj[[[[dix:16] = b"python"
            mmap_obj.affleurer()

Cette fonction ouvrira un fichier contenant déjà au moins seize caractères et changera les caractères 10 à 15 en "python".

Les modifications écrites dans mmap_obj sont visibles dans le fichier sur disque ainsi qu'en mémoire. La documentation officielle de Python recommande de toujours appeler affleurer() pour garantir que les données sont réécrites sur le disque.

Modes d'écriture

La sémantique de l'opération d'écriture est contrôlée par le accès paramètre. Une distinction entre l'écriture de fichiers mappés en mémoire et les fichiers normaux réside dans les options accès paramètre. Il existe deux options pour contrôler l'écriture des données dans un fichier mappé en mémoire:

  1. ACCESS_WRITE spécifie une sémantique d'écriture directe, ce qui signifie que les données seront écrites dans la mémoire et persistantes sur le disque.
  2. ACCESS_COPY n'écrit pas les modifications sur le disque, même si affleurer() est appelé.

En d'autres termes, ACCESS_WRITE écrit à la fois dans la mémoire et dans le fichier, alors que ACCESS_COPY écrit uniquement dans la mémoire et ne pas au fichier sous-jacent.

Rechercher et remplacer du texte

Les fichiers mappés en mémoire exposent les données sous la forme d'une chaîne d'octets, mais cette chaîne d'octets présente un autre avantage important par rapport à une chaîne régulière. Les données de fichier mappées en mémoire sont une chaîne de mutable octets. Cela signifie qu'il est beaucoup plus simple et efficace d'écrire du code qui recherche et remplace les données dans un fichier:

importer mmap
importer os
importer shutil

def regular_io_find_and_replace(nom de fichier):
    avec ouvert(nom de fichier, "r", codage="utf-8") comme orig_file_obj:
        avec ouvert("tmp.txt", "w", codage="utf-8") comme new_file_obj:
            orig_text = orig_file_obj.lis()
            nouveau_texte = orig_text.remplacer(" les ", "python")
            new_file_obj.écrire(nouvelle ligne)

    shutil.copier un fichier("tmp.txt", nom de fichier)
    os.retirer("tmp.txt")

def mmap_io_find_and_replace(nom de fichier):
    avec ouvert(nom de fichier, mode="r +", codage="utf-8") comme file_obj:
        avec mmap.mmap(file_obj.fileno(), longueur=0, accès=mmap.ACCESS_WRITE) comme mmap_obj:
            emplacement = mmap_obj.trouver(b" les ")
            mmap_obj[[[[emplacement:emplacement + 8] = b"python"
            mmap_obj.affleurer()

Ces deux fonctions changent le mot " les " à "python" dans le fichier donné. Comme vous pouvez le voir, l'approche mappée en mémoire est beaucoup plus concise. Il n'est pas non plus nécessaire de suivre manuellement un fichier temporaire supplémentaire pour effectuer le remplacement en place.

L'approche mappée en mémoire est également beaucoup plus rapide:

>>>

>>> importer timeit
>>> timeit.répéter(
...     "regular_io_find_and_replace (filename)",
...     répéter=3,
...     nombre=1,
...     installer="from __main__ import regular_io_find_and_replace, filename")
[0.060848899999999984, 0.061654600000000004, 0.061154300000000106]
>>> timeit.répéter(
...     "mmap_io_find_and_replace (filename)",
...     répéter=3,
...     nombre=1,
...     installer="from __main__ import mmap_io_find_and_replace, filename")
[0.0014825999999998896, 0.0011419999999999764, 0.0011451999999998463]

Fidèle à la tendance, la cartographie mémoire gagne à nouveau en vitesse. Dans ce scénario de base de recherche et remplacement, le mappage de mémoire se traduit à la fois par un code plus concis et des performances améliorées!

Partage de données entre processus avec Python mmap

Jusqu'à présent, vous utilisiez des fichiers mappés en mémoire uniquement pour les données sur disque. Cependant, vous pouvez également créer anonyme mappages de mémoire qui n'ont pas de stockage physique. Cela peut être fait en passant -1 comme descripteur de fichier:

importer mmap

avec mmap.mmap(-1, longueur=100, accès=mmap.ACCESS_WRITE) comme mmap_obj:
    mmap_obj[[[[0:100] = b"une" * 100
    impression(mmap_obj[[[[0:100])

Cela crée un objet anonyme mappé en mémoire dans la RAM qui contient 100 copies de la lettre "une".

Un objet anonyme mappé en mémoire est essentiellement un tampon d'une taille spécifique, spécifiée par le longueur paramètre, en mémoire. Le tampon est similaire à io.StringIO ou io.BytesIO de la bibliothèque standard. Cependant, un objet anonyme mappé en mémoire prend en charge le partage entre plusieurs processus, qui ni io.StringIO ni io.BytesIO permet.

Cela signifie que vous pouvez utiliser des objets anonymes mappés en mémoire pour échanger des données entre les processus même si les processus ont une mémoire et des piles complètement séparées. Voici un exemple de création d'un objet anonyme mappé en mémoire pour partager des données qui peuvent être écrites et lues à partir des deux processus:

importer mmap

def sharing_with_mmap():
    BUF = mmap.mmap(-1, longueur=100, accès=mmap.ACCESS_WRITE)

    pid = os.fourchette()
    si pid == 0:
        # Processus enfant
        BUF[[[[0:100] = b"une" * 100
    autre:
        temps.dormir(2)
        impression(BUF[[[[0:100])

Avec ce code, vous créez un tampon mappé en mémoire de 100 octets et permettent à ce tampon d'être lu et écrit à partir des deux processus. Cette approche peut être utile si vous souhaitez économiser de la mémoire tout en partageant une grande quantité de données entre plusieurs processus.

Le partage de mémoire avec le mappage de mémoire présente plusieurs avantages:

  • Les données n'ont pas besoin d'être copiées entre les processus.
  • Le système d'exploitation gère la mémoire de manière transparente.
  • Les données n'ont pas besoin d'être picklées entre les processus, ce qui économise du temps CPU.

En parlant de décapage, cela vaut la peine de souligner que mmap est incompatible avec les API de plus haut niveau et plus complètes comme les API intégrées multitraitement module. le multitraitement le module nécessite des données transmises entre les processus pour prendre en charge le protocole pickle, qui mmap ne fait pas.

Vous pourriez être tenté d'utiliser multitraitement au lieu de os.fork (), comme ce qui suit:

de multitraitement importer Processus

def modifier(buf):
    buf[[[[0:100] = b"xy" * 50

si __Nom__ == "__principale__":
    BUF = mmap.mmap(-1, longueur=100, accès=mmap.ACCESS_WRITE)
    BUF[[[[0:100] = b"une" * 100
    p = Processus(cible=modifier, args=(BUF,))
    p.début()
    p.joindre()
    impression(BUF[[[[0:100])

Ici, vous essayez de créer un nouveau processus et de lui transmettre le tampon mappé en mémoire. Ce code soulèvera immédiatement un Erreur-type parce que le mmap l'objet ne peut pas être décapé, ce qui est nécessaire pour transmettre les données au deuxième processus. Par conséquent, pour partager des données avec le mappage de la mémoire, vous devez vous en tenir au niveau inférieur os.fork ().

Si vous utilisez Python 3.8 ou une version plus récente, vous pouvez utiliser le nouveau la memoire partagée module pour partager plus efficacement les données entre les processus Python:

de multitraitement importer Processus
de multitraitement importer la memoire partagée

def modifier(buf_name):
    shm = la memoire partagée.La memoire partagée(buf_name)
    shm.buf[[[[0:50] = b"b" * 50
    shm.proche()

si __Nom__ == "__principale__":
    shm = la memoire partagée.La memoire partagée(créer=Vrai, Taille=100)

    essayer:
        shm.buf[[[[0:100] = b"une" * 100
        proc = Processus(cible=modifier, args=(shm.Nom,))
        proc.début()
        proc.joindre()
        impression(octets(shm.buf[:[:[:[:100]))
    enfin:
        shm.proche()
        shm.dissocier()

Ce petit programme crée une liste de 100 caractères et modifie les 50 premiers à partir d'un autre processus.

Notez que seul le nom du tampon est passé au second processus. Ensuite, le deuxième processus peut récupérer ce même bloc de mémoire en utilisant le nom unique. C'est une particularité du la memoire partagée module alimenté par mmap. Sous le capot, le la memoire partagée module utilise l'API unique de chaque système d'exploitation pour créer des mappages de mémoire nommés pour vous.

Vous connaissez maintenant certains des détails d'implémentation sous-jacents de la nouvelle fonctionnalité de mémoire partagée Python 3.8 ainsi que comment l'utiliser mmap directement!

Conclusion

Le mappage de mémoire est une approche alternative aux E / S de fichier disponible pour les programmes Python via le mmap module. Le mappage de la mémoire utilise des API de système d'exploitation de niveau inférieur pour stocker le contenu des fichiers directement dans la mémoire physique. Cette approche se traduit souvent par une amélioration des performances d'E / S car elle évite de nombreux appels système coûteux et réduit les transferts coûteux de tampons de données.

Dans ce didacticiel, vous avez appris:

  • Quelles sont les différences entre physique, virtuel, et la memoire partagée
  • Comment optimiser utilisation de la mémoire avec cartographie mémoire
  • Comment utiliser Python mmap module pour implémenter le mappage mémoire dans votre code

le mmap L'API est similaire à l'API d'E / S de fichier standard, c'est donc assez simple à tester. Essayez-le dans votre propre code pour voir si votre programme peut bénéficier des améliorations de performances offertes par le mappage mémoire.