Batchify longues opérations Linq?

J’ai posé une question et on m’a répondu ici à propos des problèmes de performances que j’avais avec une grande collection de données. (créé avec linq)

ok, laissons de côté.

Mais une des optimisations intéressantes (et géniales ) suggérées par Marc était de Batchify la requête Batchify .

 /*1*/ static IEnumerable Batchify(this IEnumerable source, int count) /*2*/ { /*3*/ var list = new List(count); /*4*/ foreach(var item in source) /*5*/ { /*6*/ list.Add(item); /*7*/ if(list.Count == count) /*8*/ { /*9*/ foreach (var x in list) yield return x; /*10*/ list.Clear(); /*11*/ } /*12*/ } /*13*/ foreach (var item in list) yield return item; /*14*/ } 

Ici, le but de Batchify est de ne pas trop aider le serveur en prenant beaucoup de temps entre chaque opération – les données sont inventées par lots de 1 000 et chaque lot est mis à disposition très rapidement.

Maintenant, je comprends ce que cela fait, mais je ne peux pas faire la différence, car je risque de ne pas comprendre comment cela fonctionne. ( Parfois, vous pensez savoir quelque chose … jusqu’à … )

OK, revenons à l’essentiel:

Autant que je sache, Linq fonctionne comme cette chaîne -:

entrez la description de l'image ici

Nous ne pouvons donc commencer à énumérer que le résultat de select in:

 Where-->OrderBy-->Select 

était accompli.

Donc en gros, j’attends que select ait toutes les données correctes ( après where , après orderby ), et seulement ensuite – mon code peut toucher ces valeurs. (cédé de la select )

Mais si je comprends bien la réponse de Marc, il semblerait qu’il existe un écart entre ces yields ce qui permet à d’autres ressources de faire quelque chose … (?)

Si tel est le cas, entre chaque itération de #4 , après la ligne #9 , le temps est-il écoulé pour que le processeur fasse autre chose?

Question

  • Quelqu’un peut-il jeter une lumière s’il vous plaît? Comment cela marche-t-il ?

nb

Je sais déjà que (par exemple) select n’est autre que:

 public static IEnumerable Select (this IEnumerable source, Func selector) { foreach (TSource element in source) yield return selector (elem![enter image description here][3]ent); } 

Mais si c’est le cas, mon code ne peut pas y toucher tant que toutes les valeurs (après where , orderby ) n’ont pas été calculées …

modifier :

Pour ceux qui demandent s’il y a une différence: http://i.stack.imgur.com/19Ojw.jpg

2 secondes pour 1 million d’ articles. 9 secondes pour 5 millions d’ articles.

(ignore la deuxième ligne de temps, (ligne console.write supplémentaire).) entrez la description de l'image ici

la voici pour la liste de 5m: http://i.stack.imgur.com/DflGR.jpg (la première est avecBatchify, l’autre non)

entrez la description de l'image ici

Important: l’image montrée comprend OrderBy : vous devez noter que cela casse batchify ici, car OrderBy est un opérateur en mémoire tampon. La méthode batchify que j’ai montrée est destinée aux stream de spooling non tamponnés.

Dans le contexte dans lequel je l’ai utilisé, l’origine (avant la batchify) était un bloc iterator qui faisait beaucoup de choses impliquant la création d’objects et des générateurs de nombres pseudo-aléatoires à chaque itération. Étant donné que le code en question était sensible au minutage, ce que je ne voulais pas, c’était introduire une pause fiable (pour le travail de la CPU consistant à créer chaque élément) entre chaque appel du magasin. C’était en partie pour émuler le code d’origine, qui créait tous les objects à l’avant, et en partie parce que je comprenais comment SE.Redis gère le travail de socket.

Considérons le comportement sans Batchify :

  • créer un élément (travail du processeur) et le céder
  • l’envoyer au magasin (réseau IO)
  • créer un élément (travail du processeur) et le céder
  • l’envoyer au magasin (réseau IO)
  • créer un élément (travail du processeur) et le céder
  • l’envoyer au magasin (réseau IO)

En particulier, cela signifie qu’il existe une pause prévisible entre les demandes de stockage. SE.Redis gère les E / S de socket sur un thread de travail dédié, ce qui peut entraîner assez facilement une fragmentation élevée des paquets, d’autant plus que j’utilisais le drapeau “fire and forget”. Le thread d’écrivain doit vider périodiquement, ce qu’il fait lorsque le tampon atteint une taille critique ou s’il n’y a plus de travail dans la queue des messages sortants.

Maintenant, considérons ce que fait batchify:

  • créer un élément (travail de la CPU) et le tamponner
  • créer un élément (travail de la CPU) et le tamponner
  • créer un élément (travail de la CPU) et le tamponner
  • donner un article
  • l’envoyer au magasin (réseau IO)
  • donner un article
  • l’envoyer au magasin (réseau IO)
  • donner un article
  • l’envoyer au magasin (réseau IO)
  • créer un élément (travail de la CPU) et le tamponner

espérons que vous constaterez ici que l’effort de la CPU entre les demandes de stockage est considérablement réduit. Cela imite plus correctement le code original dans lequel une liste de millions de personnes a été créée, puis itérée. Mais cela signifie également que le thread qui crée des messages sortants a de très bonnes chances d’aller au moins aussi vite que le thread auteur, ce qui signifie que la file d’attente sortante risque de ne pas devenir nulle pour une durée appréciable. Cela permet une fragmentation beaucoup plus faible des paquets, car au lieu d’avoir un paquet par demande, il y a de bonnes chances que plusieurs messages se trouvent dans chaque paquet. Moins de paquets signifie généralement une plus grande bande passante en raison de la réduction des frais généraux.

Je sais que cette solution a été publiée par un utilisateur probablement plus averti que moi, mais franchement, dans votre exemple, cela ne fait rien . Le vrai tueur de votre dernier message était le fait que vous utilisiez une List<> pour créer réellement 10 millions d’entrées en mémoire avant de commencer la boucle foreach sur cette collection matérialisée. Vous utilisez maintenant un IEnumerable<> qui ne crée pas 10M simultanément dans la mémoire, mais un après l’autre (peut-être plus si parallèle). La méthode Batchify est agréable … mais si vous la sautez, elle devrait fonctionner de la même manière. Le meilleur des cas, c’est une micro optimisation.