Execute tarefas simultaneamente para maximizar o uso dos nós de computação do Lote

Você pode maximizar o uso de recursos em um número menor de nós de computação em seu pool executando mais de uma tarefa simultaneamente em cada nó.

Embora alguns cenários funcionem melhor com todos os recursos de um nó dedicados a uma única tarefa, determinadas cargas de trabalho podem diminuir os tempos de trabalho e reduzir custos quando várias tarefas compartilham esses recursos. Considere os seguintes cenário:

  • Minimizar a transferência de dados para tarefas que puderem compartilhar dados. Você pode reduzir muito os encargos de transferência de dados copiando dados compartilhados para um número menor de nós, executando tarefas em paralelo em cada nó. Esta estratégia se aplica especialmente se os dados a serem copiados para cada nó precisarem ser transferidos entre regiões geográficas.
  • Maximizar o uso de memória para tarefas que exigem uma grande quantidade de memória, mas somente durante curtos períodos de tempo e em momentos variáveis durante a execução. Você pode empregar menos nós de computação, porém maiores, com mais memória para lidar de forma eficiente com esses picos. Esses nós têm várias tarefas executadas em paralelo em cada nó, mas cada tarefa aproveitará a grande quantidade de memória dos nós em momentos diferentes.
  • Reduzir dos limites do número de nós quando a comunicação entre nós for necessária em um pool. Atualmente, os pools configurados para comunicação entre nós estão limitados a 50 nós de computação. Se cada nó desse pool for capaz de executar tarefas em paralelo, será possível executar um número maior de tarefas em paralelo.
  • Replicar de um cluster de computação local, como na primeira movimentação de um ambiente de computação para o Azure. Se sua solução local atual executar várias tarefas por nó de computação, você poderá aumentar o número máximo de tarefas de nó para espelhar mais de perto a configuração.

Cenário de exemplo

Por exemplo, imagine um aplicativo de tarefa com requisitos de CPU e memória em que nós Standard_D1 são suficientes. Porém, para concluir o trabalho no tempo necessário, mil nós são necessários.

Em vez de usar nós Standard_D1, que têm um núcleo de CPU, você poderia usar nós Standard_D14, que têm 16 núcleos cada, e habilitar a execução de tarefas paralelas. Você poderia usar 16 vezes menos nós em vez de 1.000 nós, pois seriam necessários apena 63. Se arquivos de aplicativos grandes ou dados de referência forem necessários para cada nó, a eficiência e a duração do trabalho serão aperfeiçoadas, uma vez que os dados são copiados para apenas 63 nós.

Habilitar a execução de tarefas paralelas

Você configura os nós de computação para a execução paralela das tarefas no nível do pool. Com a biblioteca .NET do Lote, defina a propriedade CloudPool.TaskSlotsPerNode ao criar um pool. Se você estiver usando a API REST do Lote, defina o elemento taskSlotsPerNode no corpo da solicitação durante a criação do pool.

Observação

Você pode definir o elemento taskSlotsPerNode e a propriedade TaskSlotsPerNode apenas no momento da criação do pool. Eles não poderão ser modificados após a criação do pool.

O Lote do Azure permite configurar o número de tarefas por nó até quatro vezes (4x) o número de núcleos de nós. Por exemplo, se o pool estiver configurado com nós de tamanho "Grande" (quatro núcleos), será possível definir taskSlotsPerNode como 16. No entanto, independentemente de quantos núcleos o nó tem, você não pode ter mais de 256 slots de tarefas por nó. Para obter detalhes sobre o número de núcleos para cada um dos tamanhos de nó, consulte Tamanhos para Serviços de Nuvem (clássico). Para saber mais sobre limites de serviço, consulte Cotas e limites de serviço em Lote.

Dica

Leve em consideração o valor de taskSlotsPerNode ao criar uma fórmula de dimensionamento automático para o seu pool. Por exemplo, uma fórmula que avalia $RunningTasks poderia ser drasticamente afetada por um aumento nas tarefas por nó. Para mais informações, confirar Criar uma fórmula de dimensionamento automático de nós de computação em um pool do Lote.

Especificar distribuição de tarefas

Ao habilitar tarefas simultâneas, é importante especificar como você deseja que as tarefas sejam distribuídas entre os nós no pool.

Ao usar a propriedade CloudPool.TaskSchedulingPolicy, você pode especificar que as tarefas devam ser atribuídas uniformemente em todos os nós no pool ("difusão"). Ou você pode especificar que o máximo possível de tarefas deve ser atribuído a cada nó antes de as tarefas serem atribuídas a outro nó no pool ("remessa").

