Blog

Entfesseln Sie die Leistung von Azure Batch für Monte-Carlo-Simulationen

Esteban Garcia

Esteban Garcia

Aktualisiert Oktober 15, 2025
11 Minuten

Das Konzept der Simulation hat mich schon immer fasziniert. Der Gedanke, ein System in der Natur nachzuahmen, um Erkenntnisse zu gewinnen und daraus Entscheidungen abzuleiten. Simulationen können in komplexen Systemen eingesetzt werden, die es in vielen Bereichen wie Natur, Technik, Sport und Wirtschaft gibt. Die Verfügbarkeit moderner Technologie macht es uns leicht, diese Art von Simulationen durchzuführen, ohne dass wir dafür übermäßig viel Zeit und Geld aufwenden müssen. In diesem Blogartikel geht es um das Schreiben und Ausführen einer Simulation mit Hochleistungs-Rechenressourcen in Azure.

Monte-Carlo-Simulationen und Normalverteilung

Eine spezielle Art der Simulation, die eingesetzt werden kann, ist die Monte-Carlo-Simulation. Dabei handelt es sich um einen leistungsstarken Ansatz zur Modellierung und Untersuchung komplexer Systeme, die Zufälligkeiten und Unsicherheiten beinhalten. Die Simulationen erzeugen Ergebnisse in Form einer Wahrscheinlichkeitsverteilung, die analysiert werden können, um auf der Grundlage des Ergebnisses Entscheidungen zu treffen.

Die Normalverteilung (oder Gaußsche Verteilung) kann in einer Monte-Carlo-Simulation als Wahrscheinlichkeitsverteilung verwendet werden. Wenn Sie sich an Ihren Statistik- oder Matheunterricht zurückerinnern, ist die Normalverteilung durch den Mittelwert (Mittelpunkt der Glockenkurve) gekennzeichnet. Die Standardabweichung sind die Bereiche links und rechts vom Mittelwert.

Die Details der Monte-Carlo-Simulationen liegen außerhalb des Rahmens dieses Blogs. Wenn Sie mehr erfahren möchten, besuchen Sie diesen Link.

Wenn Sie eine Monte-Carlo-Simulation durchführen, verbessert die Erhöhung der Anzahl der Iterationen der Simulation tendenziell die Genauigkeit und Zuverlässigkeit der Ergebnisse. Je mehr Iterationen Sie durchführen, desto größer ist die Stichprobe und desto geringer ist die zufällige Variabilität der Simulation selbst. Mit anderen Worten: Je mehr Simulationen Sie durchführen, desto besser werden Ihre Ergebnisse. Bei einfachen Modellen reicht es wahrscheinlich aus, eine Simulation auf einem Laptop oder Ihrem Desktop zu Hause durchzuführen. Wenn Sie ein komplexes Modell haben und die Simulation in kurzer Zeit durchführen müssen, bietet die Cloud eine fantastische Option.

Ich habe ein Programm in C# geschrieben, das die Zeit simuliert, die für die Fertigstellung eines Softwareentwicklungsprojekts benötigt wird, indem es drei verschiedene Teams mit unterschiedlichem Personal vergleicht. Ich habe dieses Programm auch verwendet, um Tausende (leicht Millionen) von Simulationsiterationen auszuführen und dabei eines der Azure-Hochleistungs-Rechenangebote, Azure Batch, zu nutzen.

Beratungsteam Simulation

Der erste Schritt bei der Ausführung einer Monte-Carlo-Simulation mit Azure Batch besteht darin, ein Modell zu definieren, indem Sie ein Programm schreiben. Ich habe eine C#-Lösung namens 'consulting-team-simulation' und ein entsprechendes Projekt namens 'team-simulator' erstellt. Das Programm 'team-simulator' führt N Simulationsiterationen über eine Normalverteilung für drei verschiedene Softwareentwicklungsteams durch. Dies ist ein einfaches Beispiel, um zu zeigen, wie ein Modell in diesem Szenario programmiert werden kann. Die in der Simulation verwendeten Teams sind wie folgt:

  • megaTeam - 2 Entwickler, 0 Projektmanager und 1 QA-Tester
  • gigaTeam - 1 Entwickler, 1 Projektmanager und 1 QA-Tester
  • petaTeam - 2 Entwickler, 1 Projektleiter und 0 QA-Tester

