[Edit: Ce problème s'applique uniquement aux systèmes 32 bits. Si votre ordinateur, votre système d'exploitation et votre implémentation python implémentation sont 64 bits, alors mmap-ing de gros fichiers fonctionne de manière fiable et est extrêmement efficace.]
J'écris un module qui, entre autres, permet un accès en lecture au niveau des bits aux fichiers. Les fichiers peuvent potentiellement être volumineux (des centaines de Go), j'ai donc écrit une classe simple qui me permet de traiter le fichier comme une chaîne et masque toute la recherche et la lecture.
Au moment où j'ai écrit ma classe wrapper, je ne connaissais pas le module mmap . En lisant la documentation de mmap, j'ai pensé "super - c'est exactement ce dont j'avais besoin, je vais sortir mon code et le remplacer par un mmap. C'est probablement beaucoup plus efficace et c'est toujours bon de supprimer du code."
Le problème est que mmap ne fonctionne pas pour les gros fichiers! C'est très surprenant pour moi car je pensais que c'était peut-être l'application la plus évidente. Si le fichier dépasse quelques gigaoctets, j'obtiens un EnvironmentError: [Errno 12] Cannot allocate memory
. Cela se produit uniquement avec une version 32 bits Python), donc il semble qu'il manque d'espace d'adressage, mais je ne trouve aucune documentation à ce sujet.
Mon code est juste
f = open('somelargefile', 'rb')
map = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
Donc ma question est est-ce qu'il me manque quelque chose d'évident ici? Y a-t-il un moyen de faire fonctionner mmap de manière portative sur de gros fichiers ou devrais-je revenir à mon naïf wrapper de fichier?
Mise à jour: Il semble y avoir un sentiment que le Python mmap devrait avoir les mêmes restrictions que le mmap POSIX. Pour mieux exprimer ma frustration, voici une classe simple qui a une petite partie des fonctionnalités de mmap.
import os
class Mmap(object):
def __init__(self, f):
"""Initialise with a file object."""
self.source = f
def __getitem__(self, key):
try:
# A slice
self.source.seek(key.start, os.SEEK_SET)
return self.source.read(key.stop - key.start)
except AttributeError:
# single element
self.source.seek(key, os.SEEK_SET)
return self.source.read(1)
Il est en lecture seule et ne fait rien d'extraordinaire, mais je peux le faire de la même manière qu'avec un mmap:
map2 = Mmap(f)
print map2[0:10]
print map2[10000000000:10000000010]
sauf qu'il n'y a aucune restriction sur la taille des fichiers. Pas trop difficile vraiment ...
Depuis IEEE 1003.1:
La fonction mmap () doit établir un mappage entre l'espace d'adressage d'un processus et un fichier, un objet de mémoire partagée ou un objet de mémoire de type [TYM].
Il a besoin de tout l'espace d'adressage virtuel car c'est exactement ce que mmap()
fait.
Le fait que ce ne soit pas vraiment manquer de mémoire n'a pas d'importance - vous ne pouvez pas mapper plus d'espace d'adressage que vous n'en avez disponible. Puisque vous prenez ensuite le résultat et y accédez comme s'il était était mémoire, comment proposez-vous exactement d'accéder à plus de 2 ^ 32 octets dans le fichier? Même si mmap()
n'échouait pas, vous ne pouviez toujours lire que les 4 premiers Go avant de manquer d'espace dans un espace d'adressage 32 bits. Vous pouvez, bien sûr, mmap()
une fenêtre 32 bits glissante sur le fichier, mais cela ne vous rapportera pas nécessairement d'avantages à moins que vous ne puissiez optimiser votre modèle d'accès de manière à limiter le nombre de fois que vous devez visitez les fenêtres précédentes.
Désolé de répondre à ma propre question, mais je pense que le vrai problème que j'ai eu n'était pas de réaliser que mmap était un appel système POSIX standard avec des caractéristiques et des limitations particulières et que le Python mmap est censé simplement exposer sa fonctionnalité.
La documentation Python ne mentionne pas le mmap POSIX et donc si vous y arrivez en tant que Python programmeur sans beaucoup de connaissance de POSIX (comme je l'ai fait)) alors le problème d'espace d'adressage semble assez arbitraire et mal conçu!
Merci aux autres affiches de m'avoir appris le vrai sens de mmap. Malheureusement, personne n'a suggéré une meilleure alternative à ma classe artisanale pour traiter les fichiers volumineux comme des chaînes, je vais donc devoir m'en tenir pour l'instant. Peut-être que je vais le nettoyer et l'intégrer à l'interface publique de mon module lorsque j'en aurai l'occasion.
Un programme et un système d'exploitation 32 bits ne peuvent adresser qu'un maximum de 32 bits de mémoire, soit 4 Go. Il existe d'autres facteurs qui rendent le total encore plus petit; par exemple, Windows réserve entre 0,5 et 2 Go pour l'accès au matériel, et bien sûr, votre programme prendra également de la place.
Edit : La chose évidente qui vous manque est une compréhension de la mécanique de mmap, sur n'importe quel système d'exploitation. Il vous permet de mapper une partie d'un fichier à une plage de mémoire - une fois que vous avez fait cela, tout accès à cette partie du fichier se produit avec le moins de surcharge possible. La surcharge est faible car le mappage est effectué une fois et n'a pas à changer chaque fois que vous accédez à une plage différente. L'inconvénient est que vous avez besoin d'une plage d'adresses ouverte suffisante pour la partie que vous essayez de mapper. Si vous mappez le fichier entier en même temps, vous aurez besoin d'un trou dans la carte mémoire suffisamment grand pour contenir le fichier entier. Si un tel trou n'existe pas ou est plus grand que tout votre espace d'adressage, il échoue.
le module mmap fournit tous les outils dont vous avez besoin pour fouiller dans votre gros fichier, mais en raison des limitations que d'autres personnes ont mentionnées, vous ne pouvez pas le mapper en même temps . Vous pouvez mapper un morceau de bonne taille à la fois, effectuer un traitement, puis le démapper et en mapper un autre. les arguments clés de la classe mmap
sont length
et offset
, qui font exactement ce à quoi ils ressemblent, vous permettant de mapper length
octets, en commençant par l'octet offset
dans le fichier mappé. Chaque fois que vous souhaitez lire une section de la mémoire qui est en dehors de la fenêtre mappée, vous devez mapper dans une nouvelle fenêtre.
Le point qui vous manque est que mmap est une fonction de mappage de mémoire qui mappe un fichier en mémoire pour un accès arbitraire à travers la plage de données demandée par tous les moyens.
Ce que vous recherchez ressemble plus à une sorte de classe de fenêtre de données qui présente une API vous permettant de regarder à tout moment de petites fenêtres d'une grande structure de données. L'accès au-delà des limites de cette fenêtre ne serait pas possible autrement qu'en appelant la propre API de la fenêtre de données.
C'est bien, mais ce n'est pas une carte mémoire, c'est quelque chose qui offre l'avantage d'une plage de données plus large au prix d'une api plus restrictive.
mmap()
mmap()
nécessite la prise en charge matérielle du processeur pour avoir un sens avec des fichiers volumineux de plus de quelques Gio.
Il utilise les sous-systèmes MMU du CPU et d'interrompre pour permettre d'exposer les données comme si elles étaient déjà chargées en RAM.
Le MMU est un matériel qui générera une interruption à chaque fois qu'une adresse correspondant à des données non physiques RAM est accédée, et le système d'exploitation gérera l'interruption d'une manière cela a du sens au moment de l'exécution, de sorte que le code d'accès ne sait jamais (ou n'a pas besoin de savoir) que les données ne tiennent pas dans la RAM.
Cela rend votre code d'accès simple à écrire. Cependant, pour utiliser mmap()
de cette façon, tout ce qui est impliqué devra gérer des adresses 64 bits.
Ou bien il peut être préférable d'éviter complètement mmap()
et de faire votre propre gestion de la mémoire.
Vous définissez le paramètre de longueur sur zéro, ce qui signifie la carte dans tout le fichier. Sur une version 32 bits, cela ne sera pas possible si la longueur du fichier est supérieure à 2 Go (éventuellement 4 Go).
Vous demandez au système d'exploitation de mapper le fichier entier dans une plage de mémoire. Il ne sera pas lu tant que vous n'aurez pas déclenché des erreurs de page en lecture/écriture, mais il doit toujours s'assurer que toute la plage est disponible pour votre processus, et si cette plage est trop grande, il y aura des difficultés.