Como exemplo, considere o pool de nós Standard_D14 (no exemplo anterior) que está configurado com um valor de 16 para CloudPool.TaskSlotsPerNode. Se CloudPool.TaskSchedulingPolicy for configurada com um ComputeNodeFillType igual a Pack, ela maximizaria o uso de todos os 16 núcleos de cada nó e permitiria que um pool de dimensionamento automático removesse os nós não utilizados (nós sem nenhuma tarefa atribuída) do pool. O dimensionamento automático minimiza o uso de recursos e pode economizar dinheiro.

Definir slots variáveis por tarefa

Uma tarefa pode ser definida com a propriedade CloudTask.RequiredSlots, especificando quantos slots ela requer para ser executada em um nó de computação. O valor padrão é 1. Você pode definir os slots de tarefas variáveis se suas tarefas tiverem cargas diferentes associadas ao uso de recursos no nó de computação. Os slots de tarefas variáveis permitem que cada nó de computação tenha um número razoável de tarefas simultâneas em execução sem sobrecarregar os recursos do sistema, como CPU ou memória.

Por exemplo, para um pool com propriedade taskSlotsPerNode = 8, você pode enviar tarefas com uso intensivo de CPU de vários núcleos com requiredSlots = 8, enquanto outras tarefas podem ser definidas como requiredSlots = 1. Quando esta carga de trabalho mista é agendada, as tarefas de uso intensivo de CPU são executadas exclusivamente em seus nós de computação, enquanto outras podem ser executadas simultaneamente (até oito tarefas ao mesmo tempo) em todos os nós. A carga de trabalho mista ajuda a equilibrar a carga de trabalho entre os nós de computação e a melhorar a eficiência do uso de recursos.

Certifique-se de não especificar que requiredSlots de uma tarefa seja maior que taskSlotsPerNode do pool, ou a tarefa nunca será executada. Atualmente, o Serviço de Lote não valida esse conflito quando você envia tarefas. Ele não valida esse conflito, porque um trabalho pode não ter um pool limitado no momento do envio ou pode ser alterado para um pool diferente ao desabilitar/reabilitar.

Dica

Ao usar slots variáveis de tarefas, é possível que o agendamento de tarefas grandes com slots mais necessários falhe temporariamente, porque não há slots suficientes disponíveis em nenhum nó de computação, mesmo quando ainda há slots ociosos em alguns nós. Você pode aumentar a prioridade do trabalho para essas tarefas para aumentar sua chance de competir por slots disponíveis em nós.

O serviço de Lote emite o TaskScheduleFailEvent quando ele não consegue agendar a execução de uma tarefa e continua tentando novamente o agendamento até que os slots necessários fiquem disponíveis. Você pode escutar esse evento para detectar possíveis problemas de agendamento de tarefas e atenuá-los adequadamente.

Exemplo de .NET do Lote

Os seguintes trechos de código do API .NET do lote mostram como criar um pool com vários slots de tarefa por nó e como enviar uma tarefa com slots necessários.

Criar um pool com vários slots de tarefa por nó

Este snippet de código mostra uma solicitação para criar um pool que contém quatro nós, com quatro slots de tarefas permitidos por nó. Ele especifica uma política de agendamento de tarefas que preenche cada nó com tarefas antes de atribuir tarefas a outro nó no pool.

Para saber mais sobre como adicionar pools usando a API .NET do Lote, consulte BatchClient.PoolOperations.CreatePool.