Die Zeitprognose wird für jedes Team anhand eines einfachen Modells berechnet. Anhand von Agile Story Points wird den geschätzten Gesamtpunkten des Projekts ein beliebiger Wert zugewiesen. Als nächstes wird die Punktgeschwindigkeit jedes Entwicklers bestimmt. Da einige Entwickler eine höhere Anzahl von Punkten abschließen können als andere, wird dies in der Logik berücksichtigt. Sobald dies abgeschlossen ist, wird die Dauer des Projekts anhand der Geschwindigkeit des Entwicklers berechnet und die Anwesenheit eines Projektmanagers oder QA-Testers erhöht oder verringert die prognostizierte Gesamtzeit. Die Details der Logik können Sie dem folgenden Codeschnipsel entnehmen:

internal double GetWeeklyProjection()
{
    var random = new Random();
    const int hoursInWorkWeek = 40;
    const double existingProjectManagerFactor = .45;
    const double lackOfProjectManagerFactor = 1.3;
    const double qaTesterFactor = 0.5;
    var developerPerformance = new List<(double weeklyVelocity, double pointsPerHours)>();

    //Get the random weekly velocities for each dev (factor this out)
    var developerCounter = NumberOfDevelopers;
    while (developerCounter > 0)
    {
        //using a random base point value between 3 and 8 points
        var weeklyVelocity = (double)random.Next(3, 8);
        var pointsPerHour = weeklyVelocity / 40;
        developerPerformance.Add((weeklyVelocity, pointsPerHour));
        developerCounter--;
    }

    var pointsCounter = 0.0;
    var developerHoursForProject = 0;
    while (pointsCounter < TotalPoints)
    {
        pointsCounter += developerPerformance.Sum(dp => dp.pointsPerHours);
        developerHoursForProject++;
    }

    //add random drag hours for project
    var totalProjectHours = developerHoursForProject * GetRandomVariance(random);

    //Estimating having a project manager would reduce hours by 45% and without one would delay project by 30%
    if (NumberOfProjectManagers > 0)
    {
        totalProjectHours -= (totalProjectHours * existingProjectManagerFactor);
    }
    else
    {
        totalProjectHours += (totalProjectHours * lackOfProjectManagerFactor);
    }

    if (NumberOfQaTesters > 0)
    {
        totalProjectHours += developerHoursForProject * qaTesterFactor;
    }

    return totalProjectHours / hoursInWorkWeek;
}

Sobald die anfänglichen Zeitprojektionen für jedes Team berechnet sind, werden die Projektionen als Mittelwert über eine Normalverteilung verwendet. Jedes Team hat eine Zeitdauerprojektion. Zum Beispiel kann das MegaTeam eine Projektion von 30 Stunden haben und das GigaTeam eine Projektion von 25 Stunden. An dieser Stelle wird die Simulation ausgeführt, die zufällig so viele Projektionswerte erzeugt, wie Sie Versuche über eine Normalverteilungskurve haben. Wenn Sie z.B. 500 Versuche wünschen, wird das Programm 500 Projektionen über die Kurve erstellen.

Ich habe dies mit der Task Parallel Library (TPL) für Gleichzeitigkeit und Leistung getan. Für jede Iteration (oder jeden Versuch) der Simulation wird eine neue Aufgabe zur Ausführung in die Warteschlange gestellt. Die Methode SimulateTeamProjections() wird über mehrere Threads parallel aufgerufen. Die Übersicht über die Gleichzeitigkeit sehen Sie hier:

Die Methode, die die Simulation durchführt und die zufällige simulierte Projektion berechnet, verwendet die Methode InverseLeftProbability aus dem Statistics-Namespace, Meta.Numerics.Statistics.Distribution:

private static void SimulateAllTeamProjections(List<double> teamProjections)
{
    var simulationResult = new TeamSimResult();
    var trialSimulatedResults = new List<double>();
    foreach (var projection in teamProjections)
    {
        //we simulate each trial over a normal distribution curve
        var stdDev = Convert.ToDouble(0.5);
        var normalDistribution = new NormalDistribution(projection, stdDev);
        var randomDouble = GetRandomNumber();
        var simulatedProjection = normalDistribution.InverseLeftProbability(randomDouble);
        trialSimulatedResults.Add(Math.Round(simulatedProjection, 2));
    }

    simulationResult.MegaTeamTime = trialSimulatedResults[0];
    simulationResult.GigaTeamTime = trialSimulatedResults[1];
    simulationResult.PetaTeamTime = trialSimulatedResults[2];
    _simResultsBag?.Add(simulationResult);
}

