Test de charge: comment générer des requêtes par seconde?

14

J'ai un composant serveur qui tourne sur Zeroc-ICE. Quand j'ai voulu le charger, je pensais que l'utilisation d'une bibliothèque parallèle pour créer plusieurs requêtes le ferait. Mais ça ne finit pas comme ça. L'utilisation de la bibliothèque Parallel (Parallel.For) de C # était apparemment plus facile, mais il ne semble pas générer exactement tout en parallèle au même instant. Il ne peut donc pas s'agir de la définition de la création de N requêtes par seconde. Comment dois-je procéder? J'imagine que quiconque veut faire un test de charge en premier penserait à cela.

  1. Quelle est la manière efficace de créer réellement N requêtes en vraiment par seconde?

  2. Un autre mythe concerne la programmation parallèle. Veuillez nous éclairer, si vous avez utilisé des modèles de programmation parallèles en C # ou .Net en général. Imaginez que j'ai 5 processus. Comment démarrera les cinq processus en même temps. Qu'est-ce que cela signifie pour ma consommation de ressources? J'ai essayé de lire de nombreux documents disponibles sur le net, mais je reçois de plus en plus de questions qu'eux étant la réponse à mes questions.

  3. J'ai utilisé Parallel.For et créé N threads et mesuré le temps. Ensuite, j'ai essayé la même chose en utilisant Task.Factory.start pour l'énumération des tâches. Le temps mesuré était différent. Alors, quelle est exactement la différence entre les utiliser? Quand dois-je utiliser les classes correspondantes et à quelles fins exactement? nous avons souvent beaucoup de richesses mais c'est juste que nous ne savons pas exactement comment les différencier les uns des autres. C'est un tel cas pour moi, ne pouvant pas trouver pourquoi je ne devrais pas utiliser l'un de l'autre.

  4. J'ai utilisé la classe chronomètre pour mesurer ces temps qui prétendent être les meilleurs. Dans le cas d'un test de charge d'un composant, quelle serait la façon de mesurer le temps de réponse. Le chronomètre semble être la meilleure solution pour moi. Toutes les opinions sont les bienvenues.

ps: Il existe de nombreux outils de test de charge pour les applications Web. Le mien est un boîtier personnalisé de composants de serveur. Et ma question concerne davantage la création de N threads par seconde.

Toutes les opinions sont les bienvenues. Ne pensez pas que ce n'est pas vraiment une question de programmation. C'est bien sûr. Cela devrait sonner pour tout programmeur qui veut faire du QE par lui-même pour connaître les performances de son produit, de première main par lui-même.

Roi
la source
La FAQ dit que si elle se rapporte à un problème de programmation spécifique et si c'est un problème pratique et responsable dans la profession de programmation, elle peut être demandée. les gens qui sont sceptiques et signalent cela. commentez s'il vous plaît.
King
Que voulez-vous dire par "le même instant"? Je me demande si vous pouvez forcer TPL ou PLinq de quelque manière que ce soit pour y parvenir.
Gert Arnold
Ma question concerne la génération de N requêtes par seconde. Donc, le même instant dans ce scénario était destiné à ma compréhension de l'utilisation du parallèle pour démarrer les threads de manière parallèle.
King
Avez-vous fait une analyse séquentielle?
3
Cela peut concerner la programmation, mais il y a tout simplement trop de questions dans votre message (au moins 4). Je la réduirais à la seule question que vous voudriez poser avant qu'elle ne soit fermée parce qu'elle est trop large. Donnez des informations pertinentes, comme le 10000 que vous venez de mentionner, le nombre de cœurs dans votre machine de test). L'affichage du code est généralement utile.
Gert Arnold

Réponses:

10

Je n'ai pas toutes les réponses. Espérons que je peux apporter un peu de lumière sur elle.

Pour simplifier mes déclarations précédentes sur les modèles de threads de .NET, sachez simplement que la bibliothèque parallèle utilise des tâches et que le TaskScheduler par défaut pour les tâches utilise le ThreadPool. Plus vous montez dans la hiérarchie (ThreadPool est en bas), plus vous avez de frais généraux lors de la création des éléments. Ce surcoût supplémentaire ne signifie certainement pas qu'il est plus lent, mais il est bon de savoir qu'il est là. En fin de compte, les performances de votre algorithme dans un environnement multithread se résument à sa conception. Ce qui fonctionne bien séquentiellement peut ne pas fonctionner aussi bien en parallèle. Il y a trop de facteurs impliqués pour vous donner des règles dures et rapides, ils changent en fonction de ce que vous essayez de faire. Puisque vous traitez des demandes de réseau, je vais essayer de donner un petit exemple.

