2011-01-26 1 views
9

J'ai une application .NET écrite en C# (.NET 4.0). Dans cette application, nous devons lire un grand ensemble de données à partir d'un fichier et afficher le contenu dans une structure semblable à une grille. Donc, pour ce faire, j'ai placé un DataGridView sur le formulaire. Il a 3 colonnes, toutes les données de la colonne proviennent du fichier. Initialement, le fichier contenait environ 600 000 enregistrements, correspondant à 600 000 lignes dans DataGridView.Traitement de très grands ensembles de données et chargement juste à temps

J'ai rapidement découvert que DataGridView se replie avec un ensemble de données aussi volumineux, donc j'ai dû passer en mode virtuel. Pour ce faire, j'ai d'abord lu le fichier complètement dans 3 tableaux différents (correspondant à 3 colonnes), puis l'événement CellValueNeeded se déclenche, je fournis les valeurs correctes des tableaux.

Cependant, il peut y avoir un énorme (ÉNORME!) Nombre d'enregistrements dans ce fichier, comme nous l'avons rapidement découvert. Lorsque la taille de l'enregistrement est très grande, lire toutes les données dans un tableau ou dans une liste <>, etc., ne semble pas réalisable. Nous rencontrons rapidement des erreurs d'allocation de mémoire. (Exception de mémoire insuffisante)

Nous sommes restés coincés là, mais nous nous sommes alors rendu compte, pourquoi lire les données tout d'abord dans les tableaux, pourquoi ne pas lire le fichier à la demande lorsque l'événement CellValueNeeded se déclenche? Donc, c'est ce que nous faisons maintenant: Nous ouvrons le fichier, mais ne lisons rien, et comme les événements CellValueNeeded se déclenchent, nous cherchons d'abord Seek() à la position correcte dans le fichier, puis lisons les données correspondantes.

C'est le meilleur que nous puissions trouver, mais, tout d'abord, c'est assez lent, ce qui rend l'application léthargique et pas facile à utiliser. Deuxièmement, nous ne pouvons nous empêcher de penser qu'il doit y avoir un meilleur moyen d'y parvenir. Par exemple, certains éditeurs binaires (comme HXD) sont incroyablement rapides pour n'importe quelle taille de fichier, alors j'aimerais savoir comment cela peut être réalisé. Oh, et pour ajouter à nos problèmes, en mode virtuel de DataGridView, lorsque nous définissons RowCount sur le nombre de lignes disponibles dans le fichier (par exemple 16.000.000), il faut un certain temps pour que DataGridView s'initialiser. Des commentaires pour ce "problème" seraient également appréciés.

Merci

Répondre

5

