Prueba de carga: ¿cómo generar solicitudes por segundo?

14

Tengo un componente de servidor que se ejecuta sobre Zeroc-ICE. Cuando quería cargar la prueba, pensé que usar la biblioteca paralela para crear múltiples solicitudes lo haría. Pero no terminará así. Usar la biblioteca Parallel (Parallel.For) de C # aparentemente fue más fácil, pero no parece estar generando exactamente todo en paralelo en el mismo instante. Por lo tanto, no puede ser la definición para crear N solicitudes por segundo. Cómo debería hacerlo ? Supongo que cualquiera que quiera hacer pruebas de carga primero pensaría en esto.

  1. ¿Cuál es la forma eficiente de crear realmente N solicitudes realmente por segundo?

  2. Otro mito es sobre la programación paralela. Ilumínenos, si ha utilizado patrones de programación paralelos en C # o .Net en general. Imagina que tengo 5 procesos. Cómo comenzará los cinco procesos al mismo tiempo. ¿Qué significa para mi consumo de recursos? He intentado leer muchos de los materiales disponibles en la red, pero recibo más y más preguntas que la respuesta a mis preguntas.

  3. Usé Parallel.For y creé N hilos y medí el tiempo. Luego intenté lo mismo usando Task.Factory.start para la enumeración de tareas. El tiempo medido fue diferente. Entonces, ¿cuál es exactamente la diferencia entre usar estos? ¿Cuándo debo usar las clases correspondientes y para qué fines exactamente? a menudo tenemos muchas riquezas, pero es solo que no sabemos exactamente cómo diferenciar una de la otra. Este es uno de esos casos para mí, no poder encontrar por qué no debería usar uno del otro.

  4. Usé la clase de cronómetro para medir estos tiempos que dicen ser los mejores. En el escenario de prueba de carga de un componente, cuál sería la forma de medir el tiempo de respuesta. El cronómetro parece ser la mejor solución para mí. Cualquier opinión es bienvenida.

ps: hay muchas herramientas de prueba de carga para aplicaciones web. El mío es un caso personalizado de componentes del servidor. Y mi pregunta está más relacionada con la creación de N subprocesos por segundo.

Todas las opiniones son bienvenidas. Simplemente no pienses que no es tanto una cuestión de programación. Por supuesto que lo es. Debería sonar la campana para cualquier programador que quiera hacer QE por sí mismo para conocer el rendimiento de su producto, de primera mano por sí mismo. He probado muchas opciones y luego tuve que recurrir a cómo debería hacerlo.

Rey
fuente
El faq dice si se trata de un problema de programación específico y si se trata de un problema práctico responsable en la profesión de programación, se puede preguntar. personas que son escépticas y señalan esto. por favor comenta.
Rey
¿Qué quieres decir con "el mismo instante"? Me pregunto si puedes forzar TPL o PLinq de alguna manera para lograrlo.
Gert Arnold
Mi pregunta es sobre generar N solicitudes por segundo. Entonces, el mismo instante en este escenario estaba destinado a mi comprensión de que usar el paralelo comenzaría hilos de manera paralela.
Rey el
¿Has hecho algún análisis secuencial?
3
Puede pertenecer a la programación, pero hay demasiadas preguntas en su publicación (al menos 4). Lo reduciría a la única pregunta que desea hacer antes de que se cierre porque es demasiado amplio. Proporcione información relevante, como los 10000 que acaba de mencionar, el número de núcleos en su máquina de prueba). Mostrar código generalmente ayuda.
Gert Arnold

Respuestas:

10

No tengo todas las respuestas. Espero poder arrojar algo de luz sobre ello.

Para simplificar mis declaraciones anteriores sobre los modelos de subprocesos de .NET, solo sepa que Parallel Library usa Tasks, y el TaskScheduler predeterminado para Tasks, usa ThreadPool. Cuanto más alto vayas en la jerarquía (ThreadPool está en la parte inferior), más sobrecarga tendrás al crear los elementos. Esa sobrecarga adicional ciertamente no significa que sea más lenta, pero es bueno saber que está ahí. En última instancia, el rendimiento de su algoritmo en un entorno de subprocesos múltiples se reduce a su diseño. Lo que funciona bien secuencialmente puede no funcionar tan bien en paralelo. Hay demasiados factores involucrados para darle reglas duras y rápidas, cambian dependiendo de lo que está tratando de hacer. Como se trata de solicitudes de red, intentaré dar un pequeño ejemplo.