Permettez-moi de dire que je ne suis pas un expert des prises, et je ne sais pratiquement rien de Zeroc-Ice. Je connais un peu les opérations asynchrones, et c'est là que cela vous aidera vraiment. Si vous envoyez une demande synchrone via un socket, lorsque vous appelez Socket.Receive(), votre thread se bloquera jusqu'à ce qu'une demande soit reçue. Ce n'est pas bon. Votre thread ne peut plus faire de demande car il est bloqué. À l'aide de Socket.Beginxxxxxx (), la demande d'E / S sera effectuée et placée dans la file d'attente IRP pour le socket, et votre thread continuera. Cela signifie que votre thread pourrait réellement faire des milliers de demandes dans une boucle sans aucun blocage!

Si je vous comprends bien, vous utilisez des appels via Zeroc-Ice dans votre code de test, sans vraiment essayer d'atteindre un point de terminaison http. Si c'est le cas, je peux admettre que je ne sais pas comment fonctionne Zeroc-Ice. Je voudrais cependant vous suggérons de suivre les conseils énumérés ici , en particulier la partie: Consider Asynchronous Method Invocation (AMI). La page montre ceci:

En utilisant AMI, le client retrouve le thread de contrôle dès que l'invocation a été envoyée (ou, si elle ne peut pas être envoyée immédiatement, a été mise en file d'attente), ce qui permet au client d'utiliser ce thread pour effectuer d'autres travaux utiles dans l'intervalle .

Ce qui semble être l'équivalent de ce que j'ai décrit ci-dessus en utilisant des sockets .NET. Il peut y avoir d'autres façons d'améliorer les performances lorsque vous essayez de faire beaucoup d'envois, mais je commencerais ici ou avec toute autre suggestion répertoriée sur cette page. Vous avez été très vague sur la conception de votre application, je peux donc être plus précis que je ne l'ai été ci-dessus. N'oubliez pas, n'utilisez pas plus de threads que ce qui est absolument nécessaire pour faire ce dont vous avez besoin, sinon vous trouverez probablement votre application beaucoup plus lente que vous le souhaitez.

Quelques exemples en pseudocode (j'ai essayé de le rapprocher le plus possible de la glace sans que je doive réellement l'apprendre):

var iterations = 100000;
for (int i = 0; i < iterations; i++)
{
    // The thread blocks here waiting for the response.
    // That slows down your loop and you're just wasting
    // CPU cycles that could instead be sending/receiving more objects
    MyObjectPrx obj = iceComm.stringToProxy("whateverissupposedtogohere");
    obj.DoStuff();
}

Une meilleure façon:

public interface MyObjectPrx : Ice.ObjectPrx
{
    Ice.AsyncResult GetObject(int obj, Ice.AsyncCallback cb, object cookie);
    // other functions
}

public static void Finished(Ice.AsyncResult result)
{
    MyObjectPrx obj = (MyObjectPrx)result.GetProxy();
    obj.DoStuff();
}

static void Main(string[] args)
{
    // threaded code...
    var iterations = 100000;
    for (int i = 0; i < iterations; i++)
    {
        int num = //whatever
        MyObjectPrx prx = //whatever
        Ice.AsyncCallback cb = new Ice.AsyncCallback(Finished);
        // This function immediately gets called, and the loop continues
        // it doesn't wait for a response, it just continually sends out socket
        // requests as fast as your CPU can handle them.  The response from the
        // server will be handled in the callback function when the request
        // completes.  Hopefully you can see how this is much faster when 
        // sending sockets.  If your server does not use an Async model 
        // like this, however, it's quite possible that your server won't 
        // be able to handle the requests
        prx.GetObject(num, cb, null);
    }
}

