Skip to content

Commit

Permalink
impl(batch/parallel): add parallel finsim example (#332)
Browse files Browse the repository at this point in the history
* impl(batch/parallel): add parallel finsim example

* address comments

* run checkers

* revert checkers mistake
  • Loading branch information
alevenberg committed May 22, 2024
1 parent 07b0dff commit 00b154d
Show file tree
Hide file tree
Showing 10 changed files with 392 additions and 3 deletions.
13 changes: 11 additions & 2 deletions .clang-format
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,16 @@ IncludeCategories:
Priority: 2000
- Regex: '^<google/'
Priority: 3000
- Regex: '^<[^>]*/'
- Regex: '^<absl/'
Priority: 4000
- Regex: '^<[^/>]*>'
- Regex: '^<.*\.h>'
Priority: 5000
- Regex: '^<.*\.hpp>'
Priority: 5000
# Put the regex for "system" headers first, they need to go to the bottom.
- Regex: '^<sys/[A-Za-z0-9_]*\.h>$'
Priority: 10000
- Regex: '^<[A-Za-z0-9_]*\.h>$'
Priority: 10000
- Regex: '^<[^/\.]*>'
Priority: 6000
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ include(ExternalProject)
set(samples
# cmake-format: sort
batch/cpp_application
batch/parallel/application
batch/simple
bigquery/write
cloud-run-hello-world
Expand Down
111 changes: 111 additions & 0 deletions batch/parallel/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Using Cloud Batch

This example shows how to take an
[embarrasingly parallel](https://en.wikipedia.org/wiki/Embarrassingly_parallel)
job and run it on Cloud Batch job using C++.

If you are not familiar with the Batch API, we recommend you first read the
[API overview] before starting this guide.

## The example

The following steps are included:

1. Create a docker image
1. Upload it to Artifact registry
1. Create the Cloud Batch job

## Pre-reqs

1. Install the [gcloud CLI](https://cloud.google.com/sdk/docs/install).
1. Install [docker](https://docs.docker.com/engine/install/).

## 1. Create the docker image

The instructions are [here](application/README.md).

## 2. Upload it to Artifact registry

1. \[If it does not already exist\] Create the artifact repository
1. Build the image locally
1. Tag and push the image to the artifact repository

### 1. Create the artifact repository

To run this example, replace the `[PROJECT ID]` placeholder with the id of your
project:

Authorize via gcloud cli

```shell
PROJECT_ID=[PROJECT_ID]
LOCATION="us-central1"
REPOSITORY="parallel-repo"

gcloud auth login
gcloud config set project ${PROJECT_ID}
# Create the repository
gcloud artifacts repositories create ${REPOSITORY} \
--repository-format=docker \
--location=${LOCATION} \
--description="Store the example parallel C++ application" \
--async
```

<details>
<summary>To verify repo was created</summary>
```
gcloud artifacts repositories list
```

You should see something like

```
parallel-repo DOCKER STANDARD_REPOSITORY Store the example parallel C++ application us-central1 Google-managed key 2024-05-21T12:39:54 2024-05-21T12:39:54 0
```

</details>

### 2. Build the image locally

```
cd batch/parallel/application
docker build --tag=fimsim-image:latest .
```

### 3. Tag and push the image to the artifact repository

```
cd batch/parallel/application
gcloud builds submit --region=${LOCATION} --tag ${LOCATION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY}/finsim-image:latest
```

## 3. Create the job using the gcloud CLI

1. Replace the `imageURI` field in application.json

```
"runnables": [
{
"container": {
"imageUri": "${LOCATION_ID}-docker.pkg.dev/${PROJECT_ID}/{REPOSITORY}/finsim-image:latest",
}
}
],
```

2. Submit the job

```
gcloud batch jobs submit cpp-finsim-cli-run \
--config=finsim.json \
--location=us-central1
```

3. Check on the job status

```
gcloud batch jobs describe cpp-finsim-cli-run --location=us-central1
```

[api overview]: https://cloud.google.com/batch/docs
24 changes: 24 additions & 0 deletions batch/parallel/application/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# ~~~
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ~~~

cmake_minimum_required(VERSION 3.20)

# Define the project name and where to report bugs.
set(PACKAGE_BUGREPORT
"https://github.com/GoogleCloudPlatform/cpp-samples/issues")
project(cpp-samples-batch CXX)

add_executable(finsim src/finsim.cc)
52 changes: 52 additions & 0 deletions batch/parallel/application/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# We chose Alpine to build the image because it has good support for creating
# statically-linked, small programs.
FROM alpine:3.19 AS build

# Install the typical development tools for C++, and
# the base OS headers and libraries.
RUN apk update && \
apk add \
build-base \
cmake \
curl \
git \
gcc \
g++ \
libc-dev \
linux-headers \
ninja \
pkgconfig \
tar \
unzip \
zip

# Copy the source code to /src and compile it.
COPY . /src
WORKDIR /src

# Run the CMake configuration step, setting the options to create
# a statically linked C++ program
RUN cmake -S /src -B /build -GNinja \
-DCMAKE_BUILD_TYPE=Release

# Compile the binary and strip it to reduce its size.
RUN cmake --build /build
RUN strip /build/finsim

# Make the program the entry point.
ENTRYPOINT [ "/build/finsim" ]
CMD [ "input.txt"]
27 changes: 27 additions & 0 deletions batch/parallel/application/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
## The program

This example runs a Monte Carlo financial simulation that approximates the
future value of a stock price given the inputs.

# To build and run

```
cd cpp-samples/batch/parallel/application
cmake -S . -B build
cmake --build build
build/finsim input.txt
```

# To create docker image

```
docker build --tag=finsim-image:latest .
```

## To run and enter your image

```
docker run -it --entrypoint bash finsim-image:latest
```

To exit container, type `exit`
6 changes: 6 additions & 0 deletions batch/parallel/application/input.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
1000
1000
GOOG
173.6
.04
.08
123 changes: 123 additions & 0 deletions batch/parallel/application/src/finsim.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <omp.h>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <random>
#include <string>
#include <thread>
#include <vector>

struct InputConfig {
// Total simulations (e.g. 10 simulations).
std::int64_t simulations;
// Total iterations per simulation in days (e.g. 100 days).
std::int64_t iterations;
// Stock ticker name (e.g. GOOG).
std::string ticker;
// Initial stock price (e.g. $100).
double price;
// Daily percent variance (e.g. 5%)
double variance;
// Standard deviation in variance (e.g. 5% +/- 1%).
double deviation;
// Total threads used (max available using hardware).
std::int64_t total_threads;
};

void print_input_config(InputConfig const& config) {
std::cout << std::fixed << std::setprecision(2);

std::cout << "\nInput Configuration\n";
std::cout << "---------------------\n";
std::cout << "Simulations: " << config.simulations << "\n";
std::cout << "Iterations: " << config.iterations << "\n";
std::cout << "Ticker: " << config.ticker << "\n";
std::cout << "Price: $" << config.price << "\n";
std::cout << "Variance: " << config.variance * 100.0 << " +/- "
<< config.deviation * 100.0 << "%\n";
std::cout << "Threads: " << config.total_threads << "\n";
}

double simulate(InputConfig const& config) {
double updated_price = config.price;
thread_local static std::mt19937 generator(std::random_device{}());
std::uniform_real_distribution<> dis(-1 * config.deviation, config.deviation);

for (int j = 0; j < config.iterations; ++j) {
updated_price =
updated_price * (1 + ((config.variance + dis(generator)) * 0.01));
}
return updated_price;
}

int main(int argc, char* argv[]) {
if (argc < 2) {
std::cerr << "usage: finsim <input>\n";
return 1;
}

std::string input_filename = std::string(argv[1]);

std::ifstream input_file(input_filename);
std::vector<std::string> lines;
if (!input_file.is_open()) {
std::cout << "Couldn't open file\n";
return 1;
}
for (std::string line; std::getline(input_file, line);) {
lines.push_back(std::move(line));
}

if (lines.size() != 6) {
std::cout << "Input is not the expected length\n";
return 1;
}

InputConfig input_config;
input_config.simulations = std::stoi(lines.at(0));
input_config.iterations = std::stoi(lines.at(1));
input_config.ticker = lines.at(2);
input_config.price = std::stof(lines.at(3));
input_config.variance = std::stof(lines.at(4));
input_config.deviation = std::stof(lines.at(5));

input_config.total_threads =
static_cast<std::int64_t>(std::thread::hardware_concurrency());

print_input_config(input_config);

std::vector<double> results(input_config.simulations);
simulate(input_config);

#pragma omp parallel for
for (int i = 0; i < input_config.simulations; i++) {
results[i] = simulate(input_config);
}

double sum = 0.0;
#pragma omp parallel for reduction(+ : sum)
for (int i = 0; i < input_config.simulations; i++) {
sum += results[i];
}
double mean = sum / input_config.simulations;

std::cout << "\nOutput\n";
std::cout << "---------------------\n";
std::cout << "Mean final price: " << mean << "\n";

return 0;
}
Loading

0 comments on commit 00b154d

Please sign in to comment.