Question

Je construis une base de données de jouets en C # pour en savoir plus sur le compilateur, l'optimiseur et la technologie d'indexation.

Je souhaite conserver un parallélisme maximal entre les requêtes (au moins lues) pour amener des pages dans le pool de mémoire tampon, mais je ne comprends pas comment y parvenir au mieux.

Voici quelques options et les problèmes rencontrés avec chacune d’elles:

  1. Utilisez System.IO.FileStream et la méthode BeginRead

    Mais, la position dans le fichier n'est pas un argument de BeginRead , c'est une propriété du FileStream (défini via le Seek méthode), je ne peux donc émettre qu’une demande à la fois et verrouiller le flux pour la durée. (Ou alors? La documentation n’est pas claire sur ce qui se produirait si je gardais le verrou uniquement entre les appels Seek et BeginRead mais que je la libériais avant d’appeler EndRead . Est-ce que quelqu'un sait?) Je sais comment faire cela, je ne suis pas sûr que ce soit le meilleur moyen.

  2. Il semble exister une autre solution centrée sur la structure System.Threading.Overlapped et P \ Invoke sur la fonction ReadFileEx dans kernel32.dll.

    Malheureusement, les échantillons sont rares, en particulier dans les langues gérées. Cet itinéraire (s'il peut fonctionner du tout) implique apparemment également la méthode ThreadPool.BindHandle et les threads d'achèvement IO du pool de threads. J'ai l'impression que c'est la manière autorisée de traiter ce scénario sous Windows, mais je ne le comprends pas et je ne trouve pas de point d'entrée dans la documentation utile aux non-initiés.

  3. Autre chose?

  4. Dans un commentaire, Jacob suggère de créer un nouveau FileStream pour chaque lecture en vol.

  5. Lire le fichier entier en mémoire.

    Cela fonctionnerait si la base de données était petite. La base de code est petite et il existe de nombreuses autres inefficacités, mais la base de données elle-même ne l'est pas. Je veux aussi m'assurer que je fais toute la comptabilité nécessaire pour gérer une base de données volumineuse (ce qui s'avère être une partie énorme de la complexité: pagination, tri externe, ...) et je crains que cela ne soit trop facile à tricher accidentellement.

Modifier

Clarification de la raison pour laquelle je me méfie de la solution 1: garder un verrou unique de BeginRead à EndRead signifie que je dois bloquer toute personne souhaitant lancer une lecture simplement parce qu'une autre lecture est en cours. Cela semble anormal, car le fil à l'origine de la nouvelle lecture pourrait (en général) pouvoir effectuer davantage de travail avant que les résultats ne soient disponibles. (En fait, le simple fait d’écrire cela m’a amené à imaginer une nouvelle solution, c’est une nouvelle réponse.)

Était-ce utile?

La solution

Ce que nous avons fait est d'écrire une petite couche autour des ports d'achèvement d'E / S, de ReadFile et de GetQueuedCompletion en C ++ / CLI, puis de rappeler en C # une fois l'opération terminée. Nous avons choisi cette route plutôt que BeginRead et le modèle d'opération asynchrone c # pour mieux contrôler les tampons utilisés pour lire le fichier (ou le socket). C’est un assez gros gain de performance par rapport à l’approche purement gérée qui attribue un nouvel octet [] sur le tas à chaque lecture.

De plus, il existe des exemples C ++ beaucoup plus complets d'utilisation des ports IO Completion sur les interwebs

Autres conseils

Je ne suis pas sûr de comprendre pourquoi l'option 1 ne fonctionnerait pas pour vous. Gardez à l'esprit que vous ne pouvez pas avoir deux threads différents essayant d'utiliser le même FileStream en même temps - cela vous causera certainement des problèmes. BeginRead / EndRead est conçu pour permettre à votre code de continuer à s'exécuter pendant que l'opération d'E / S potentiellement coûteuse prend place, et non pour permettre une sorte d'accès multi-thread à un fichier.

Je suggérerais donc que vous cherchiez puis que vous commenciez une lecture.

Que se passe-t-il si vous avez d'abord chargé la ressource (données de fichier ou autre) en mémoire, puis l'avez partagée entre plusieurs threads? Comme c'est un petit db. - vous n'aurez pas autant de problèmes à traiter.

Utilisez l'approche n ° 1, mais

  1. Quand une demande arrive, prenez le verrou A. Utilisez-le pour protéger une file d'attente de demandes de lecture en attente. Ajoutez-le à la file d'attente et renvoyez un nouveau résultat asynchrone. Si cela entraîne le premier ajout à la file d'attente, appelez l'étape 2 avant de revenir. Libérez le verrou A avant de retourner.

  2. Lorsqu'une lecture est terminée (ou appelée par l'étape 1), verrouillez le verrou A. Utilisez-le pour vous protéger de la demande de lecture de la file d'attente. Prenez le verrou B. Utilisez-le pour protéger Chercher - > BeginRead - > Séquence EndRead . Libérez le verrou B. Mettez à jour le résultat asynchrone créé à l'étape 1 pour cette opération de lecture. (Une fois la lecture terminée, rappelez-le.)

Ceci résout le problème de ne pas bloquer les threads qui commencent une lecture simplement parce qu'une autre lecture est en cours, mais que les séquences sont toujours lues afin que la position actuelle du flux de fichiers ne soit pas gâchée.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top