Pourquoi le mmap de Python ne fonctionne-t-il pas avec de gros fichiers?

[Edit: ce problème ne s'applique qu'aux systèmes 32 bits. Si votre ordinateur, votre OS et votre implémentation python sont 64-bit, alors mmap-ing de gros fichiers fonctionne de manière fiable et est extrêmement efficace.]

je suis en train d'écrire un module qui permet, entre autres choses, un accès en lecture bitwise aux fichiers. Les fichiers peuvent potentiellement être grands (des centaines de GO) donc j'ai écrit une classe simple qui me permet de traiter le fichier comme une chaîne et cache toutes les recherches et lecture.

au moment où j'ai écrit ma classe d'emballage, Je ne connaissais pas le" module mmap 1519110920". En lisant la documentation pour mmap j'ai pensé " génial - 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 il est toujours bon de supprimer le code."

Le problème est que mmap ne fonctionne pas pour les gros fichiers! C'est très surprenant pour moi car je pensais c'était peut-être l'application la plus évidente. Si le fichier est au-dessus de quelques gigaoctets alors j'obtiens un EnvironmentError: [Errno 12] Cannot allocate memory . Cela ne se produit qu'avec une construction Python 32 bits, donc il semble qu'il manque d'espace d'adresse, mais je ne peux pas trouver de 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 que je manque quelque chose d'évident ici? y a-t-il un moyen d'amener mmap à travailler de manière satisfaisante sur les gros dossiers ou dois-je retourner à mon naïf fichier wrapper?


mise à jour: il semble y avoir un sentiment que le mmap Python devrait avoir les mêmes restrictions que le MMAP POSIX. Pour mieux exprimer ma frustration voici une classe simple qui a une petite partie de la fonctionnalité 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)

c'est en lecture seule et ne fait rien de fantaisiste, mais je peux le faire exactement comme avec un mmap:

map2 = Mmap(f)
print map2[0:10]
print map2[10000000000:10000000010]

sauf qu'il n'y a pas restrictions sur filesize. Pas trop difficile, vraiment...

43
demandé sur Yaakov Belch 2009-11-02 18:34:22

8 réponses

de IEEE 1003.1:

la fonction mmap () doit établir correspondance entre d'adressage d'un processus espace et un fichier, mémoire partagée objet, ou [TYM] mémoire tapée objet.

il a besoin de tout l'espace d'adresse virtuel parce que c'est exactement ce que mmap() fait .

le fait qu'il n'est pas vraiment s'épuise de mémoire ne matière - vous ne pouvez pas cartographier plus d'espace d'adresse que vous avez disponible. Puisque vous prenez alors le résultat et l'accès comme si elle était mémoire, comment exactement proposez-vous d'accéder à plus de 2^32 octets dans le fichier? Même si mmap() n'avait pas échoué, vous ne pouviez lire que les premiers 4 Go avant de manquer d'espace dans un espace d'adresse de 32 bits. Vous pouvez, bien sûr, mmap() une fenêtre coulissante de 32 bits au-dessus du fichier, mais cela ne vous apportera pas nécessairement un avantage à moins que vous peut optimiser votre modèle d'accès de sorte que vous limitez combien de fois vous devez visiter les fenêtres précédentes.

36
répondu Nick Bastin 2009-11-02 16:52:46

désolé de répondre à ma propre question, mais je pense que le vrai problème que j'avais n'était pas de me rendre compte que mmap était un appel système POSIX standard avec des caractéristiques et des limites particulières et que le mmap Python est supposé juste exposer sa fonctionnalité.

