web-dev-qa-db-fra.com

Erreur de segmentation lors de la création d'un tableau multitraitement

J'essaie de remplir un tableau numpy en utilisant le multitraitement, après this post . Ce que j’utilise fonctionne bien sur mon Mac, mais lorsque je le porte sous Ubuntu, je rencontre souvent des erreurs de segmentation.

J'ai réduit le code à l'exemple minimal suivant:

import numpy as np
from multiprocessing import sharedctypes

a = np.ctypeslib.as_ctypes(np.zeros((224,224,3)))
print("Made a, now making b")
b = sharedctypes.RawArray(a._type_, a)
print("Finished.")

Sur Ubuntu 16.04, avec Python 3.6.5 et numpy 1.15.4 (mêmes versions que sur mon Mac), je reçois la sortie

Made a, now making b
Segmentation fault (core dumped)

Maintenant, je peux modifier quelque peu les dimensions du tableau et dans certains cas, cela fonctionnera (par exemple, modifiez le premier 224 en 100 et cela fonctionne). Mais surtout, il y a des défauts. 

Quelqu'un peut-il donner une idée?

Je vois un message sur un sujet connexe de 2016 auquel personne n'a répondu } et un autre pointant des pointeurs que je n'utilise pas. 

PS- Cela ne semble pas faire de différence que je spécifie a en tant que tableau multidimensionnel ou en tant que tableau aplati (par exemple, np.zeros(224*224*3)). Cela ne semble pas non plus faire de différence si je change de type de données (par exemple, float to int); ça échoue le même.

Une autre mise à jour: Même le réglage "size = 224" dans le code du original post provoque des erreurs de segmentation sur deux machines Ubuntu différentes avec des versions différentes de numpy, mais fonctionne correctement sur Mac. 

8
sh37211

C’est plus une supposition qu'une réponse, mais vous pouvez être confronté à un problème dû à la récupération de place du tampon de données sous-jacent. Cela peut expliquer pourquoi il semble y avoir une dépendance à la taille globale du tableau que vous essayez de créer.

Si c'est le cas, la solution consiste à affecter le tableau de zéros Numpy que vous créez à sa propre variable. Cela garantirait que le tampon "vit" à travers la création de la variable RawArray. Le code serait alors:

zs = np.zeros((224,224,3))
a = np.ctypeslib.as_ctypes(zs)
print("Made a, now making b")
b = sharedctypes.RawArray(a._type_, a)
print("Finished.")

Je n'ai qu'un mac en ce moment, donc je ne peux pas le tester moi-même.

4
tel

Analyse supplémentaire et correction de la cause première.

Comme indiqué ci-dessus, ceci est le résultat d'un bogue de récupération de place, cela m'a donné un indice sur la façon de le résoudre.

En gardant la référence à l'objet np.zeros d'origine, le bogue a été évité. Cela signifiait (pour moi) que la collection de l'objet original corrompait le tableau résultant.

Regarder l'implémentation de as_ctypes (tiré de c52543e4a )

def as_ctypes(obj):
    """Create and return a ctypes object from a numpy array.  Actually
    anything that exposes the __array_interface__ is accepted."""
    ai = obj.__array_interface__
    if ai["strides"]:
        raise TypeError("strided arrays not supported")
    if ai["version"] != 3:
        raise TypeError("only __array_interface__ version 3 supported")
    addr, readonly = ai["data"]
    if readonly:
        raise TypeError("readonly arrays unsupported")
    tp = _ctype_ndarray(_typecodes[ai["typestr"]], ai["shape"])
    result = tp.from_address(addr)
    result.__keep = ai
    return result

il est évident que l'auteur d'origine a pensé à cela (affectation de .__keep pour conserver une référence à l'objet d'origine). Cependant, il semble qu'ils doivent conserver une référence à l'objet original.

J'ai écrit un patch qui fait ceci:

-        result.__keep = ai
+        result.__keep = obj
4
Anthony Sottile

Note finale

Laissant mes tests pour la postérité, mais tel a la réponse. 

Remarque

Les résultats du test ci-dessous sont sur Debian. Les tests sur Ubuntu (WSL) sont en effet bien pires. Sur Ubuntu, n=193 pour toute forme de plantage (aussi si je remplace la variable 3D n par 1), et toute variable n ci-dessus. Ressemble à (voir bla.py ci-dessous):

  1. py bla.py n 1 alloue 3204 sur A, 29323 ob B pour al 0<n<193
  2. Pour n>=193, une erreur de segmentation se produit sur B et 3208 sont alloués sur A. Apparemment, il existe une limite de mémoire fixe quelque part dans ubuntu.

