2017-09-02 2 views
0

J'ai trouvé quelque chose de bizarre: pour une raison quelconque, la version tableau de ceci contiendra presque toujours des 0 aléatoires après l'exécution du code suivant, ce qui n'est pas le cas de la version pointeur.Parallélisme rapide: Array vs UnsafeMutablePointer dans GCD

var a = UnsafeMutablePointer<Int>.allocate(capacity: N) 
//var a = [Int](repeating: 0, count: N) 

let n = N/iterations 

DispatchQueue.concurrentPerform(iterations: iterations) { j in 
    for i in max(j * n, 1)..<((j + 1) * n) { 
     a[i] = 1 
    } 
} 

for i in max(1, N - (N % n))..<N { 
    a[i] = 1 
} 

Y a-t-il une raison particulière à cela? Je sais que les tableaux Swift peuvent ne pas être consécutifs en mémoire, mais accéder à l'emplacement de la mémoire par rapport à chaque index une seule fois, à partir d'un seul thread ne devrait rien faire de trop drôle.

Répondre

0

Les tableaux ne sont pas thread-safe et, bien qu'ils soient reliés à des objets Objective-C, ils se comportent comme des types de valeur avec la logique COW (copy on Write). COW sur un tableau fera une copie du tableau entier quand un élément change et que le compteur de référence est supérieur à 1 (conceptuellement, l'implémentation réelle est un peu plus subtile).

Votre thread qui modifie le tableau déclenchera une copie de la mémoire à chaque fois que le thread principal arrivera à la référence et à l'élément. Le thread principal, effectue également des modifications afin qu'il provoque également COW. Ce que vous finissez avec est l'état de la dernière copie modifiée utilisée par l'un ou l'autre fil. Cela laissera aléatoirement certains des changements dans les limbes et explique les éléments "manqués". Pour éviter cela, vous devez effectuer toutes les modifications dans un thread spécifique et utiliser sync() pour vous assurer que COW sur la matrice est uniquement effectuée par ce thread (cela peut effectivement réduire le nombre de copies de mémoire et donner de meilleures performances pour les très grands réseaux). Il y aura des conflits généraux et potentiels en utilisant cette approche. C'est le prix à payer pour la sécurité des fils. L'autre façon d'aborder cela serait d'utiliser un tableau d'objets (types référencés). Cela rend votre tableau une liste simple de pointeurs qui ne sont pas réellement modifiés en modifiant les données dans les objets référencés. Bien que, dans les programmes actuels, vous deviez vous soucier de la sécurité des threads dans chaque instance d'objet, il y aurait beaucoup moins d'interférences (et de surcharge) que ce que vous obtenez avec les tableaux de types de valeurs.

+0

Un tableau de références d'objet est toujours une valeur, n'est-ce pas? – Raphael

+0

J'ai aussi pensé à cela. Mais alors les zéros ne devraient pas être "aléatoires". Eh bien, l'OP ne donne pas d'exemples ... peut-être qu'ils n'ont pas trouvé le modèle. – Raphael

+0

Oui, un tableau d'objets contient des "valeurs", mais ce sont des valeurs de type "référencé". Ils sont comme des pointeurs sur le contenu réel. La modification de la valeur "pointée" (la valeur référencée) ne nécessite pas la mise à jour du pointeur dans le tableau. Pour les types de valeur, COW effectue également une indirection interne mais le langage masque complètement cette manipulation de la mémoire. Cela peut prêter à confusion, mais vous obtenez une double indirection pour les types de valeur et une indirection triple pour les types référencés. Les pointeurs dangereux en comparaison sont une seule indirection. –