2017-01-21 2 views
2

Lequel des deux morceaux de code suivants fonctionnera le mieux dans différents cas et pourquoi?GetOrAdd new vs performance d'usine

.

private readonly ConcurrentDictionary<int, List<T>> _coll; 
_coll.GetOrAdd(1, new List<T>()); 

Cela crée une nouvelle List à chaque appel, même quand il est pas nécessaire (Combien cette déclaration d'importance encore si nous passons la capacity comme 0?).

.

private readonly ConcurrentDictionary<int, List<T>> _coll; 
_coll.GetOrAdd(1, (val) => new List<T>()); 

Cela crée seulement le List à la demande, mais a un appel de délégué.

Répondre

4

En termes de mémoire, la première façon va provoquer une allocation à chaque fois, alors que le second utilisera un objet délégué en cache, car il ne tient pas compte des variables. Le compilateur gère la génération du délégué mis en cache. Il n'y a pas de différence dans le premier cas pour la capacité mise à zéro puisque le constructeur par défaut pour List<T> utilise un tableau vide à l'initialisation, le même qu'une capacité explicite de 0.

En termes d'instructions d'exécution, elles sont les mêmes quand la clé est trouvée puisque le second argument n'est pas utilisé. Si la clé n'est pas trouvée, la première doit simplement lire une variable locale tandis que la seconde aura une couche d'indirection pour appeler un délégué. En outre, looking into the source code, il semble que GetOrAdd avec l'usine fera une recherche supplémentaire (via TryGetValue) pour éviter d'invoquer l'usine. Le délégué pourrait également potentiellement être exécuté plusieurs fois. GetOrAdd garantit simplement que vous voyez une entrée dans le dictionnaire, pas que l'usine est appelée une seule fois.

En résumé, la première façon peut-être plus si la clé performante est généralement introuvable depuis l'attribution doit se passer de toute façon et il n'y a pas indirection par un délégué. Cependant, si la clé est généralement trouvée, la seconde est plus performante car il y a moins d'allocations. Pour une mise en œuvre dans un cache, vous vous attendez généralement à ce qu'il y ait beaucoup de hits, donc si c'est le cas, je vous recommande la deuxième méthode. En pratique, la différence entre les deux dépend de la sensibilité de l'application globale aux allocations dans ce chemin de code.

aussi quelle que soit l'application qui utilise cette aura probablement besoin de mettre en œuvre le verrouillage autour du List<T> qui est retourné car il est pas thread-safe.

+0

J'ai utilisé une liste '' titre d'exemple, la collecte effective à sa place est thread-safe. Aussi, en prenant la suggestion de Joseph, est-ce que '() => Enumerable.Empty ()' aide à la performance? – Hele

+1

En outre, cela semble dépendre de la version du framework utilisée (et de l'implémentation d'un nouveau comportement), comme dans le cas de .NET 4.5 où les modifications ont eu des effets secondaires indésirables. Reportez-vous à https://basildoncoder.com/blog/concurrentdictionary-getoradd-vs.html – Alex

+0

@Hele Eh bien, si vous allez utiliser 'Enumerable.Empty ()' alors un délégué n'est pas nécessaire.'Enumerable.Empty ()' lit juste un champ readonly statique. Cependant, je ne sais pas pourquoi vous utiliseriez une interface en lecture seule comme 'IEnumerable '. Je pense que cela rendrait les mises à jour plus difficiles, mais je devrais voir plus de code. –

0

Je ne peux pas imaginer que vous verriez bien d'une différence de performance, à moins que vous travaillez avec un ensemble de données extrêmement grande. Cela dépend aussi de la probabilité que chacun de vos objets soit touché. Les génériques sont extrêmement bien optimisés au niveau de l'exécution, et l'utilisation d'un délégué entraîne une allocation dans les deux cas.

Ma suggestion serait d'utiliser Enumerable.Empty<T>() comme vous allez vous sauver une allocation sur chaque élément du tableau.