Les anciens tests sur Debian

Après quelques tests, cela ressemble à un problème de mémoire, avec une étrange mise à l'échelle des allocations de mémoire avec dimension.

Le montage avec seulement 2 dimensions ne plante pas pour moi, mais 3 le font - je vais répondre en supposant cela.

Pour moi:

b = sharedctypes.RawArray(a._type_, a)

ne plantera pas si:

a = np.ctypeslib.as_ctypes(np.zeros((224**3))) #Though generating b takes a while
a = np.ctypeslib.as_ctypes(np.zeros((100,100,100)))

Il semble donc que moins de demande en mémoire supprime le problème, mais curieusement, le même nombre de cellules nécessaires dans un tableau unidimensionnel fonctionne bien - il semble donc qu'il reste quelque chose de plus profond dans la mémoire.

Bien sûr, vous utilisez des pointeurs. Essayons quelques choses (bla.py):

import tracemalloc
import numpy as np
from sys import argv
from multiprocessing import sharedctypes

n,shape = (int (x) for x in argv[1:])
if shape == 1: shape = n
if shape == 2: shape = (n**2,n)
if shape == 3: shape = (n,n,n)

tracemalloc.start()
a = np.ctypeslib.as_ctypes(np.zeros(shape))
x=tracemalloc.take_snapshot().statistics('lineno')
print(len(x),sum((a.size for a in x)))
b = sharedctypes.RawArray(a._type_, a)
x=tracemalloc.take_snapshot().statistics('lineno')
print(len(x),sum((a.size for a in x)))

Résultant en:

           n   shape    (a mallocs sum) (b mallocs sum)
>py bla.py 100 1     => 5 3478 76 30147
>py bla.py 100 2     => 5 5916 76 948313
>py bla.py 100 3     => 5 8200 76 43033
>py bla.py 150 1     => 5 3478 76 30195
>py bla.py 150 2     => 5 5916 76 2790461
>py bla.py 150 3     => 5 8200 76 45583
>py bla.py 159 1     => 5 3478 76 30195
>py bla.py 159 2     => 5 5916 76 2937854
>py bla.py 159 3     => 5 8200 76 46042
>py bla.py 160 1     => 5 3478 76 30195
>py bla.py 160 2     => 5 5916 72 2953989
>py bla.py 160 3     => 5 8200 Segmentation fault
>py bla.py 161 1     => 5 3478 76 30195
>py bla.py 161 2     => 5 5916 75 2971746
>py bla.py 161 3     => 5 8200 75 46116

>py bla.py 221 1     => 5 3478 76 30195
>py bla.py 221 2     => 5 5916 76 5759398
>py bla.py 221 3     => 5 8200 76 55348
>py bla.py 222 1     => 5 3478 76 30195
>py bla.py 222 2     => 5 5916 76 5782877
>py bla.py 222 3     => 5 8200 76 55399
>py bla.py 223 1     => 5 3478 76 30195
>py bla.py 223 2     => 5 5916 76 5806462
>py bla.py 223 3     => 5 8200 76 55450
>py bla.py 224 1     => 5 3478 76 30195
>py bla.py 224 2     => 5 5916 72 5829381
>py bla.py 224 3     => 5 8200 Segmentation fault
>py bla.py 225 1     => 5 3478 76 30195
>py bla.py 225 2     => 5 5916 76 5853950
>py bla.py 225 3     => 5 8200 76 55552

Weird stuff (n**2,n) a une quantité énorme de mémoire allouée pour cela dans le type partagé, mais pas n**3 ou (n,n,n). Mais c'est en plus le but.

  1. a Les mallocs sont cohérents et ne dépendent que légèrement de la dimension et pas du tout de n (pour les nombres testés).
  2. b mallocs en plus d'être haut sur la forme 2, augmenter avec n légèrement aussi, mais avec la forme ils varient énormément.
  3. Les erreurs de segmentation se produisent par cycles! L'allocation de mémoire pour la forme (n,n,n) sur ma machine se rapproche d'un numéro dépendant n avant le sefault - mais pour n+1, tout va bien à nouveau. Semble être ~ 46k environ 160 et ~ 56k environ 224.

Aucune bonne explication de ma part, mais la dépendance à n me fait penser que les allocations doivent s’inscrire parfaitement dans une structure de bits, et parfois cela se casse.

Je suppose que l'utilisation de 225 pour vos dimensions fonctionnera comme solution de contournement.

2
kabanus