Si vous ne pouvez pas adapter à votre ensemble de données en mémoire, alors vous avez besoin d'un système de mise en mémoire tampon. Plutôt que de lire uniquement la quantité de données nécessaires pour remplir le DataGridView en réponse à CellValueNeeded, votre application doit anticiper les actions de l'utilisateur et lire à l'avance. Ainsi, par exemple, lorsque le programme démarre, il devrait lire les 10 000 premiers enregistrements (ou peut-être seulement 1 000 ou peut-être 100 000 - tout ce qui est raisonnable dans votre cas). Ensuite, CellValueNeeded demandes peuvent être remplies immédiatement à partir de la mémoire. Au fur et à mesure que l'utilisateur se déplace dans la grille, votre programme reste le plus longtemps possible devant l'utilisateur. Il peut y avoir des pauses courtes si l'utilisateur saute devant vous (par exemple, veut sauter à la fin de l'avant) et vous devez aller sur le disque afin de répondre à une demande. Cette synchronisation est généralement mieux accomplie par un thread séparé, bien que la synchronisation peut parfois être un problème si le thread lit en anticipation de l'action suivante de l'utilisateur, puis l'utilisateur fait quelque chose de complètement inattendu comme sauter au début de la liste. 16 millions d'enregistrements ne sont pas vraiment autant d'enregistrements à conserver en mémoire, à moins que les enregistrements ne soient très volumineux. Ou si vous n'avez pas beaucoup de mémoire sur votre serveur. Certainement, 16 millions est loin de la taille maximale d'un List<T>, à moins que T soit un type de valeur (structure). De combien de gigaoctets de données parlez-vous ici?

+1

Bonjour Jim, T, est un struct avec 4 flotteurs double précision. Donc, 4 * 8 * 16M = 512 Mo de données. – SomethingBetter

+0

J'ai essayé d'utiliser .NET MemoryMappedFile, mais dès que vous créez une vue, il essaie apparemment de charger le fichier en mémoire, car j'ai des exceptions de mémoire insuffisante. Je pensais que MemoryMappedFile segmenterait en interne les accès aux données en pages et ne chargerait que les pages requises en mémoire. – SomethingBetter

+0

@SomethingBetter: Je suppose que 512 Mo est un problème si vous êtes sur une machine 32 bits. Si vous utilisez un fichier mappé en mémoire, vous souhaiterez afficher votre fichier dans un fichier plus petit que la taille totale du fichier. Ensuite, vous ajustez votre vue en tant que pages de l'utilisateur à travers les données. –

1

Gestion des lignes et des colonnes qui peuvent être enroulable, sous-totaux, utilisé dans les calculs multi-colonnes, etc présente un ensemble unique de défis; pas vraiment juste de comparer le problème à ceux qu'un éditeur rencontrerait. Les contrôles DataGrid de tierce partie ont abordé le problème de l'affichage et de la manipulation de grands ensembles de données côté client depuis les jours VB6. Ce n'est pas une tâche triviale d'obtenir des performances vraiment rapides en utilisant des jeux de données Garguantuan côté client à charge sur demande ou autonomes. Le chargement à la demande peut souffrir d'une latence côté serveur; manipuler l'ensemble de données sur le client peut souffrir de la mémoire et des limites du processeur. Certains contrôles tiers prenant en charge le chargement juste à temps fournissent à la fois la logique côté client et côté serveur, tandis que d'autres tentent de résoudre le problème 100% côté client.

3

Eh bien, voici une solution qui semble fonctionner beaucoup mieux:

Etape 0: Régler dataGridView.RowCount à une valeur faible, disons 25 (ou le nombre réel qui correspond à votre forme/écran)

Étape 1: Désactivez la barre de défilement du contrôle dataGridView.

Étape 2: Ajoutez votre propre barre de défilement.

Étape 3: Dans votre routine CellValueNeeded, répondre à e.RowIndex + scrollBar.Value

Étape 4: En ce qui concerne le datastore, j'ouvre actuellement un ruisseau, et dans la routine CellValueNeeded, tout d'abord faire un seek () et Lire() les données requises.

Avec ces étapes, je reçois très raisonnable des performances de défilement par la dataGrid pour des fichiers très volumineux (testé jusqu'à 0.8GB). Donc, en conclusion, il semble que la cause réelle du ralentissement n'était pas le fait que nous ayons gardé Seek() et Read(), mais le véritable dataGridView lui-même.

+0

C'est vrai. Afficher le même DataSet dans un TextBox (avec une aide de StringBuilder (5000000);))) est environ 4 fois plus rapide. – TomeeNS

0

Pour traiter cette question, je vous suggère de ne pas charger toutes les données à la fois. Au lieu de charger les données dans les morceaux et afficher les données les plus pertinentes en cas de besoin. Je viens de faire un test rapide et a constaté que la mise en une propriété DataSource d'un DataGridView est une bonne approche, mais avec un grand nombre de lignes, il faut aussi du temps. Donc utilisez la fonction Merge de DataTable pour charger les données en morceaux et montrer à l'utilisateur les données les plus pertinentes. Here J'ai démontré un exemple qui peut vous aider.

Questions connexes