Sobald die Zeitprojektionen für alle Versuche über eine Normalverteilungskurve simuliert wurden, werden die Ergebnisse im csv-Format auf dem Dateisystem ausgegeben.

Azure Batch verwenden

Azure Batch ist ein High-Performance-Computing-Angebot, das große parallele und Batch-Computing-Workloads ausführt. Es ist am besten für zeitintensive, komplexe Simulationen geeignet. Bevor wir fortfahren, sollten wir einige der Komponenten definieren, die zur Ausführung von Simulationen in Azure Batch verwendet werden. Sie umfassen:

  • Pool - stellt eine Gruppe von Knoten (virtuellen Maschinen) dar, die zur Ausführung eines Programms oder eines Skripts verwendet werden.
  • Job - eine Sammlung von Aufgaben, die auf den Nodes innerhalb des Pools ausgeführt werden.
  • Aufgabe - stellt eine Arbeitseinheit innerhalb des Auftrags dar. Jede Aufgabe kann eine Instanz eines Programms oder Skripts ausführen. Einem Knoten können mehrere Aufgaben zugewiesen werden.

Jede dieser Komponenten kann mit dem Batch SDK angepasst und konfiguriert werden. Es ist für viele Sprachen verfügbar, darunter C#, Java, Node und Python. Ich habe eine Logik geschrieben, die den Pool, den Job und die Aufgaben programmatisch erstellt und einstellt. Der Ablauf auf hoher Ebene ist hier dargestellt:

Ich werde die in der Microsoft-Dokumentation beschriebenen Schritte zur Einrichtung der Azure Batch-Ressourcen innerhalb des Portals nicht ausführen. Sie können auch Infrastruktur als Code (wie Bicep) verwenden, um die Ressourcen bereitzustellen. Die Schritte sind hier beschrieben.

Zurück zu unserem 'Team-Simulator'-Programm. Der erste Schritt bei der Ausführung des Programms auf mehreren Knoten in Azure Batch besteht darin, die Konsolenanwendung in einer eigenständigen ausführbaren Datei zu veröffentlichen. Dies ist sehr wichtig, da auf den Nodes möglicherweise nicht die .NET-Laufzeitumgebung installiert ist. Ein weiterer wichtiger Hinweis ist, dass Sie sicherstellen müssen, dass Sie das Paket mit dem passenden Chipsatz erstellen. Wenn die Nodes über x64-Chips verfügen, sollten Sie die Anwendung nicht in ARM64 erstellen. Sie sollten die Build-Ausgabe zippen und zu einem Speicherkonto hinzufügen, das mit Azure Batch verknüpft ist (siehe unten):

Sobald die Anwendung dem mit Azure Batch verknüpften Speicherkonto hinzugefügt worden ist. Jetzt ist es an der Zeit, das Batch SDK zu nutzen, um die Simulation programmatisch auszuführen.

Sie benötigen den Namen des Batch-Kontos, den Kontoschlüssel und die Konto-URL, um eine Verbindung mit dem Batch-Konto in Azure herzustellen. Außerdem benötigen Sie die Anmeldedaten für das Speicherkonto in Ihrem Code, insbesondere den Namen und den Schlüssel des Speicherkontos.

Wie Sie aus dem Code unten ersehen können, stelle ich zunächst eine Verbindung zum Speicherkonto her, erhalte einen Verweis auf den Blob-Container und erstelle den 'Output'-Container, falls er nicht existiert. Danach wird der Batch-Client als Parameter an 3 Methoden übergeben, die für die Erstellung des Pools, des Jobs und der Aufgaben zuständig sind. Die Methode MonitorTasks() schließt das Programm, sobald die Simulation abgeschlossen ist. Es ist in der Regel sinnvoll, auch Code einzubauen, der die Ressourcen in Azure aufräumt. Es gibt mehrere Beispiele für den Einstieg in Azure Batch. In diesem GitHub Repo finden Sie die Details.