Déjame decirte que no soy un experto en enchufes, y no sé casi nada sobre Zeroc-Ice. Sé un poco acerca de las operaciones asincrónicas, y aquí es donde realmente te ayudará. Si envía una solicitud síncrona a través de un socket, cuando llameSocket.Receive() , su subproceso se bloqueará hasta que se reciba una solicitud. Esto no es bueno Su hilo no puede hacer más solicitudes ya que está bloqueado. Usando Socket.Beginxxxxxx (), la solicitud de E / S se realizará y se colocará en la cola IRP para el socket, y su hilo continuará. ¡Esto significa que su hilo podría hacer miles de solicitudes en un bucle sin ningún bloqueo!

Si lo entiendo correctamente, está utilizando llamadas a través de Zeroc-Ice en su código de prueba, en realidad no está tratando de llegar a un punto final http. Si ese es el caso, puedo admitir que no sé cómo funciona Zeroc-Ice. Quisiera, sin embargo, sugieren siguiendo el consejo que aparece aquí , sobre todo la parte: Consider Asynchronous Method Invocation (AMI). La página muestra esto:

Al usar AMI, el cliente recupera el hilo de control tan pronto como se ha enviado la invocación (o, si no se puede enviar de inmediato, se ha puesto en cola), permitiendo que el cliente use ese hilo para realizar otro trabajo útil mientras tanto .

Que parece ser el equivalente de lo que describí anteriormente usando sockets .NET. Puede haber otras formas de mejorar el rendimiento al intentar hacer muchos envíos, pero comenzaría aquí o con cualquier otra sugerencia que aparezca en esa página. Ha sido muy vago sobre el diseño de su aplicación, por lo que puedo ser más específico de lo que he sido anteriormente. Solo recuerde, no use más subprocesos de los absolutamente necesarios para obtener lo que necesita, de lo contrario, es probable que su aplicación se ejecute mucho más lentamente de lo que desea.

Algunos ejemplos en pseudocódigo (traté de hacerlo lo más cerca posible del hielo sin que realmente tuviera que aprenderlo):

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();
}

Una mejor manera:

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);
    }
}

Tenga en cuenta que más subprocesos! = Mejor rendimiento al intentar enviar sockets (o realmente hacer algo). Los hilos no son mágicos ya que resolverán automáticamente cualquier problema en el que estés trabajando. Idealmente, desea 1 hilo por núcleo, a menos que un hilo pase mucho tiempo esperando, entonces puede justificar tener más. Ejecutar cada solicitud en su propio hilo es una mala idea, ya que ocurrirán cambios de contexto y desperdicio de recursos. (Si quieres ver todo lo que escribí sobre eso, haz clic en editar y mira las revisiones anteriores de esta publicación. Lo eliminé ya que solo parecía nublar el problema principal en cuestión).

Definitivamente puede realizar estas solicitudes en subprocesos, si desea realizar una gran cantidad de solicitudes por segundo. Sin embargo, no te excedas con la creación del hilo. Encuentre un equilibrio y manténgalo. Obtendrá un mejor rendimiento si utiliza un modelo asincrónico frente a uno sincrónico.

Espero que eso ayude.

Christopher Currens
fuente
¿Por qué estás hablando tanto de rendimiento? Eso no parece ser lo que quiere el OP.
svick
@svick bueno, la publicación original de operaciones tenía 4 preguntas originalmente, e hicieron preguntas sobre el desempeño de las tareas paralelas vs, luego se editó y ahora vuelven a entrar. Entonces, gran parte de lo que leyó fue el resultado de eso. En última instancia, aunque su pregunta tiene que ver con el rendimiento, ya que tiene la idea general correcta, pero aparentemente carece de su implementación. Creo que mis respuestas puntiagudas al final responden la pregunta que no editó.
Christopher Currens
Me vi obligado a reducir mis preguntas porque querían votar para cerrar. Ahora parece, es válido aquí para tenerlos. @ChristopherCurrens +1 buen punto para la diferencia con threadpool para tareas. Eso amplió mi comprensión. Pero todavía estoy atascado, ¿cómo es posible generar algunas solicitudes de N por segundo? ¿Cuál es exactamente la mejor manera de hacer eso?
Rey
@ King - Creo que no estaba tan claro como pensaba que era. Los últimos 3-4 párrafos pensé que te ayudarían. Asumí que ya estabas usando una especie de bucle. Si estaba haciendo eso, el problema es que sus envíos / recibos de socket están bloqueando y, por lo tanto, ralentizando sus solicitudes. Tal vez encuentre algo de tiempo para publicar un pseudocódigo de ejemplo.
Christopher Currens
No tengo ningún problema en enviarlos por ICE. El problema es lo que define la implementación que realmente crearía N solicitudes y algo que se puede decir verdadero a ese número, N.
Rey
2