la documentation de Python ne mentionne pas le POSIX mmap et donc si vous venez à elle comme un programmeur Python sans beaucoup de connaissance de POSIX (comme je l'ai fait) alors le problème d'espace d'adresse apparaît assez arbitraire et mal conçu!

Merci aux autres affiches pour m'avoir appris le vrai sens du mmap. Malheureusement, personne n'a suggéré une meilleure alternative à ma classe conçue à la main pour traiter les gros fichiers comme des chaînes, donc je vais devoir m'y tenir pour l'instant. Peut-être que je vais le nettoyer et en faire une partie de l'interface publique de mon module quand j'en aurai l'occasion.

17
répondu Scott Griffiths 2009-11-05 17:50:15

un programme 32 bits et un système d'exploitation ne peuvent traiter qu'un maximum de 32 bits de mémoire, soit 4 Go. Il y a 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 matériel, et bien sûr votre programme va prendre un peu de place aussi bien.

Edit: la chose évidente que vous manquez est une compréhension de la mécanique de mmap, sur n'importe quel système d'exploitation. Il 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 possible de frais généraux. C'est peu de frais généraux parce que la cartographie est faite une fois, et ne doit pas changer chaque fois que vous accédez à une gamme différente. L'inconvénient est que vous avez besoin de l'adresse de gamme suffisante pour la partie que vous essayez de carte. Si vous cartographiez l'ensemble du fichier à la fois, vous aurez besoin d'un trou dans la carte mémoire assez grand pour s'adapter à l'ensemble du fichier. Si un tel trou ne existe, ou est plus grand que votre espace d'adresse entier, il échoue.

16
répondu Mark Ransom 2009-11-02 20:18:58

le module mmap fournit tous les outils dont vous avez besoin pour fouiner dans votre grand fichier, mais en raison des limites que d'autres personnes ont mentionnées, vous ne pouvez pas mapper tout à la fois . Vous pouvez cartographier un morceau de bonne taille à la fois, faire un peu de traitement et ensuite démonter cela et cartographier un autre. les arguments clés de la classe mmap sont length et offset , qui font exactement ce à quoi ils ressemblent, vous permettant de cartographier length octets, départ à l'octet offset dans le fichier mappé. Chaque fois que vous souhaitez lire une section de mémoire qui est en dehors de la fenêtre mappée, vous devez mapper dans une nouvelle fenêtre.

9
répondu SingleNegationElimination 2009-11-02 20:48:42

le point que vous manquez est que mmap est une fonction de mappage de mémoire qui mappe un fichier dans la mémoire pour l'accès arbitraire à travers la gamme de données demandée par n'importe quel moyen.

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 de petites fenêtres d'une grande structure de données à tout moment. 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 plus large plage de données au prix d'une plus restrictive de l'api.

6
répondu morechilli 2009-11-02 17:34:48

vous définissez le paramètre length à zéro, ce qui signifie map dans le fichier entier. Sur une construction 32 bits, cela ne sera pas possible si la longueur du fichier est supérieure à 2 Go (peut-être 4 Go).

2
répondu Rod Hyde 2009-11-02 15:42:06

utilisez un ordinateur 64 bits, avec un OS 64 bits et une implémentation Python 64 bits, ou évitez memmap()

memmap() nécessite un support matériel CPU de pour avoir du sens avec de gros fichiers plus gros que quelques GiB.

il utilise le CPU MMU et interrompt les sous-systèmes pour permettre d'exposer les données comme si elles étaient déjà chargées ram.

le matériel MMU est qui va générer un interrompre chaque fois qu'une adresse correspondant à des données qui ne sont pas dans la RAM physique est accédée, et L'OS va gérer l'interruption d'une manière qui a du sens à 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 s'inscrivent pas dans la RAM.

cela rend votre code d'accès simple à écrire. Cependant, pour utiliser memmap() de cette façon, tout ce qui est impliqué devra traiter des adresses 64 bits.

ou bien il peut être préférable d'éviter memmap() et faire votre propre gestion de mémoire.

2
répondu RGD2 2016-12-13 04:35:15

vous demandez à L'OS de cartographier le fichier entier dans une plage de mémoire. Il ne sera pas lu jusqu'à ce que vous déclenchiez des erreurs de page en lisant/écrivant, mais il doit toujours s'assurer que toute la gamme est disponible pour votre processus, et si cette gamme est trop grande, il y aura des difficultés.

1
répondu Macke 2009-11-02 17:40:28