const string storageConnectionString = $"DefaultEndpointsProtocol=https;AccountName={StorageAccountName};AccountKey={StorageAccountKey}";

var storageAccount = CloudStorageAccount.Parse(storageConnectionString);

// Create the blob client, for use in obtaining references to blob storage containers
var blobClient = storageAccount.CreateCloudBlobClient();
const string outputContainerName = "output";
var outputContainerSasUrl = GetContainerSasUrl(blobClient, outputContainerName, SharedAccessBlobPermissions.Write);
var sharedKeyCredentials = new BatchSharedKeyCredentials(BatchAccountUrl, BatchAccountName, BatchAccountKey);

await CreateContainerIfNotExistAsync(blobClient, outputContainerName);

using var batchClient = BatchClient.Open(sharedKeyCredentials);
await CreatePoolIfNotExistAsync(batchClient, PoolId);
await CreateJobAsync(batchClient, JobId, PoolId);
await AddTasksAsync(batchClient, JobId, outputContainerSasUrl);
await MonitorTasks(batchClient, JobId, TimeSpan.FromMinutes(30));

In der Methode CreatePoolIfNotExistAsync() werden die Konfiguration der virtuellen Maschine sowie einige Pool-Eigenschaften wie Aufgaben pro Knoten, die Planungsrichtlinie und die Anwendung, die der Pool verwenden soll, definiert.

Console.WriteLine("Creating pool [{0}]...", poolId);

var imageReference = new ImageReference(
    publisher: "MicrosoftWindowsServer",
    offer: "WindowsServer",
    sku: "2016-datacenter-smalldisk",
    version: "latest");

var virtualMachineConfiguration =
    new VirtualMachineConfiguration(
        imageReference: imageReference,
        nodeAgentSkuId: "batch.node.windows amd64");

var pool = batchClient.PoolOperations.CreatePool(
    poolId: poolId,
    targetDedicatedComputeNodes: DedicatedNodeCount,
    targetLowPriorityComputeNodes: LowPriorityNodeCount,
    virtualMachineSize: PoolVmSize,
    virtualMachineConfiguration: virtualMachineConfiguration);

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

pool.ApplicationPackageReferences = new List<ApplicationPackageReference>
{
    new()
    {
        ApplicationId = AppPackageId,
        Version = AppPackageVersion
    }
};

await pool.CommitAsync();

Die Methode CreateJobAsync() ist trivial:

private static async Task CreateJobAsync(BatchClient batchClient, string jobId, string poolId)
{
    Console.WriteLine("Creating job [{0}]...", jobId);
    var job = batchClient.JobOperations.CreateJob();
    job.Id = jobId;
    job.PoolInformation = new PoolInformation { PoolId = poolId };
    await job.CommitAsync();
}

Die Methode AddTasksAsync() durchläuft die Anzahl der definierten Aufgaben und definiert für jede Aufgabe den Pfad der Anwendung und wie sie im Kontext des Knotens ausgeführt werden soll. Die andere interessante Logik innerhalb dieser Methode ist die Festlegung, wohin die Ausgabe des Programms gehen soll. Sie können sehen, dass das outputFile-Objekt das Dateimuster und das Ziel angibt. Das bedeutet, dass nach erfolgreicher Beendigung der Aufgabe alle Dateien mit der Erweiterung .csv in den Blob-Ausgabecontainer im Speicherkonto hochgeladen werden.

private static async Task AddTasksAsync(BatchClient batchClient, string jobId, string outputContainerSasUrl)
{
    // Create a collection to hold the tasks added to the job:
    var tasks = new List<CloudTask>();

    for (var i = 0; i <= NumberOfTasks; i++)
    {
        // Assign a task ID for each iteration
        var taskId = $"Task{i}";
        var appPath = $"%AZ_BATCH_APP_PACKAGE_{AppPackageId}#{AppPackageVersion}%";

        var taskCommandLine = $"cmd /c {appPath}team-performance-simulator.exe";

        // Create a cloud task (with the task ID and command line) and add it to the task list
        var task = new CloudTask(taskId, taskCommandLine)
        {
            ResourceFiles = new List<ResourceFile>()
        };

        // Task output file will be uploaded to the output container in Storage.
        var outputFileList = new List<OutputFile>();
        var outputContainer = new OutputFileBlobContainerDestination(outputContainerSasUrl);
        var outputFile = new OutputFile($"***.csv",
            new OutputFileDestination(outputContainer),
            new OutputFileUploadOptions(OutputFileUploadCondition.TaskSuccess));
        outputFileList.Add(outputFile);
        task.OutputFiles = outputFileList;
        tasks.Add(task);
    }
    await batchClient.JobOperations.AddTaskAsync(jobId, tasks);
}