Voy a saltar sobre la pregunta 1) y pasar directamente al n. ° 2, ya que generalmente es una forma aceptable de lograr lo que estás buscando. En el pasado, para lograr n mensajes por segundo, puede crear un solo proceso que luego lanzará p AppDomains. Básicamente, cada AppDomain comienza a ejecutar un ciclo de solicitud una vez que se alcanza un cierto punto en el tiempo (usando un Temporizador). Este tiempo debe ser el mismo para cada AppDomain para garantizar que comiencen a llegar a su servidor en el mismo momento.

Algo como esto debería funcionar para enviar sus solicitudes:

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);
}

Esto probablemente reducirá el rendimiento en cualquier máquina en la que lo esté ejecutando, por lo que siempre podría implementar un tipo de bucle similar desde varias máquinas diferentes si tiene los recursos (usando procesos en lugar de dominios de aplicación).

Para su tercera pregunta, lea este enlace http://www.albahari.com/threading/

Finalmente, un cronómetro debe combinarse con un contador de visitas para rastrear tanto la duración como las visitas únicas en su servidor. Eso debería permitirle realizar un análisis después del hecho.

Chris
fuente
2
¿Qué posible razón tendrías para crear AppDomains separados aquí? Eso parece completamente innecesario.
svick
0

No se moleste con hilos, si N es razonablemente pequeño. Para generar N solicitudes por segundo, use el tiempo del reloj de pared ( DateTime.Now). Tómese el tiempo antes y después de la solicitud, luego agregue unSleep para retrasar la próxima solicitud.

Por ejemplo, con 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)

Esto no es perfecto; Es posible que Sleepno sea exacto. Puede mantener un recuento de desviaciones (antes de las X solicitudes, el tiempo debe ser X-1 / N más tarde) y ajustar el período de suspensión en consecuencia.

Una vez que N se vuelve demasiado grande, simplemente crea M hilos y deja que cada hilo genere solicitudes N / M de la misma manera.

MSalters
fuente
Tengo que generar un número muy alto de solicitudes. Entonces, esta no puede ser la opción porque beberá mi memoria (4 GB de RAM) incluso antes de 100 hilos.
Rey
He creado 20,000 solicitudes por segundo a partir de un solo hilo, en 250K de código. De todos modos, no tiene suficientes CPU para ejecutar 100 subprocesos (esa clase de máquinas no viene con 4 GB). El siguiente problema sería rechazar todas esas solicitudes; ¿tiene 10 Gbit / s Ethernet entre su creador de carga y su servidor? Por lo tanto, es posible que desee verificar sus requisitos reales.
MSalters
Para aclarar, tengo algo así como más de 20 Gbps. Entonces eso no es un problema. Sobre la clase de máquinas, ¿a qué se referiría? cantidad de procesadores?
Rey
@ King: para empujar 100 hilos esperaría una máquina de 48 núcleos. SGI vende máquinas con tantos núcleos, por ejemplo, pero en las que normalmente obtendrías 32 GB o más.
MSalters
0

La forma más fácil de lograr pruebas de carga para cualquier proyecto .NET es comprar la edición Ultimate de Visual Studio. Esto viene con herramientas de prueba integradas para ayudar a realizar todo tipo de pruebas, incluidas las pruebas de carga. Las pruebas de carga se pueden realizar mediante la creación de usuarios virtuales, ya sea en una sola PC o distribuidos en varios para un mayor número de usuarios, también hay un pequeño programa que se puede instalar en los servidores de destino para devolver datos adicionales durante la prueba.

Sin embargo, esto es costoso, pero la última edición viene con muchas características, por lo que si se usaran todas, sería un precio más razonable.

Ryathal
fuente
0

Si desea que todos los hilos X lleguen a su recurso exactamente al mismo tiempo, puede colocar cada hilo detrás de un cierre de cuenta atrás y especificar un breve período de espera entre las comprobaciones del semáforo.

C # tiene una implementación (http://msdn.microsoft.com/en-us/library/system.threading.countdownevent(VS.100).aspx).

Al mismo tiempo, si está haciendo una prueba de esfuerzo de su sistema, es posible que también desee verificar las condiciones de carrera, en cuyo caso desearía configurar períodos de suspensión de hilo en cada hilo que oscilan con el tiempo con frecuencia aleatoria y picos / licencias.

De manera similar, es posible que no desee enviar rápidamente múltiples solicitudes, puede tener más éxito al poner su servidor en un mal estado / probar su rendimiento en el mundo real al configurar un número menor de hilos que pasan más tiempo consumiendo y enviando mensajes de regreso y adelante sobre el zócalo, ya que su servidor probablemente necesitará girar sus propios hilos para manejar mensajes lentos en curso.

Keith trae
fuente