Teste de carga: como gerar solicitações por segundo?

14

Eu tenho um componente de servidor que roda sobre o Zeroc-ICE. Quando eu queria carregar testá-lo, pensei que o uso da biblioteca paralela para criar várias solicitações o faria. Mas isso acaba assim. Usar a biblioteca Parallel (Parallel.For) a partir do C # aparentemente foi mais fácil, mas não parece estar gerando exatamente tudo paralelo no mesmo instante. Portanto, não pode ser a definição para criar N pedidos por segundo. Como devo fazer isso? Eu acho que quem quiser fazer o teste de carga primeiro pensaria sobre isso.

  1. Qual é a maneira eficiente de realmente criar solicitações de N em realmente por segundo?

  2. Outro mito é sobre a programação paralela. Por favor, esclareça-nos, se você usou padrões de programação paralela em C # ou .Net em geral. Imagine que eu tenho 5 processos. Como iniciará todos os cinco processos ao mesmo tempo. O que isso significa para o meu consumo de recursos? Tentei ler muitos dos materiais disponíveis na rede, mas recebo mais e mais perguntas do que elas sendo a resposta para minhas perguntas.

  3. Eu usei Parallel.For e criei N threads e medi o tempo. Então tentei a mesma coisa usando Task.Factory.start para enumeração de tarefas. O tempo medido foi diferente. Então, qual é exatamente a diferença entre usá-los? Quando devo usar as classes correspondentes e com que propósitos exatamente? geralmente temos muitas riquezas, mas não sabemos exatamente como diferenciar uma da outra. Esse é um caso para mim, não sendo possível descobrir por que não devo usar um do outro.

  4. Eu usei a classe de cronômetro para medir esses tempos, que afirma ser o melhor. No cenário em que eu carrego testando um componente, qual seria a maneira de medir o tempo de resposta. O cronômetro parece ser a melhor solução para mim. Quaisquer opiniões são bem-vindas.

ps: Existem muitas ferramentas de teste de carga para aplicativos da web. O meu é um caso personalizado de componentes de servidor. E minha pergunta é mais relacionada à criação de N threads por segundo.

Todas as opiniões são bem-vindas. Só não pense que não é uma questão de programação. Claro que sim. Qualquer programador que queira fazer o controle de qualidade por conta própria deve saber o desempenho de seu produto, em primeira mão. Tentei várias opções e tive que recorrer a como devo fazê-lo?

Rei
fonte
O FAQ diz que se refere a um problema específico de programação e se é um problema prático prático na profissão de programador, pode ser perguntado. pessoas céticas e sinalizando isso. por favor comente.
Rei
O que você quer dizer com "no mesmo instante"? Gostaria de saber se você pode forçar o TPL ou o PLinq de alguma forma para conseguir isso.
Gert Arnold
Minha pergunta é sobre a geração de N solicitações por segundo. Portanto, o mesmo instante nesse cenário foi direcionado ao meu entendimento do uso do paralelo que iniciaria os threads de maneira paralela.
Rei
Você já fez alguma análise sequencial?
3
Pode pertencer à programação, mas há muitas perguntas em sua postagem (pelo menos 4). Eu o reduziria à única pergunta que você deseja fazer antes que ela seja fechada porque é muito ampla. Forneça informações relevantes, como o número de núcleos 10000 que você acabou de mencionar na sua máquina de teste). Mostrar código geralmente ajuda.
Gert Arnold

Respostas:

10

Eu não tenho todas as respostas. Espero que eu possa lançar alguma luz sobre isso.

Para simplificar minhas declarações anteriores sobre os modelos de encadeamento do .NET, basta saber que a Parallel Library usa Tasks, e o TaskScheduler for Tasks padrão, usa o ThreadPool. Quanto mais alto você subir na hierarquia (o ThreadPool fica na parte inferior), mais sobrecarga você terá ao criar os itens. Essa sobrecarga extra certamente não significa que é mais lenta, mas é bom saber que está lá. Por fim, o desempenho do seu algoritmo em um ambiente multithread se resume ao seu design. O que executa bem sequencialmente pode não funcionar tão bem em paralelo. Existem muitos fatores envolvidos para fornecer regras rígidas e rápidas, que mudam dependendo do que você está tentando fazer. Como você está lidando com solicitações de rede, tentarei dar um pequeno exemplo.