Gardez à l'esprit que plus de threads! = De meilleures performances lorsque vous essayez d'envoyer des sockets (ou vraiment faire quoi que ce soit). Les threads ne sont pas magiques car ils résoudront automatiquement le problème sur lequel vous travaillez. Idéalement, vous voulez 1 thread par cœur, à moins qu'un thread ne passe la plupart de son temps à attendre, alors vous pouvez justifier d'en avoir plus. Exécuter chaque demande dans son propre thread est une mauvaise idée, car des changements de contexte se produiront et un gaspillage de ressources. (Si vous voulez voir tout ce que j'ai écrit à ce sujet, cliquez sur modifier et consultez les dernières révisions de ce post. Je l'ai supprimé car il ne semblait que brouiller le problème principal.)

Vous pouvez certainement faire ces requêtes dans des threads, si vous voulez faire un grand nombre de requêtes par seconde. Cependant, n'allez pas trop loin avec la création de threads. Trouvez un équilibre et respectez-le. Vous obtiendrez de meilleures performances si vous utilisez un modèle asynchrone par rapport à un modèle synchrone.

J'espère que ça aide.

Christopher Currens
la source
Pourquoi parlez-vous autant de performances? Cela ne semble pas être ce que souhaite le PO.
svick
1
@svick bien le post original de l'op avait 4 questions à l'origine, et ils ont posé des questions sur les performances des tâches parallèles vs, puis il a été édité, et maintenant ils sont de retour. Donc, une grande partie de ce que vous avez lu est le résultat de cela. En fin de compte, bien que sa question ait à voir avec la performance, car il a l'idée générale correcte, mais semble manquer dans sa mise en œuvre. Je crois que mes réponses pointues à la fin répondent à la question qu'il n'a pas modifiée.
Christopher Currens
1
J'ai été obligé de réduire mes questions parce qu'ils voulaient le voter pour fermer. Maintenant, il semble que c'est valable ici pour les avoir. @ChristopherCurrens +1 bon point pour la différence avec threadpool aux tâches. Cela a élargi ma compréhension. Mais je ne sais toujours pas comment générer des N requêtes par seconde est vraiment possible? Quelle est exactement la meilleure façon de procéder?
King
@King - Je suppose que je n'étais pas aussi clair que je le pensais. Les 3-4 derniers paragraphes que je pensais pourraient vous aider. J'avais supposé que vous utilisiez déjà une sorte de boucle. Si vous faisiez cela, le problème est que vos envois / réceptions de socket bloquent et ralentissent ainsi vos demandes. Je trouverai peut-être un peu de temps pour publier un exemple de pseudo-code.
Christopher Currens
Je n'ai aucun problème à les envoyer via ICE. Le problème est ce qui définit l'implémentation qui créerait réellement N requêtes et quelque chose qui peut être dit fidèle à ce nombre, N.
King
2

Je vais sauter la question 1) et passer directement au n ° 2, car c'est généralement un moyen acceptable d'accomplir ce que vous recherchez. Dans le passé, pour obtenir n messages par seconde, vous pouvez créer un seul processus qui lancera ensuite p AppDomains. Chaque AppDomain commence simplement à exécuter une boucle de demande une fois qu'un certain moment a été atteint (à l'aide d'un temporisateur). Cette heure doit être la même pour chaque AppDomain afin de s'assurer qu'ils commencent à frapper votre serveur au même moment.

Quelque chose comme ça devrait fonctionner pour envoyer vos demandes:

WaitCallback del = state => 
{ 
    ManualResetEvent[] resetEvents = new ManualResetEvent[10000]; 
    WebClient[] clients = new WebClient[10000]; 

    for (int index = 0; index < 10000; index++) 
    { 
        resetEvents[index] = new ManualResetEvent(false); 
        clients[index] = new WebClient(); 

        clients[index].OpenReadCompleted += new OpenReadCompletedEventHandler (client_OpenReadCompleted); 

        clients[index].OpenReadAsync(new Uri(@"<REQUESTURL>"), resetEvents[index]); 
    } 

    bool succeeded = ManualResetEvent.WaitAll(resetEvents, 10000); 
    Complete(succeeded); 

    for (int index = 0; index < 10000; index++) 
    { 
        resetEvents[index].Dispose(); 
        clients[index].Dispose(); 
    } 
}; 

while(running)
{
    ThreadPool.QueueUserWorkItem(del);
    Thread.Sleep(1000);
}

Cela réduira probablement les performances sur la machine sur laquelle vous l'exécutez, vous pouvez donc toujours implémenter un type de boucle simmilaire à partir de plusieurs machines différentes si vous avez les ressources (en utilisant des processus au lieu des domaines d'application).