Wenn dieses Programm ausgeführt wird, werden der Auftrag und die Aufgaben im Batch-Konto bereitgestellt und die Simulation beginnt mit der Ausführung.

Anhand der folgenden Screenshots können Sie sehen, wie der Batch-Dienst den Start der Knoten, die Ausführung der Aufgaben und das Ergebnis mit den CSV-Dateien des Programms 'team-simulation' im Speicherkonto anzeigt:

Verwendung der Ausgabedaten aus der Simulation

Als Referenz sehen Sie hier einige der Beispieldaten, die durch die Simulation erzeugt wurden (die Zeit ist in Stunden angegeben):

Sobald die Simulation abgeschlossen ist, stellt sich die Frage, wie ich auf die Daten zugreifen und sie analysieren kann, um Entscheidungen zu treffen. Es gibt mehrere Möglichkeiten, wie Sie die Daten nutzen können, sobald sie im Blob-Speicher sind.

Die Ansätze sind in der folgenden Tabelle aufgeführt:

OptionBeschreibung
Azure SynapseSynapse bietet eine erstaunliche Funktion, mit der Sie SQL-Anweisungen gegen Blob-Speicher schreiben können. Die SQL-Abfragen können für die Analyse und die Ableitung von Entscheidungen ausreichend sein. Dies kann Ihnen die Zeit ersparen, die Sie benötigen, um die in den csv-Dateien enthaltenen Daten in den Tabellenspeicher oder eine Azure SQL-Datenbank zu verschieben.
Azure DatenfabrikEine Data Factory-Pipeline kann einfach geschrieben werden, um die in den csv-Dateien enthaltenen Daten in eine Azure SQL-Datenbank in einem Warehouse oder Lake zu verschieben und umzuwandeln. Sobald die Daten in der Datenbank gespeichert sind, können Abfragen zur Analyse geschrieben werden.
Azure FunktionenSie können einen Trigger in eine Funktion (oder eine dauerhafte Funktion) schreiben, um die Daten in ein persistentes Datenlager Ihrer Wahl zu verschieben.
Azure Logic AppsÄhnlich wie bei einer Funktion kann ein Trigger geschrieben werden und die Daten können an einen der vielen Konnektoren gesendet werden, die eine Logic App bietet.

Fazit

Die Möglichkeiten von Simulationen und ihre Anwendung in vielen verschiedenen Bereichen der Welt sind praktisch grenzenlos. Ich hoffe, Ihnen in diesem Artikel einen Einblick gegeben zu haben, wie Monte-Carlo-Simulationen zum einen in einem Standardprogramm und zum anderen mit Hilfe von Azure Batch ausgeführt werden können. Dieser Artikel kratzt nur an der Oberfläche, wie komplexe Modelle funktionieren und welche Möglichkeiten Azure Batch bietet. Ich fordere Sie auf, sich einen Bereich auszudenken, in dem Sie eine Simulation anwenden und Azure Batch zur Rationalisierung der Arbeitslast nutzen können!

Der Quellcode kann über mein GitHub-Konto hier eingesehen werden.

Bleiben Sie dran für den nächsten Blog, der entweder ein anderes technisches Thema oder einen Teil meiner Serie über technische Führung behandelt

Folgen Sie mir hier auf Twitter !

 

Verfasst von

Esteban Garcia

Managing Director at Xebia Microsoft Services US and a recognized expert in DevOps, GitHub Advanced Security, and GitHub Copilot. As a Microsoft Regional Director (RD) and Most Valuable Professional (MVP), he leads his team in adopting cutting-edge Microsoft technologies to enhance client services across various industries. Esteemed for his contributions to the tech community, Esteban is a prominent speaker and advocate for innovative software development and security solutions.

Contact

Let’s discuss how we can support your journey.