CloudPool pool =
    batchClient.PoolOperations.CreatePool(
        poolId: "mypool",
        targetDedicatedComputeNodes: 4
        virtualMachineSize: "standard_d1_v2",
        VirtualMachineConfiguration: new VirtualMachineConfiguration(
            imageReference: new ImageReference(
                                publisher: "MicrosoftWindowsServer",
                                offer: "WindowsServer",
                                sku: "2019-datacenter-core",
                                version: "latest"),
            nodeAgentSkuId: "batch.node.windows amd64");

pool.TaskSlotsPerNode = 4;
pool.TaskSchedulingPolicy = new TaskSchedulingPolicy(ComputeNodeFillType.Pack);
pool.Commit();

Criar uma tarefa com slots necessários

Este snippet de código cria uma tarefa requiredSlots não padrão. Esta tarefa é executada quando há slots livres suficientes disponíveis em um nó de computação.

CloudTask task = new CloudTask(taskId, taskCommandLine)
{
    RequiredSlots = 2
};

Listar nós de computação com contagens para execução de tarefas e slots

Este snippet de código lista todos os nós de computação no pool e imprime as contagens de tarefas em execução e slots de tarefa por nó.

ODATADetailLevel nodeDetail = new ODATADetailLevel(selectClause: "id,runningTasksCount,runningTaskSlotsCount");
IPagedEnumerable<ComputeNode> nodes = batchClient.PoolOperations.ListComputeNodes(poolId, nodeDetail);

await nodes.ForEachAsync(node =>
{
    Console.WriteLine(node.Id + " :");
    Console.WriteLine($"RunningTasks = {node.RunningTasksCount}, RunningTaskSlots = {node.RunningTaskSlotsCount}");

}).ConfigureAwait(continueOnCapturedContext: false);

Listar contagens de tarefas para o trabalho

Esse snippet de código obtém as contagens de tarefas para o trabalho, o que inclui tarefas e contagem de slots de tarefa por estado de tarefa.

TaskCountsResult result = await batchClient.JobOperations.GetJobTaskCountsAsync(jobId);

Console.WriteLine("\t\tActive\tRunning\tCompleted");
Console.WriteLine($"TaskCounts:\t{result.TaskCounts.Active}\t{result.TaskCounts.Running}\t{result.TaskCounts.Completed}");
Console.WriteLine($"TaskSlotCounts:\t{result.TaskSlotCounts.Active}\t{result.TaskSlotCounts.Running}\t{result.TaskSlotCounts.Completed}");

Exemplo REST do Lote

Os seguintes trechos de código do API REST do lote mostram como criar um pool com vários slots de tarefa por nó e como enviar uma tarefa com slots necessários.

Criar um pool com vários slots de tarefa por nó

Esse snippet mostra uma solicitação para criar um pool com dois nós grandes, com um máximo de quatro tarefas por nó.

Para obter mais informações sobre como adicionar pools usando a API REST, consulte Adicionar um pool a uma conta.

{
  "odata.metadata":"https://myaccount.myregion.batch.azure.com/$metadata#pools/@Element",
  "id":"mypool",
  "vmSize":"large",
  "virtualMachineConfiguration": {
    "imageReference": {
      "publisher": "canonical",
      "offer": "ubuntuserver",
      "sku": "20.04-lts"
    },
    "nodeAgentSKUId": "batch.node.ubuntu 20.04"
  },
  "targetDedicatedComputeNodes":2,
  "taskSlotsPerNode":4,
  "enableInterNodeCommunication":true,
}

Criar uma tarefa com slots necessários

Este snippet de código mostra uma solicitação para adicionar uma tarefa requiredSlots não padrão. Esta tarefa só é executada quando há slots livres suficientes disponíveis no nó de computação.

{
  "id": "taskId",
  "commandLine": "bash -c 'echo hello'",
  "userIdentity": {
    "autoUser": {
      "scope": "task",
      "elevationLevel": "nonadmin"
    }
  },
  "requiredSLots": 2
}

Exemplo de código no GitHub

O projeto ParallelTasks no GitHub ilustra o uso da propriedade CloudPool.TaskSlotsPerNode.

Este aplicativo de console C# usa a biblioteca .NET do Lote para criar um pool com um ou mais nós de computação. Ele executa um número configurável de tarefas nesses nós para simular uma carga variável. A saída do aplicativo especifica mostra quais nós executaram cada tarefa. O aplicativo também fornece um resumo dos parâmetros do trabalho e a duração.

O exemplo a seguir mostra a parte de resumo da saída de duas execuções diferentes do aplicativo de exemplo ParallelTasks. As durações de trabalho mostradas aqui não incluem o tempo de criação do pool, pois cada trabalho foi enviado para um pool criado anteriormente cujos nós de computação estavam no estado Ocioso no momento do envio.

A primeira execução do aplicativo de exemplo mostra que com um único nó no pool, e a configuração padrão de uma tarefa por nó, a duração do trabalho será superior a 30 minutos.

Nodes: 1
Node size: large
Task slots per node: 1
Max slots per task: 1
Tasks: 32
Duration: 00:30:01.4638023

A segunda execução do exemplo mostra uma redução significativa na duração do trabalho. Esta redução se deve ao fato de o pool ter sido configurado com quatro tarefas por nó, permitindo que a execução de tarefas paralelas concluísse o trabalho em quase um quarto do tempo.

Nodes: 1
Node size: large
Task slots per node: 4
Max slots per task: 1
Tasks: 32
Duration: 00:08:48.2423500

Próximas etapas