Deixe-me declarar que não sou especialista em soquetes e não sei quase nada sobre o Zeroc-Ice. Eu sei um pouco sobre operações assíncronas, e é aqui que ele realmente o ajudará. Se você enviar uma solicitação síncrona por meio de um soquete, quando ligar Socket.Receive(), seu encadeamento será bloqueado até que uma solicitação seja recebida. Isso não é bom. Seu segmento não pode fazer mais solicitações, pois está bloqueado. Usando Socket.Beginxxxxxx (), a solicitação de E / S será feita e colocada na fila do IRP para o soquete, e seu encadeamento continuará. Isso significa que seu encadeamento pode realmente fazer milhares de solicitações em um loop sem nenhum bloqueio!

Se estou entendendo corretamente, você está usando chamadas via Zeroc-Ice em seu código de teste, sem realmente tentar alcançar um ponto de extremidade http. Se for esse o caso, posso admitir que não sei como o Zeroc-Ice funciona. Gostaria, no entanto, sugerem seguindo o conselho aqui , particularmente a parte: Consider Asynchronous Method Invocation (AMI). A página mostra isso:

Ao usar a AMI, o cliente recupera o encadeamento de controle assim que a chamada foi enviada (ou, se não puder ser enviada imediatamente, estiver na fila), permitindo que o cliente use esse encadeamento para executar outro trabalho útil nesse meio tempo. .

O que parece ser o equivalente ao que descrevi acima usando soquetes .NET. Pode haver outras maneiras de melhorar o desempenho ao tentar fazer muitos envios, mas eu começaria aqui ou com qualquer outra sugestão listada nessa página. Você tem sido muito vago quanto ao design do seu aplicativo, para que eu possa ser mais específico do que estive acima. Lembre-se de que não use mais threads do que o absolutamente necessário para obter o que você precisa, caso contrário, o aplicativo será executado muito mais lentamente do que o necessário.

Alguns exemplos em pseudocódigo (tentaram torná-lo o mais próximo possível do gelo, sem que eu realmente precisasse aprender):

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

Uma maneira melhor:

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

Lembre-se de que mais threads! = Melhor desempenho ao tentar enviar soquetes (ou realmente fazer alguma coisa). Os threads não são mágicos, pois resolverão automaticamente qualquer problema em que você esteja trabalhando. Idealmente, você deseja 1 thread por núcleo, a menos que um thread gaste muito tempo aguardando, você pode justificar ter mais. A execução de cada solicitação em seu próprio encadeamento é uma má idéia, pois as alternâncias de contexto ocorrerão e o desperdício de recursos. (Se você quiser ver tudo o que escrevi sobre isso, clique em editar e veja as revisões anteriores deste post. Eu o removi, pois ele parecia apenas obscurecer o principal problema em questão.)

Definitivamente, você pode fazer essas solicitações em threads, se desejar fazer um grande número de solicitações por segundo. No entanto, não exagere com a criação do encadeamento. Encontre um equilíbrio e fique com ele. Você obterá melhor desempenho se usar um modelo assíncrono versus um síncrono.

Espero que ajude.

Christopher Currens
fonte
Por que você está falando tanto sobre desempenho? Isso não parece ser o que o OP quer.
svick
@svick bem, o post original do ops tinha 4 perguntas originalmente, e eles fizeram perguntas sobre o desempenho de tarefas paralelas versus tarefas, depois foram editadas e agora estão de volta. Então, muito do que você leu foi resultado disso. Por fim, embora sua pergunta tenha a ver com desempenho, como ele tem a idéia geral correta, mas aparentemente está faltando em sua implementação. Acredito que minhas respostas pontuais no final respondem à pergunta que ele não editou.
Christopher Currens
Fui forçado a reduzir minhas perguntas porque eles queriam votar para encerrar. Agora parece que é válido aqui tê-los. @ChristopherCurrens +1 em um bom ponto para a diferença com o conjunto de threads nas tarefas. Isso ampliou meu entendimento. Mas ainda estou paralisado como é possível gerar algumas solicitações de N por segundo? Qual é exatamente a melhor maneira de fazer isso?
Rei
@ King - Eu acho que não era tão claro quanto pensei que era. Os últimos 3-4 parágrafos que eu pensei que o ajudariam. Eu tinha assumido que você já estava usando uma espécie de loop. Se você estava fazendo isso, o problema é que seu soquete envia / recebe está bloqueando e, assim, diminuindo a velocidade de suas solicitações. Talvez eu encontre algum tempo para postar um exemplo de pseudo-código.
Christopher Currens
Não tenho nenhum problema em realmente enviá-los pelo ICE. O problema é o que define a implementação que realmente criaria N pedidos e algo que pode ser dito verdadeiro para esse número, N.
Rei
2