Pour votre troisième question, donnez à ce lien une lecture http://www.albahari.com/threading/

Enfin, un chronomètre doit être associé à un compteur de hits pour suivre à la fois la durée et les hits uniques sur votre serveur. Cela devrait vous permettre d'effectuer une analyse après coup.

Chris
la source
2
Quelle raison possible auriez-vous à créer ici des AppDomains distincts? Cela semble complètement inutile.
svick
0

Ne vous embêtez pas avec les fils, si N est raisonnablement petit. Pour générer N requêtes par seconde, utilisez l'heure de l'horloge murale ( DateTime.Now). Prenez le temps avant et après la demande, puis ajoutez un Sleeppour retarder la prochaine demande.

Par exemple, avec N = 5 (200 ms):

Before request: 12:33:05.014
After request: 12:33:05.077
Sleep(137)
Before request: 12:33:05.214
After request: 12:33:05.271
Sleep(131)

Ce n'est pas parfait; vous constaterez peut-être que ce Sleepn'est pas exact. Vous pouvez conserver un décompte continu des écarts (avant les Xe demandes, l'heure devrait être X-1 / N plus tard) et ajuster la période de sommeil en conséquence.

Une fois que N devient trop grand, vous créez simplement M threads et laissez chaque thread générer N / M requêtes de la même manière.

MSalters
la source
Je dois générer un nombre très élevé de demandes. Donc, cela ne peut pas être l'option car cela va boire ma mémoire (4 Go de RAM) avant même 100 threads.
King
J'ai créé 20 000 requêtes par seconde à partir d'un seul thread, dans 250 Ko de code. De toute façon, vous n'avez pas suffisamment de CPU pour exécuter 100 threads (cette classe de machines n'est pas livrée avec 4 Go). Le prochain problème serait de rejeter toutes ces demandes; avez-vous un Ethernet 10 Gbit / s entre votre créateur de charge et votre serveur? Donc, vous voudrez peut-être vérifier vos besoins réels.
MSalters
Pour clarifier, j'ai quelque chose comme 20+ Gbps. Ce n'est donc pas un problème. À propos de la classe de machines, à quoi feriez-vous référence? nombre de processeurs?
King
@King: pour pousser 100 threads, je m'attendrais à une machine à 48 cœurs. SGI vend des machines avec autant de cœurs, par exemple, mais sur celles que vous obtenez généralement 32 Go ou plus.
MSalters
0

La façon la plus simple de réaliser des tests de charge pour tout projet .NET consiste à acheter l'édition Ultimate de Visual Studio. Cela vient avec des outils de test intégrés pour aider à effectuer toutes sortes de tests, y compris des tests de charge. Les tests de charge peuvent être préformés en créant des utilisateurs virtuels sur un seul PC ou répartis sur plusieurs pour un plus grand nombre d'utilisateurs, il existe également un petit programme qui peut être installé sur les serveurs cibles pour renvoyer des données supplémentaires pendant la durée du test.

C'est cher, mais l'édition ultime comprend de nombreuses fonctionnalités, donc si tous étaient utilisés, ce serait un prix plus raisonnable.

Ryathal
la source
0

Si vous souhaitez uniquement que tous les threads X atteignent votre ressource au même moment, vous pouvez placer chaque thread derrière un verrou de compte à rebours et spécifier une courte période d'attente entre les vérifications de sémaphore.

C # a une implémentation (http://msdn.microsoft.com/en-us/library/system.threading.countdownevent(VS.100).aspx).

Dans le même temps, si vous testez votre système sous contrainte, vous pouvez également vérifier les conditions de concurrence, auquel cas vous souhaiterez configurer des périodes de veille de thread sur chaque thread qui osculent au fil du temps avec une fréquence aléatoire et des pics / congés.

De même, vous ne souhaiterez peut-être pas simplement envoyer rapidement plusieurs demandes, vous pourriez mieux réussir à mettre votre serveur dans un mauvais état / à tester ses performances réelles en configurant un plus petit nombre de threads qui passent plus de temps à consommer et à renvoyer des messages et-vient sur le socket, car votre serveur devra probablement tourner ses propres threads pour gérer les messages lents en cours.

Keith apporte
la source