Click or drag to resize

Constrain the amount of resources required for a task to be scheduled

Problem

You need fine grain control over how many cores a task is consuming.

Solution

On the task, call setProperty(java.lang.String, java.lang.Object) and pass the resource to be configured as the second parameter. There are currently two resources that can be configured for consumption: cores and memory. These can be set by specifying TaskProperties.MAX_CORES_FOR_TASK and TaskProperties.MAX_MEMORY_FOR_TASK respectively.

Java
task.setProperty(TaskProperties.MIN_CORES_FOR_TASK, 2);
task.setProperty(TaskProperties.MAX_CORES_FOR_TASK, 4);

A task can inspect the following properties when it executes to retrieve the resource amounts allocated to it: TaskProperties.ALLOCATED_CORES_FOR_TASK and TaskProperties.ALLOCATED_MEMORY_FOR_TASK.

Java
long coresAllocated = (Long) this.getProperty(TaskProperties.ALLOCATED_CORES_FOR_TASK);

Below is a code example that demonstrates how to configure a task to consume between 2 and 4 cores.

Java
package stkparallelcomputingserversdk.howto;

import agi.parallel.client.ClusterJobScheduler;
import agi.parallel.client.IJobScheduler;
import agi.parallel.client.Job;
import agi.parallel.infrastructure.Task;
import agi.parallel.infrastructure.TaskProperties;

public class ControlResourcesGivenToTask {
    public static void main(String[] args) {
        try (IJobScheduler scheduler = new ClusterJobScheduler("localhost")) {
            scheduler.connect();

            Job job = scheduler.createJob();
            Task task = new ReturnsNumberOfAllocatedCores();

            // Configure the task to use at least 2 cores, but no more than 4.
            task.setProperty(TaskProperties.MIN_CORES_FOR_TASK, 2);
            task.setProperty(TaskProperties.MAX_CORES_FOR_TASK, 4);

            job.addTask(task);

            job.submit();
            job.waitUntilDone();

            System.out.println("Cores allocated: " + task.getResult());
        }

        /*
         * The output of the application should resemble:
         * Cores allocated: 4
         */
    }

    public static class ReturnsNumberOfAllocatedCores extends Task {
        @Override
        public void execute() {
            /*
             * You can get the number of cores allocated by inspecting the
             * TaskProperties.ALLOCATED_CORES_FOR_TASK property.
             * You can use this value to decide how many threads you can spawn in your task.
             */
            long coresAllocated = (Long) this.getProperty(TaskProperties.ALLOCATED_CORES_FOR_TASK);
            this.setResult(coresAllocated);
        }
    }
}

Below is another code example that demonstrates how to configure a task to consume a specific amount of the estimated memory budget.

J#
package stkparallelcomputingserversdk.howto;

import agi.parallel.client.ClusterJobScheduler;
import agi.parallel.client.IJobScheduler;
import agi.parallel.client.Job;
import agi.parallel.infrastructure.Task;
import agi.parallel.infrastructure.TaskProperties;

public class ControlEMBGivenToTask {

    public static void main(String[] args) {
        try (IJobScheduler scheduler = new ClusterJobScheduler("localhost")) {
            scheduler.connect();
            Job job = scheduler.createJob();
            Task task;
            for (int i = 1; i < 3; i++) {
                task = new EstimatedMemoryUsageExample();
                // Configure the task to use i*3 GB of the estimated memory budget
                task.setProperty(TaskProperties.ESTIMATED_MEMORY_USAGE_FOR_TASK, i * 3072);
                job.addTask(task);
            }            

            job.submit();
            job.waitUntilDone();
        }

        /*
         * If the machine has 6GB of memory and the two tasks are 
         * estimated to require 6GB and 3GB of memory, the task estimated 
         * to require 6GB memory won't run until the task estimated to 
         * require 3GB memory completes.
         */
    }

    public static class EstimatedMemoryUsageExample extends Task {
        @Override
        public void execute() {
            /*
             * Even though the body of the Execute() function is empty, the agent's 
             * available estimated memory budget must be large enough to schedule a
             * task with a non-zero value of the EstimatedMemoryUsageForTask.
             */
        }
    }
}
Discussion

When a task executes, what is happening internally is that the task is consuming a resource. The most common of these resources is "Cores". By default a task consumes one resource of type "Cores" on an agent. On a machine with eight cores, an agent can run eight concurrent tasks at a time until its "Cores" resource is exhausted. The amount of resources a task can consume can be optionally specified. For instance, if each task is configured to consume at least two cores, the eight core machine can only run four of these tasks concurrently. When a task is finished, then it will release the resources that it was allocated back into the pool of available resources.

The resource "Memory" has a limited number of useful applications. This resource will check the current available memory of the current machine at the time a task is attempting to be executed. Imagine a machine with 8GB of memory and 8 cores. If you attempt to submit four tasks each with a minimum memory set at 4GB, even if two tasks are already executing, as long as there is 4GB of available memory, the other tasks can begin execution. In this case, the number of tasks running is not being constrained by the memory resource properly. An example of a practical use for this resource is if you have a cluster of machines and the ExclusiveExecution flag is true for the job so that only one task may run at a time per machine. After finishing executing a task, a check will be made to ensure there is enough free memory on the machine to execute the next task.

The third type of resource is the "Estimated Memory Usage". This resource will simulate allocation of memory for tasks, without actually checking how much physical memory each task consumes as it executes. The total memory budget is set for the agent with the default value equal to the number of MB of physical memory available on the machine. The estimated memory usage is then set for each task and subtracted from the agent's total memory budget as the task executes. If a task's estimated memory usage is less than the agent's available budget, it will run. If a tasks's estimated memory usage is greater than the available budget, it will wait to be executed until there is enough memory available. The default estimated memory usage for a task is zero. Imagine a task believed to consume 2GB of memory. On an agent with a estimated memory budget of 6GB and eight cores, the agent would only execute three of these tasks concurrently to prevent the agent from swapping massive amounts of memory. In this case, configure the task's estimated memory usage to 2GB.

Note that tasks are not restricted from only using the resources allocated to them. That is, a task with two resources of type "Cores" can spawn as many threads as it wants. The task will not be stopped from over consuming its declared resources. Efficient use of resource consumption requires that tasks are coded to be honest and only use the resources they are allocated.

See Also

STK Parallel Computing Server 2.9 API for Java