Vou pular a pergunta 1) e entrar na segunda posição, pois geralmente é uma maneira aceitável de realizar o que você está procurando. No passado para alcançar n mensagens por segundo que você pode criar um único processo que irá, então, lançar p AppDomains. Cada AppDomain basicamente começa a executar um loop de solicitação assim que um determinado momento é atingido (usando um Timer). Esse tempo deve ser o mesmo para cada AppDomain para garantir que eles comecem a atingir o servidor no mesmo momento.

Algo assim deve funcionar para enviar seus pedidos:

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

Provavelmente, isso prejudicará o desempenho em qualquer máquina em que você esteja executando, portanto, você sempre poderá implementar um tipo semelhante de loop de várias máquinas diferentes se tiver os recursos (usando processos em vez de domínios de aplicativo).

Para sua terceira pergunta, leia este link em http://www.albahari.com/threading/

Finalmente, um cronômetro deve ser associado a um contador de ocorrências para rastrear a duração e as ocorrências únicas no seu servidor. Isso deve permitir que você faça algumas análises após o fato.

Chris
fonte
2
Que possível motivo você teria para criar AppDomains separados aqui? Isso parece completamente desnecessário.
svick
0

Não se preocupe com os tópicos, se N for razoavelmente pequeno. Para gerar N solicitações por segundo, use o relógio de parede ( DateTime.Now). Reserve um tempo antes e depois da solicitação e adicione a Sleeppara atrasar a próxima solicitação.

Por exemplo, com 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)

Isto não é perfeito; você pode achar que isso Sleepnão é exato. Você pode manter uma contagem contínua de desvios (antes do X'th solicitar, o tempo deve ser X-1 / N depois) e ajustar o período de suspensão de acordo.

Quando N se torna muito grande, você simplesmente cria M threads e permite que cada thread gere solicitações N / M da mesma maneira.

MSalters
fonte
Eu tenho que gerar um número muito alto de solicitações. Portanto, essa não pode ser a opção, pois ela consumirá minha memória (4 GB de RAM) antes mesmo de 100 threads.
Rei
Criei 20.000 solicitações por segundo a partir de um único thread, em 250K de código. Você não tem CPU suficiente para executar 100 threads de qualquer maneira (essa classe de máquinas não vem com 4 GB). O próximo problema seria empurrar todos esses pedidos; você tem Ethernet de 10 Gbit / s entre o criador de carga e o servidor? Portanto, convém verificar seus requisitos reais.
precisa saber é o seguinte
Para esclarecer, tenho algo como 20+ Gbps. Então isso não é um problema. Sobre a classe de máquinas, a que você se referiria? número de processadores?
Rei
@ King: para empurrar 100 threads, eu esperaria uma máquina com 48 núcleos. A SGI vende máquinas com tantos núcleos, por exemplo, mas naquelas você normalmente obtém 32 GB ou mais.
precisa saber é o seguinte
0

A maneira mais fácil de realizar o teste de carga para qualquer projeto .NET é comprar a edição Ultimate do Visual Studio. Isso vem com ferramentas de teste integradas para ajudar a realizar todos os tipos de testes, incluindo testes de carga. Os testes de carga podem ser pré-formados, criando usuários virtuais em um único PC ou distribuídos por vários para um número maior de usuários; também há um pequeno programa que pode ser instalado nos servidores de destino para retornar dados adicionais durante o teste.

Porém, isso é caro, mas a edição final vem com muitos recursos; portanto, se todos fossem usados, seria um preço mais razoável.

Ryathal
fonte
0

Se você deseja que todos os threads do X atinjam seu recurso exatamente ao mesmo tempo, você pode colocar cada thread atrás de uma trava de contagem regressiva e especificar um curto período de espera entre as verificações do semáforo.

O C # possui uma implementação (http://msdn.microsoft.com/en-us/library/system.threading.countdownevent(VS.100).aspx).

Ao mesmo tempo, se você estiver testando o sistema com estresse, também poderá verificar as condições de corrida. Nesse caso, você desejaria configurar períodos de suspensão de encadeamento em cada encadeamento que oscilam ao longo do tempo com frequência aleatória e picos / sulcos.

Da mesma forma, talvez você não queira enviar rapidamente várias solicitações rapidamente, pode ter um sucesso maior em colocar seu servidor em mau estado / testar seu desempenho no mundo real, configurando um número menor de threads que gastam mais tempo consumindo e enviando mensagens de volta e adiante no soquete, pois seu servidor provavelmente precisará ativar seus próprios threads para lidar com mensagens lentas em andamento.

Keith Brings
fonte