"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "xHxb-dlhMIzW"
},
"source": [
"## Overview\n",
"\n",
"GPUs and TPUs can radically reduce the time required to execute a single training step.\n",
"Achieving peak performance requires an efficient input pipeline that delivers data for the next step before the current step has finished.\n",
"The `tf.data` API helps to build flexible and efficient input pipelines.\n",
"This document demonstrates how to use the `tf.data` API to build highly performant TensorFlow input pipelines.\n",
"\n",
"Before you continue, check the [Build TensorFlow input pipelines](./data.ipynb) guide to learn how to use the `tf.data` API."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "UhNtHfuxCGVy"
},
"source": [
"## Resources\n",
"\n",
"* [Build TensorFlow input pipelines](./data.ipynb)\n",
"* `tf.data.Dataset` API\n",
"* [Analyze `tf.data` performance with the TF Profiler](./data_performance_analysis.md)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "MUXex9ctTuDB"
},
"source": [
"## Setup"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"execution": {
"iopub.execute_input": "2024-08-15T02:29:59.608267Z",
"iopub.status.busy": "2024-08-15T02:29:59.607850Z",
"iopub.status.idle": "2024-08-15T02:30:01.973658Z",
"shell.execute_reply": "2024-08-15T02:30:01.972911Z"
},
"id": "IqR2PQG4ZaZ0"
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"2024-08-15 02:29:59.860833: E external/local_xla/xla/stream_executor/cuda/cuda_fft.cc:485] Unable to register cuFFT factory: Attempting to register factory for plugin cuFFT when one has already been registered\n",
"2024-08-15 02:29:59.882136: E external/local_xla/xla/stream_executor/cuda/cuda_dnn.cc:8454] Unable to register cuDNN factory: Attempting to register factory for plugin cuDNN when one has already been registered\n",
"2024-08-15 02:29:59.888640: E external/local_xla/xla/stream_executor/cuda/cuda_blas.cc:1452] Unable to register cuBLAS factory: Attempting to register factory for plugin cuBLAS when one has already been registered\n"
]
}
],
"source": [
"import tensorflow as tf\n",
"\n",
"import time"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "QthTHCKF-jKD"
},
"source": [
"Throughout this guide, you will iterate across a dataset and measure the performance.\n",
"Making reproducible performance benchmarks can be difficult. Different factors affecting reproducibility include:\n",
"\n",
"- The current CPU load\n",
"- The network traffic\n",
"- Complex mechanisms, such as cache\n",
"\n",
"To get a reproducible benchmark, you will build an artificial example."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "3bU5gsSI-jKF"
},
"source": [
"### The dataset\n",
"\n",
"Start with defining a class inheriting from `tf.data.Dataset` called `ArtificialDataset`.\n",
"This dataset:\n",
"\n",
"- Generates `num_samples` samples (default is 3)\n",
"- Sleeps for some time before the first item to simulate opening a file\n",
"- Sleeps for some time before producing each item to simulate reading data from a file"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"execution": {
"iopub.execute_input": "2024-08-15T02:30:01.977718Z",
"iopub.status.busy": "2024-08-15T02:30:01.977354Z",
"iopub.status.idle": "2024-08-15T02:30:01.982291Z",
"shell.execute_reply": "2024-08-15T02:30:01.981698Z"
},
"id": "zUQv4kCd-jKH"
},
"outputs": [],
"source": [
"class ArtificialDataset(tf.data.Dataset):\n",
" def _generator(num_samples):\n",
" # Opening the file\n",
" time.sleep(0.03)\n",
" \n",
" for sample_idx in range(num_samples):\n",
" # Reading data (line, record) from the file\n",
" time.sleep(0.015)\n",
" \n",
" yield (sample_idx,)\n",
" \n",
" def __new__(cls, num_samples=3):\n",
" return tf.data.Dataset.from_generator(\n",
" cls._generator,\n",
" output_signature = tf.TensorSpec(shape = (1,), dtype = tf.int64),\n",
" args=(num_samples,)\n",
" )"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "O9y1WjNv-jKL"
},
"source": [
"This dataset is similar to the `tf.data.Dataset.range` one, adding a fixed delay at the beginning of and in-between each sample."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "FGK1Y4jn-jKM"
},
"source": [
"### The training loop\n",
"\n",
"Next, write a dummy training loop that measures how long it takes to iterate over a dataset.\n",
"Training time is simulated."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"execution": {
"iopub.execute_input": "2024-08-15T02:30:01.985429Z",
"iopub.status.busy": "2024-08-15T02:30:01.985202Z",
"iopub.status.idle": "2024-08-15T02:30:01.988833Z",
"shell.execute_reply": "2024-08-15T02:30:01.988266Z"
},
"id": "MIaM3u00-jKP"
},
"outputs": [],
"source": [
"def benchmark(dataset, num_epochs=2):\n",
" start_time = time.perf_counter()\n",
" for epoch_num in range(num_epochs):\n",
" for sample in dataset:\n",
" # Performing a training step\n",
" time.sleep(0.01)\n",
" print(\"Execution time:\", time.perf_counter() - start_time)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "KK58SuXS-jKT"
},
"source": [
"## Optimize performance\n",
"\n",
"To exhibit how performance can be optimized, you will improve the performance of the `ArtificialDataset`."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "Xi8t26y7-jKV"
},
"source": [
"### The naive approach\n",
"\n",
"Start with a naive pipeline using no tricks, iterating over the dataset as-is."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"execution": {
"iopub.execute_input": "2024-08-15T02:30:01.992175Z",
"iopub.status.busy": "2024-08-15T02:30:01.991628Z",
"iopub.status.idle": "2024-08-15T02:30:04.538180Z",
"shell.execute_reply": "2024-08-15T02:30:04.537460Z"
},
"id": "_gP7J1y4-jKY"
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"WARNING: All log messages before absl::InitializeLog() is called are written to STDERR\n",
"I0000 00:00:1723689002.526086 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689002.529717 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689002.533395 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689002.537215 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689002.548874 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689002.552105 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689002.555507 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689002.559016 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See mo"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"re at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689002.562268 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689002.565421 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689002.568842 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689002.572296 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689003.797478 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689003.799457 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689003.801473 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689003.803565 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689003.805613 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689003.807466 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689003.809382 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689003.811373 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689003.813299 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689003.815122 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689003.817022 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689003.819005 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689003.857728 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689003.860099 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689003.862063 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689003.864094 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689003.866073 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689003.867912 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689003.869940 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689003.871942 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689003.873924 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689003.876256 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689003.878556 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n",
"I0000 00:00:1723689003.880974 112933 cuda_executor.cc:1015] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero. See more at https://github.com/torvalds/linux/blob/v6.0/Documentation/ABI/testing/sysfs-bus-pci#L344-L355\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Execution time: 0.28992818999995507\n"
]
}
],
"source": [
"benchmark(ArtificialDataset())"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "Lxeat5dH-jKf"
},
"source": [
"Under the hood, this is how your execution time was spent:\n",
"\n",
"\n",
"\n",
"The plot shows that performing a training step involves:\n",
"\n",
"- Opening a file if it hasn't been opened yet\n",
"- Fetching a data entry from the file\n",
"- Using the data for training\n",
"\n",
"However, in a naive synchronous implementation like here, while your pipeline is fetching the data, your model is sitting idle. \n",
"Conversely, while your model is training, the input pipeline is sitting idle.\n",
"The training step time is thus the sum of opening, reading and training times.\n",
"\n",
"The next sections build on this input pipeline, illustrating best practices for designing performant TensorFlow input pipelines."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "mfukBGNz-jKh"
},
"source": [
"### Prefetching\n",
"\n",
"\n",
"\n",
"Prefetching overlaps the preprocessing and model execution of a training step.\n",
"While the model is executing training step `s`, the input pipeline is reading the data for step `s+1`.\n",
"Doing so reduces the step time to the maximum (as opposed to the sum) of the training and the time it takes to extract the data.\n",
"\n",
"The `tf.data` API provides the `tf.data.Dataset.prefetch` transformation.\n",
"It can be used to decouple the time when data is produced from the time when data is consumed.\n",
"In particular, the transformation uses a background thread and an internal buffer to prefetch elements from the input dataset ahead of the time they are requested.\n",
"The number of elements to prefetch should be equal to (or possibly greater than) the number of batches consumed by a single training step.\n",
"You could either manually tune this value, or set it to `tf.data.AUTOTUNE`, which will prompt the\n",
"`tf.data` runtime to tune the value dynamically at runtime.\n",
"\n",
"Note that the prefetch transformation provides benefits any time there is an opportunity to overlap the work of a \"producer\" with the work of a \"consumer.\""
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"execution": {
"iopub.execute_input": "2024-08-15T02:30:04.542348Z",
"iopub.status.busy": "2024-08-15T02:30:04.541704Z",
"iopub.status.idle": "2024-08-15T02:30:04.856786Z",
"shell.execute_reply": "2024-08-15T02:30:04.856143Z"
},
"id": "DHpUVqH1-jKi"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Execution time: 0.282483695999872\n"
]
}
],
"source": [
"benchmark(\n",
" ArtificialDataset()\n",
" .prefetch(tf.data.AUTOTUNE)\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "h7z_kzo--jKn"
},
"source": [
"\n",
"\n",
"Now, as the data execution time plot shows, while the training step is running for sample 0, the input pipeline is reading the data for the sample 1, and so on."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "52QMKfaY-jKq"
},
"source": [
"### Parallelizing data extraction\n",
"\n",
"\n",
"\n",
"In a real-world setting, the input data may be stored remotely (for example, on Google Cloud Storage or HDFS).\n",
"A dataset pipeline that works well when reading data locally might become bottlenecked on I/O when reading data remotely because of the following differences between local and remote storage:\n",
"\n",
"- **Time-to-first-byte**: Reading the first byte of a file from remote storage can take orders of magnitude longer than from local storage.\n",
"- **Read throughput**: While remote storage typically offers large aggregate bandwidth, reading a single file might only be able to utilize a small fraction of this bandwidth.\n",
"\n",
"In addition, once the raw bytes are loaded into memory, it may also be necessary to deserialize and/or decrypt the data (e.g. [protobuf](https://developers.google.com/protocol-buffers/)), which requires additional computation.\n",
"This overhead is present irrespective of whether the data is stored locally or remotely, but can be worse in the remote case if data is not prefetched effectively.\n",
"\n",
"To mitigate the impact of the various data extraction overheads, the `tf.data.Dataset.interleave` transformation can be used to parallelize the data loading step, interleaving the contents of other datasets (such as data file\n",
"readers).\n",
"The number of datasets to overlap can be specified by the `cycle_length` argument, while the level of parallelism can be specified by the `num_parallel_calls` argument. Similar to the `prefetch` transformation, the `interleave` transformation supports `tf.data.AUTOTUNE`, which will delegate the decision about what level of parallelism to use to the `tf.data` runtime."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "gs8O8Vbu-jKu"
},
"source": [
"#### Sequential interleave\n",
"\n",
"The default arguments of the `tf.data.Dataset.interleave` transformation make it interleave single samples from two datasets sequentially."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"execution": {
"iopub.execute_input": "2024-08-15T02:30:04.860656Z",
"iopub.status.busy": "2024-08-15T02:30:04.860246Z",
"iopub.status.idle": "2024-08-15T02:30:05.380865Z",
"shell.execute_reply": "2024-08-15T02:30:05.380192Z"
},
"id": "fDH12GiK-jKw"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Execution time: 0.4770545540004605\n"
]
}
],
"source": [
"benchmark(\n",
" tf.data.Dataset.range(2)\n",
" .interleave(lambda _: ArtificialDataset())\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "78CsSOnf-jK0"
},
"source": [
"\n",
"\n",
"This data execution time plot allows to exhibit the behavior of the `interleave` transformation, fetching samples alternatively from the two datasets available.\n",
"However, no performance improvement is involved here."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "j3cqqmYl-jK2"
},
"source": [
"#### Parallel interleave\n",
"\n",
"Now, use the `num_parallel_calls` argument of the `interleave` transformation.\n",
"This loads multiple datasets in parallel, reducing the time waiting for the files to be opened."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"execution": {
"iopub.execute_input": "2024-08-15T02:30:05.384554Z",
"iopub.status.busy": "2024-08-15T02:30:05.384264Z",
"iopub.status.idle": "2024-08-15T02:30:05.805199Z",
"shell.execute_reply": "2024-08-15T02:30:05.804480Z"
},
"id": "a3FQcTPY-jK4"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Execution time: 0.37743708600009995\n"
]
}
],
"source": [
"benchmark(\n",
" tf.data.Dataset.range(2)\n",
" .interleave(\n",
" lambda _: ArtificialDataset(),\n",
" num_parallel_calls=tf.data.AUTOTUNE\n",
" )\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "RxRLPB6C-jLA"
},
"source": [
"\n",
"\n",
"This time, as the data execution time plot shows, the reading of the two datasets is parallelized, reducing the global data processing time."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "5ZCLFWyv-jLB"
},
"source": [
"### Parallelizing data transformation\n",
"\n",
"\n",
"\n",
"When preparing data, input elements may need to be pre-processed.\n",
"To this end, the `tf.data` API offers the `tf.data.Dataset.map` transformation, which applies a user-defined function to each element of the input dataset.\n",
"Because input elements are independent of one another, the pre-processing can be parallelized across multiple CPU cores.\n",
"To make this possible, similarly to the `prefetch` and `interleave` transformations, the `map` transformation provides the `num_parallel_calls` argument to specify the level of parallelism.\n",
"\n",
"Choosing the best value for the `num_parallel_calls` argument depends on your hardware, characteristics of your training data (such as its size and shape), the cost of your map function, and what other processing is happening on the CPU at the same time.\n",
"A simple heuristic is to use the number of available CPU cores.\n",
"However, as for the `prefetch` and `interleave` transformation, the `map` transformation supports `tf.data.AUTOTUNE` which will delegate the decision about what level of parallelism to use to the `tf.data` runtime."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"execution": {
"iopub.execute_input": "2024-08-15T02:30:05.809046Z",
"iopub.status.busy": "2024-08-15T02:30:05.808779Z",
"iopub.status.idle": "2024-08-15T02:30:05.812585Z",
"shell.execute_reply": "2024-08-15T02:30:05.811987Z"
},
"id": "GSkKetpx-jLD"
},
"outputs": [],
"source": [
"def mapped_function(s):\n",
" # Do some hard pre-processing\n",
" tf.py_function(lambda: time.sleep(0.03), [], ())\n",
" return s"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "wiU7W_QC-jLI"
},
"source": [
"#### Sequential mapping\n",
"\n",
"Start by using the `map` transformation without parallelism as a baseline example."
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"execution": {
"iopub.execute_input": "2024-08-15T02:30:05.816153Z",
"iopub.status.busy": "2024-08-15T02:30:05.815573Z",
"iopub.status.idle": "2024-08-15T02:30:06.364742Z",
"shell.execute_reply": "2024-08-15T02:30:06.363940Z"
},
"id": "ZSBvDpJG-jLL"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Execution time: 0.49222556600034295\n"
]
}
],
"source": [
"benchmark(\n",
" ArtificialDataset()\n",
" .map(mapped_function)\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "ngwMTDb6-jLR"
},
"source": [
"\n",
"\n",
"As for the [naive approach](#The-naive-approach), here, as the plot shows, the times spent for opening, reading, pre-processing (mapping) and training steps sum together for a single iteration."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "U-10PE1D-jLU"
},
"source": [
"#### Parallel mapping\n",
"\n",
"Now, use the same pre-processing function but apply it in parallel on multiple samples."
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"execution": {
"iopub.execute_input": "2024-08-15T02:30:06.368778Z",
"iopub.status.busy": "2024-08-15T02:30:06.368286Z",
"iopub.status.idle": "2024-08-15T02:30:06.765549Z",
"shell.execute_reply": "2024-08-15T02:30:06.764838Z"
},
"id": "F8AYLZbg-jLV"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Execution time: 0.36392719900049997\n"
]
}
],
"source": [
"benchmark(\n",
" ArtificialDataset()\n",
" .map(\n",
" mapped_function,\n",
" num_parallel_calls=tf.data.AUTOTUNE\n",
" )\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "-MoJklzP-jLe"
},
"source": [
"\n",
"\n",
"As the data plot demonstrates, the pre-processing steps overlap, reducing the overall time for a single iteration."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "ZY1Q9kJO-jLh"
},
"source": [
"### Caching\n",
"\n",
"\n",
"\n",
"The `tf.data.Dataset.cache` transformation can cache a dataset, either in memory or on local storage.\n",
"This will save some operations (like file opening and data reading) from being executed during each epoch."
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"execution": {
"iopub.execute_input": "2024-08-15T02:30:06.769615Z",
"iopub.status.busy": "2024-08-15T02:30:06.768968Z",
"iopub.status.idle": "2024-08-15T02:30:07.190903Z",
"shell.execute_reply": "2024-08-15T02:30:07.190097Z"
},
"id": "xieLApaI-jLi"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Execution time: 0.3864543270001377\n"
]
}
],
"source": [
"benchmark(\n",
" ArtificialDataset()\n",
" .map( # Apply time consuming operations before cache\n",
" mapped_function\n",
" ).cache(\n",
" ),\n",
" 5\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "KeMgW9XI-jLn"
},
"source": [
"\n",
"\n",
"Here, the data execution time plot shows that when you cache a dataset, the transformations before the `cache` one (like the file opening and data reading) are executed only during the first epoch.\n",
"The next epochs will reuse the data cached by the`cache` transformation.\n",
"\n",
"If the user-defined function passed into the `map` transformation is expensive, apply the `cache` transformation after the `map` transformation as long as the resulting dataset can still fit into memory or local storage.\n",
"If the user-defined function increases the space required to store the dataset beyond the cache capacity, either apply it after the `cache` transformation or consider pre-processing your data before your training job to reduce resource usage."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "i3NtGI3r-jLp"
},
"source": [
"### Vectorizing mapping\n",
"\n",
"\n",
"\n",
"Invoking a user-defined function passed into the `map` transformation has overhead related to scheduling and executing the user-defined function.\n",
"Vectorize the user-defined function (that is, have it operate over a batch of inputs at once) and apply the `batch` transformation _before_ the `map` transformation.\n",
"\n",
"To illustrate this good practice, your artificial dataset is not suitable.\n",
"The scheduling delay is around 10 microseconds (10e-6 seconds), far less than the tens of milliseconds used in the `ArtificialDataset`, and thus its impact is hard to see.\n",
"\n",
"For this example, use the base `tf.data.Dataset.range` function and simplify the training loop to its simplest form."
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"execution": {
"iopub.execute_input": "2024-08-15T02:30:07.194856Z",
"iopub.status.busy": "2024-08-15T02:30:07.194224Z",
"iopub.status.idle": "2024-08-15T02:30:07.200596Z",
"shell.execute_reply": "2024-08-15T02:30:07.199991Z"
},
"id": "xqtiYPmb-jLt"
},
"outputs": [],
"source": [
"fast_dataset = tf.data.Dataset.range(10000)\n",
"\n",
"def fast_benchmark(dataset, num_epochs=2):\n",
" start_time = time.perf_counter()\n",
" for _ in tf.data.Dataset.range(num_epochs):\n",
" for _ in dataset:\n",
" pass\n",
" tf.print(\"Execution time:\", time.perf_counter() - start_time)\n",
" \n",
"def increment(x):\n",
" return x+1"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "Fj2gmsMT-jL5"
},
"source": [
"#### Scalar mapping"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"execution": {
"iopub.execute_input": "2024-08-15T02:30:07.204115Z",
"iopub.status.busy": "2024-08-15T02:30:07.203558Z",
"iopub.status.idle": "2024-08-15T02:30:07.475971Z",
"shell.execute_reply": "2024-08-15T02:30:07.475223Z"
},
"id": "Imn3SslJ-jMA"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Execution time: 0.23993100699954084\n"
]
}
],
"source": [
"fast_benchmark(\n",
" fast_dataset\n",
" # Apply function one item at a time\n",
" .map(increment)\n",
" # Batch\n",
" .batch(256)\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "BWUNbPqv-jMF"
},
"source": [
"\n",
"\n",
"The plot above illustrates what is going on (with less samples) using the scalar mapping method.\n",
"It shows that the mapped function is applied for each sample.\n",
"While this function is very fast, it has some overhead that impact the time performance."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "tDVSM0A--jMG"
},
"source": [
"#### Vectorized mapping"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"execution": {
"iopub.execute_input": "2024-08-15T02:30:07.479399Z",
"iopub.status.busy": "2024-08-15T02:30:07.479112Z",
"iopub.status.idle": "2024-08-15T02:30:07.541914Z",
"shell.execute_reply": "2024-08-15T02:30:07.541303Z"
},
"id": "nAw1mDLw-jMI"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Execution time: 0.04984995199993136\n"
]
}
],
"source": [
"fast_benchmark(\n",
" fast_dataset\n",
" .batch(256)\n",
" # Apply function on a batch of items\n",
" # The tf.Tensor.__add__ method already handle batches\n",
" .map(increment)\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "DbMteMY9-jMO"
},
"source": [
"\n",
"\n",
"This time, the mapped function is called once and applies to a batch of sample.\n",
"As the data execution time plot shows, while the function could takes more time to execute, the overhead appear only once, improving the overall time performance."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "hfueG0Wj-jMR"
},
"source": [
"### Reducing memory footprint\n",
"\n",
"\n",
"\n",
"A number of transformations, including `interleave`, `prefetch`, and `shuffle`, maintain an internal buffer of elements. If the user-defined function passed into the `map` transformation changes the size of the elements, then the ordering of the map transformation and the transformations that buffer elements affects the memory usage. In general, choose the order that results in lower memory footprint, unless different ordering is desirable for performance.\n",
"\n",
"#### Caching partial computations\n",
"\n",
"It is recommended to cache the dataset after the `map` transformation except if this transformation makes the data too big to fit in memory.\n",
"A trade-off can be achieved if your mapped function can be split in two parts: a time consuming one and a memory consuming part.\n",
"In this case, you can chain your transformations like below:\n",
"\n",
"```python\n",
"dataset.map(time_consuming_mapping).cache().map(memory_consuming_mapping)\n",
"```\n",
"\n",
"This way, the time consuming part is only executed during the first epoch, and you avoid using too much cache space."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "MYOHG69M-jMT"
},
"source": [
"## Best practice summary\n",
"\n",
"Here is a summary of the best practices for designing performant TensorFlow\n",
"input pipelines:\n",
"\n",
"* [Use the `prefetch` transformation](#prefetching) to overlap the work of a producer and consumer\n",
"* [Parallelize the data reading transformation](#parallelizing_data_extraction) using the `interleave` transformation\n",
"* [Parallelize the `map` transformation](#parallelizing_data_transformation) by setting the `num_parallel_calls` argument\n",
"* [Use the `cache` transformation](#caching) to cache data in memory during the first epoch\n",
"* [Vectorize user-defined functions](#vectorizing_mapping) passed in to the `map` transformation\n",
"* [Reduce memory usage](#reducing_memory_footprint) when applying the `interleave`, `prefetch`, and `shuffle` transformations"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "mP_EMFsQ-jMU"
},
"source": [
"## Reproducing the figures\n",
"\n",
"Note: The rest of this notebook is about how to reproduce the above figures. Feel free to play around with this code, but understanding it is not an essential part of this tutorial.\n",
"\n",
"To go deeper in the `tf.data.Dataset` API understanding, you can play with your own pipelines.\n",
"Below is the code used to plot the images from this guide.\n",
"It can be a good starting point, showing some workarounds for common difficulties such as:\n",
"\n",
"- Execution time reproducibility\n",
"- Mapped functions eager execution\n",
"- `interleave` transformation callable"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {
"execution": {
"iopub.execute_input": "2024-08-15T02:30:07.546005Z",
"iopub.status.busy": "2024-08-15T02:30:07.545474Z",
"iopub.status.idle": "2024-08-15T02:30:07.823712Z",
"shell.execute_reply": "2024-08-15T02:30:07.823057Z"
},
"id": "7M_jFLer-jMV"
},
"outputs": [],
"source": [
"import itertools\n",
"from collections import defaultdict\n",
"\n",
"import numpy as np\n",
"import matplotlib as mpl\n",
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "Z3pjnxtK-jMa"
},
"source": [
"### The dataset\n",
"\n",
"Similar to the `ArtificialDataset` you can build a dataset returning the time spent in each step."
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {
"execution": {
"iopub.execute_input": "2024-08-15T02:30:07.827441Z",
"iopub.status.busy": "2024-08-15T02:30:07.827202Z",
"iopub.status.idle": "2024-08-15T02:30:07.834182Z",
"shell.execute_reply": "2024-08-15T02:30:07.833596Z"
},
"id": "OgGl4U7t-jMc"
},
"outputs": [],
"source": [
"class TimeMeasuredDataset(tf.data.Dataset):\n",
" # OUTPUT: (steps, timings, counters)\n",
" OUTPUT_TYPES = (tf.dtypes.string, tf.dtypes.float32, tf.dtypes.int32)\n",
" OUTPUT_SHAPES = ((2, 1), (2, 2), (2, 3))\n",
" \n",
" _INSTANCES_COUNTER = itertools.count() # Number of datasets generated\n",
" _EPOCHS_COUNTER = defaultdict(itertools.count) # Number of epochs done for each dataset\n",
" \n",
" def _generator(instance_idx, num_samples):\n",
" epoch_idx = next(TimeMeasuredDataset._EPOCHS_COUNTER[instance_idx])\n",
" \n",
" # Opening the file\n",
" open_enter = time.perf_counter()\n",
" time.sleep(0.03)\n",
" open_elapsed = time.perf_counter() - open_enter\n",
" \n",
" for sample_idx in range(num_samples):\n",
" # Reading data (line, record) from the file\n",
" read_enter = time.perf_counter()\n",
" time.sleep(0.015)\n",
" read_elapsed = time.perf_counter() - read_enter\n",
" \n",
" yield (\n",
" [(\"Open\",), (\"Read\",)],\n",
" [(open_enter, open_elapsed), (read_enter, read_elapsed)],\n",
" [(instance_idx, epoch_idx, -1), (instance_idx, epoch_idx, sample_idx)]\n",
" )\n",
" open_enter, open_elapsed = -1., -1. # Negative values will be filtered\n",
" \n",
" \n",
" def __new__(cls, num_samples=3):\n",
" return tf.data.Dataset.from_generator(\n",
" cls._generator,\n",
" output_types=cls.OUTPUT_TYPES,\n",
" output_shapes=cls.OUTPUT_SHAPES,\n",
" args=(next(cls._INSTANCES_COUNTER), num_samples)\n",
" )"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "YQqDP4jk-jMj"
},
"source": [
"This dataset provides samples of shape `[[2, 1], [2, 2], [2, 3]]` and of type `[tf.dtypes.string, tf.dtypes.float32, tf.dtypes.int32]`.\n",
"Each sample is:\n",
"```\n",
"(\n",
" [(\"Open\"), (\"Read\")],\n",
" [(t0, d), (t0, d)],\n",
" [(i, e, -1), (i, e, s)]\n",
")\n",
"```\n",
"\n",
"Where:\n",
"\n",
"- `Open` and `Read` are steps identifiers\n",
"- `t0` is the timestamp when the corresponding step started\n",
"- `d` is the time spent in the corresponding step\n",
"- `i` is the instance index\n",
"- `e` is the epoch index (number of times the dataset has been iterated)\n",
"- `s` is the sample index"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "IQK913bB-jMm"
},
"source": [
"### The iteration loop\n",
"\n",
"Make the iteration loop a little bit more complicated to aggregate all timings.\n",
"This will only work with datasets generating samples as detailed above."
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {
"execution": {
"iopub.execute_input": "2024-08-15T02:30:07.837625Z",
"iopub.status.busy": "2024-08-15T02:30:07.837360Z",
"iopub.status.idle": "2024-08-15T02:30:07.845075Z",
"shell.execute_reply": "2024-08-15T02:30:07.844437Z"
},
"id": "zAy-K_Cq-jMn"
},
"outputs": [],
"source": [
"def timelined_benchmark(dataset, num_epochs=2):\n",
" # Initialize accumulators\n",
" steps_acc = tf.zeros([0, 1], dtype=tf.dtypes.string)\n",
" times_acc = tf.zeros([0, 2], dtype=tf.dtypes.float32)\n",
" values_acc = tf.zeros([0, 3], dtype=tf.dtypes.int32)\n",
" \n",
" start_time = time.perf_counter()\n",
" for epoch_num in range(num_epochs):\n",
" epoch_enter = time.perf_counter()\n",
" for (steps, times, values) in dataset:\n",
" # Record dataset preparation informations\n",
" steps_acc = tf.concat((steps_acc, steps), axis=0)\n",
" times_acc = tf.concat((times_acc, times), axis=0)\n",
" values_acc = tf.concat((values_acc, values), axis=0)\n",
" \n",
" # Simulate training time\n",
" train_enter = time.perf_counter()\n",
" time.sleep(0.01)\n",
" train_elapsed = time.perf_counter() - train_enter\n",
" \n",
" # Record training informations\n",
" steps_acc = tf.concat((steps_acc, [[\"Train\"]]), axis=0)\n",
" times_acc = tf.concat((times_acc, [(train_enter, train_elapsed)]), axis=0)\n",
" values_acc = tf.concat((values_acc, [values[-1]]), axis=0)\n",
" \n",
" epoch_elapsed = time.perf_counter() - epoch_enter\n",
" # Record epoch informations\n",
" steps_acc = tf.concat((steps_acc, [[\"Epoch\"]]), axis=0)\n",
" times_acc = tf.concat((times_acc, [(epoch_enter, epoch_elapsed)]), axis=0)\n",
" values_acc = tf.concat((values_acc, [[-1, epoch_num, -1]]), axis=0)\n",
" time.sleep(0.001)\n",
" \n",
" tf.print(\"Execution time:\", time.perf_counter() - start_time)\n",
" return {\"steps\": steps_acc, \"times\": times_acc, \"values\": values_acc}"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "jw_WSQC8-jMs"
},
"source": [
"### The plotting method\n",
"\n",
"Finally, define a function able to plot a timeline given the values returned by the `timelined_benchmark` function."
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {
"execution": {
"iopub.execute_input": "2024-08-15T02:30:07.848514Z",
"iopub.status.busy": "2024-08-15T02:30:07.847974Z",
"iopub.status.idle": "2024-08-15T02:30:07.858065Z",
"shell.execute_reply": "2024-08-15T02:30:07.857473Z"
},
"id": "1j73RxiP-jMw"
},
"outputs": [],
"source": [
"def draw_timeline(timeline, title, width=0.5, annotate=False, save=False):\n",
" # Remove invalid entries (negative times, or empty steps) from the timelines\n",
" invalid_mask = np.logical_and(timeline['times'] > 0, timeline['steps'] != b'')[:,0]\n",
" steps = timeline['steps'][invalid_mask].numpy()\n",
" times = timeline['times'][invalid_mask].numpy()\n",
" values = timeline['values'][invalid_mask].numpy()\n",
" \n",
" # Get a set of different steps, ordered by the first time they are encountered\n",
" step_ids, indices = np.stack(np.unique(steps, return_index=True))\n",
" step_ids = step_ids[np.argsort(indices)]\n",
"\n",
" # Shift the starting time to 0 and compute the maximal time value\n",
" min_time = times[:,0].min()\n",
" times[:,0] = (times[:,0] - min_time)\n",
" end = max(width, (times[:,0]+times[:,1]).max() + 0.01)\n",
" \n",
" cmap = mpl.cm.get_cmap(\"plasma\")\n",
" plt.close()\n",
" fig, axs = plt.subplots(len(step_ids), sharex=True, gridspec_kw={'hspace': 0})\n",
" fig.suptitle(title)\n",
" fig.set_size_inches(17.0, len(step_ids))\n",
" plt.xlim(-0.01, end)\n",
" \n",
" for i, step in enumerate(step_ids):\n",
" step_name = step.decode()\n",
" ax = axs[i]\n",
" ax.set_ylabel(step_name)\n",
" ax.set_ylim(0, 1)\n",
" ax.set_yticks([])\n",
" ax.set_xlabel(\"time (s)\")\n",
" ax.set_xticklabels([])\n",
" ax.grid(which=\"both\", axis=\"x\", color=\"k\", linestyle=\":\")\n",
" \n",
" # Get timings and annotation for the given step\n",
" entries_mask = np.squeeze(steps==step)\n",
" serie = np.unique(times[entries_mask], axis=0)\n",
" annotations = values[entries_mask]\n",
" \n",
" ax.broken_barh(serie, (0, 1), color=cmap(i / len(step_ids)), linewidth=1, alpha=0.66)\n",
" if annotate:\n",
" for j, (start, width) in enumerate(serie):\n",
" annotation = \"\\n\".join([f\"{l}: {v}\" for l,v in zip((\"i\", \"e\", \"s\"), annotations[j])])\n",
" ax.text(start + 0.001 + (0.001 * (j % 2)), 0.55 - (0.1 * (j % 2)), annotation,\n",
" horizontalalignment='left', verticalalignment='center')\n",
" if save:\n",
" plt.savefig(title.lower().translate(str.maketrans(\" \", \"_\")) + \".svg\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "xto6GNdO-jM1"
},
"source": [
"### Use wrappers for mapped function\n",
"\n",
"To run mapped function in an eager context, you have to wrap them inside a `tf.py_function` call."
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {
"execution": {
"iopub.execute_input": "2024-08-15T02:30:07.861450Z",
"iopub.status.busy": "2024-08-15T02:30:07.860827Z",
"iopub.status.idle": "2024-08-15T02:30:07.864790Z",
"shell.execute_reply": "2024-08-15T02:30:07.864103Z"
},
"id": "39v7JD4L-jM2"
},
"outputs": [],
"source": [
"def map_decorator(func):\n",
" def wrapper(steps, times, values):\n",
" # Use a tf.py_function to prevent auto-graph from compiling the method\n",
" return tf.py_function(\n",
" func,\n",
" inp=(steps, times, values),\n",
" Tout=(steps.dtype, times.dtype, values.dtype)\n",
" )\n",
" return wrapper"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "7eJRCinb-jM5"
},
"source": [
"### Pipelines comparison"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {
"execution": {
"iopub.execute_input": "2024-08-15T02:30:07.868084Z",
"iopub.status.busy": "2024-08-15T02:30:07.867558Z",
"iopub.status.idle": "2024-08-15T02:30:07.870675Z",
"shell.execute_reply": "2024-08-15T02:30:07.870064Z"
},
"id": "YwX4ndHE-jM6"
},
"outputs": [],
"source": [
"_batch_map_num_items = 50\n",
"\n",
"def dataset_generator_fun(*args):\n",
" return TimeMeasuredDataset(num_samples=_batch_map_num_items)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "EwxJT2aR-jNA"
},
"source": [
"#### Naive"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {
"execution": {
"iopub.execute_input": "2024-08-15T02:30:07.873537Z",
"iopub.status.busy": "2024-08-15T02:30:07.873318Z",
"iopub.status.idle": "2024-08-15T02:30:21.186494Z",
"shell.execute_reply": "2024-08-15T02:30:21.185705Z"
},
"id": "wLKgurx_-jNC"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"WARNING:tensorflow:From /tmpfs/tmp/ipykernel_112933/64197174.py:32: calling DatasetV2.from_generator (from tensorflow.python.data.ops.dataset_ops) with output_types is deprecated and will be removed in a future version.\n",
"Instructions for updating:\n",
"Use output_signature instead\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"WARNING:tensorflow:From /tmpfs/tmp/ipykernel_112933/64197174.py:32: calling DatasetV2.from_generator (from tensorflow.python.data.ops.dataset_ops) with output_shapes is deprecated and will be removed in a future version.\n",
"Instructions for updating:\n",
"Use output_signature instead\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Execution time: 13.208576904999973\n"
]
}
],
"source": [
"@map_decorator\n",
"def naive_map(steps, times, values):\n",
" map_enter = time.perf_counter()\n",
" time.sleep(0.001) # Time consuming step\n",
" time.sleep(0.0001) # Memory consuming step\n",
" map_elapsed = time.perf_counter() - map_enter\n",
"\n",
" return (\n",
" tf.concat((steps, [[\"Map\"]]), axis=0),\n",
" tf.concat((times, [[map_enter, map_elapsed]]), axis=0),\n",
" tf.concat((values, [values[-1]]), axis=0)\n",
" )\n",
"\n",
"naive_timeline = timelined_benchmark(\n",
" tf.data.Dataset.range(2)\n",
" .flat_map(dataset_generator_fun)\n",
" .map(naive_map)\n",
" .batch(_batch_map_num_items, drop_remainder=True)\n",
" .unbatch(),\n",
" 5\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "EJqUMDsO-jNG"
},
"source": [
"### Optimized"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {
"execution": {
"iopub.execute_input": "2024-08-15T02:30:21.190475Z",
"iopub.status.busy": "2024-08-15T02:30:21.189814Z",
"iopub.status.idle": "2024-08-15T02:30:28.084324Z",
"shell.execute_reply": "2024-08-15T02:30:28.083578Z"
},
"id": "HYHcwabr-jNH"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Execution time: 6.8234945540007175\n"
]
}
],
"source": [
"@map_decorator\n",
"def time_consuming_map(steps, times, values):\n",
" map_enter = time.perf_counter()\n",
" time.sleep(0.001 * values.shape[0]) # Time consuming step\n",
" map_elapsed = time.perf_counter() - map_enter\n",
"\n",
" return (\n",
" tf.concat((steps, tf.tile([[[\"1st map\"]]], [steps.shape[0], 1, 1])), axis=1),\n",
" tf.concat((times, tf.tile([[[map_enter, map_elapsed]]], [times.shape[0], 1, 1])), axis=1),\n",
" tf.concat((values, tf.tile([[values[:][-1][0]]], [values.shape[0], 1, 1])), axis=1)\n",
" )\n",
"\n",
"\n",
"@map_decorator\n",
"def memory_consuming_map(steps, times, values):\n",
" map_enter = time.perf_counter()\n",
" time.sleep(0.0001 * values.shape[0]) # Memory consuming step\n",
" map_elapsed = time.perf_counter() - map_enter\n",
"\n",
" # Use tf.tile to handle batch dimension\n",
" return (\n",
" tf.concat((steps, tf.tile([[[\"2nd map\"]]], [steps.shape[0], 1, 1])), axis=1),\n",
" tf.concat((times, tf.tile([[[map_enter, map_elapsed]]], [times.shape[0], 1, 1])), axis=1),\n",
" tf.concat((values, tf.tile([[values[:][-1][0]]], [values.shape[0], 1, 1])), axis=1)\n",
" )\n",
"\n",
"\n",
"optimized_timeline = timelined_benchmark(\n",
" tf.data.Dataset.range(2)\n",
" .interleave( # Parallelize data reading\n",
" dataset_generator_fun,\n",
" num_parallel_calls=tf.data.AUTOTUNE\n",
" )\n",
" .batch( # Vectorize your mapped function\n",
" _batch_map_num_items,\n",
" drop_remainder=True)\n",
" .map( # Parallelize map transformation\n",
" time_consuming_map,\n",
" num_parallel_calls=tf.data.AUTOTUNE\n",
" )\n",
" .cache() # Cache data\n",
" .map( # Reduce memory usage\n",
" memory_consuming_map,\n",
" num_parallel_calls=tf.data.AUTOTUNE\n",
" )\n",
" .prefetch( # Overlap producer and consumer works\n",
" tf.data.AUTOTUNE\n",
" )\n",
" .unbatch(),\n",
" 5\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {
"execution": {
"iopub.execute_input": "2024-08-15T02:30:28.088090Z",
"iopub.status.busy": "2024-08-15T02:30:28.087533Z",
"iopub.status.idle": "2024-08-15T02:30:28.682784Z",
"shell.execute_reply": "2024-08-15T02:30:28.682068Z"
},
"id": "b_CSUbxL-jNK"
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/tmpfs/tmp/ipykernel_112933/2966908191.py:17: MatplotlibDeprecationWarning: The get_cmap function was deprecated in Matplotlib 3.7 and will be removed in 3.11. Use ``matplotlib.colormaps[name]`` or ``matplotlib.colormaps.get_cmap()`` or ``pyplot.get_cmap()`` instead.\n",
" cmap = mpl.cm.get_cmap(\"plasma\")\n"
]
},
{
"data": {
"image/png": "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7e1t5vSAU729vWprayO/MIn8wiqyC8t6e3v1vvdtxng+zKH2wjLyC6vIbnQ0tW19wQUX6NJLL9Xxxx+/4rETTjhBu+yyiy644AIdd9xxDR0nzPufAlFRKBTU09NDfmES+YVVZBeWFQoF3XbbTF18wbOulwKEQu2FZeQXVpHd6GjqTtOlS5dq7733XuXxz3zmM1q2bFnDx0kkGFGCPYlEQuPHjye/MIn8wiqyC8sSiYS23XYHxWJNvzMW4AS1F5aRX1hFdqOjqc5tv/32069+9atVHr/99tu17777NnwcbjWGRb29vYrFYuQXJpFfWEV2YVlvb6+22GI04/kwh9oLy8gvrCK70dHUeP6OO+6o888/X/fff786OjokSY888ohmzZqlU045RZdffvmK555wwgnveJyWlpZmTg841dLSogULFpBfmER+YRXZhWUtLS16+OF/6Jtn/Nn1UoBQqL2wjPzCKrIbHU1tml577bUaPXq0nnzyST355JMrHh81apSuvfbaFb+OxWKr3TSNxWLNnB5wKhaLqa2tjfzCJPILq8guLIvFYmppaZVEfmELtReWkV9YRXajo6lN0+eff16StHjxYknSmDFjmjp5X1+f2tvbm3ot4Mry3C5btkxtbW2ulwOEQn5hFdmFZX19fXr/+zfX7rv+RBJ3jcAOai8sI7+wiuxGR+j3NF26dKmOO+44jRkzRhtttJE22mgjjRkzRscff7yWLl0a6litra1hTw8419raqmXLlpFfmER+YRXZhWWtra36+99fVCKRdb0UIBRqLywjv7CK7EZHqDtNlyxZoo6ODr300ks69NBDtcMOO0iSnnzySd1www2677779PDDD2v06NENHa9er4dfMeBYvV5Xb2+vWlpauF0e5pBfWEV2YVm9Xld/f58kel/YQu2FZeQXVpHd6Ah1p+m5556rdDqtuXPn6ic/+YlOOukknXTSSbr66qv1z3/+U6lUSueee27Dx+vv7w+9YMC1/v5+jRs3jvzCJPILq8guLOvv79fHP76TfL/ieilAKNReWEZ+YRXZjY5Qm6a//vWv9f3vf18bbbTRKr+38cYb6+KLL9avfvWrho/HezPAora2NtXrdfILk8gvrCK7sKytrU3PP/+Gksmc66UAoVB7YRn5hVVkNzpCbZouWrRI48ePf8ff32mnnfTKK680fDzf98OcHogE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XHvssYdaW1vV1tamD3/4w/rzn/+84vc///nP689//rPmzp07pF8fAAAA2JJ0vQAAAADYd9lll+nZZ5/VTjvtpHPPPVeStMEGG+iFF15Y5bkDAwO6/PLLdcstt6ivr0+TJk3SAQccoFGjRunuu+/WvHnz9IUvfEG77LKLDjroIEnS8ccfryeffFK33HKLNt10U/3qV7/S3nvvrSeeeELbbLPN267pj3/8o770pS+t9Nihhx6qD37wg7ryyiuVSCT0t7/9TalUasXvb7bZZtpoo430xz/+UVtttdUQfXUAAABgDZumAAAAWGvt7e1Kp9PK5/PaeOONV/vcWq2mK6+8csWm5Be/+EX97Gc/07/+9S+1tLRoxx131B577KE//OEPOuiggzR//nxdf/31mj9/vjbddFNJ0qmnnqrf/va3uv7663XBBRe87XlefPHFFc9fbv78+TrttNO0/fbbS9LbbrhuuummevHFF0N/DQAAAPDuwaYpAAAAhlU+n1/pLs6NNtpI733ve9XS0rLSY6+++qok6YknnpDv+9p2221XOk6lUtH666//jucplUorjeZLUmdnp6ZOnaqf/exn2muvvTR58uRV7ijN5XIaGBho+s8HAAAA+9g0BQAAwLAaPA4vSbFY7G0fC4JAktTf369EIqG//OUvSiQSKz1v8EbrfxozZozeeOONlR6bNm2avvSlL+muu+7SPffco29/+9u65ZZbdMABB6x4zpIlS7TBBhs09WcDAADAuwObpgAAABgS6XR6pQ9vGiof/OAH5fu+Xn31VX3iE58I9bonn3xylce33XZbbbvttjr55JN1yCGH6Prrr1+xaVoulzV37lx98IMfHLL1AwAAwJ646wUAAADg3eG9732v/vSnP+mFF17Q4sWLV9wpura23XZbHXrooZoyZYpmzJih559/Xo8++qi++93v6q677nrH102cOFEPPfTQil+XSiUdf/zxuv/++/Xiiy9q1qxZmj17tnbYYYcVz3nkkUeUyWTU0dExJGsHAACATWyaAgAAYEiceuqpSiQS2nHHHbXBBhto/vz5Q3bs66+/XlOmTNEpp5yi7bbbTvvvv79mz56tzTbb7B1fc+ihh2rOnDl65plnJEmJREKvv/66pkyZom233VYHHnigPvvZz+qcc85Z8Zqbb75Zhx56qPL5/JCtHQAAAPbE6vV63fUiAAAAgHXhtNNOU29vr37yk5+s8bmLFy/Wdtttpz//+c/aYosthmF1AAAAiCruNAUAAMC71llnnaXNN9+8obcKeOGFF3TFFVewYQoAAADuNAUAAAAAAACAwbjTFAAAAAAAAAAGYdMUAAAAAAAAAAZh0xQAAAAAAAAABmHTFAAAAAAAAAAGYdMUAAAAAAAAAAZh0xQAAAAAAAAABmHTFAAAAAAAAAAGYdMUAAAAAAAAAAZh0xQAAAAAAAAABvn/yMh8zmkmtIgAAAAASUVORK5CYII=",
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"draw_timeline(naive_timeline, \"Naive\", 15)"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {
"execution": {
"iopub.execute_input": "2024-08-15T02:30:28.686209Z",
"iopub.status.busy": "2024-08-15T02:30:28.685879Z",
"iopub.status.idle": "2024-08-15T02:30:29.224088Z",
"shell.execute_reply": "2024-08-15T02:30:29.223400Z"
},
"id": "DoovY7qr-jNR"
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/tmpfs/tmp/ipykernel_112933/2966908191.py:17: MatplotlibDeprecationWarning: The get_cmap function was deprecated in Matplotlib 3.7 and will be removed in 3.11. Use ``matplotlib.colormaps[name]`` or ``matplotlib.colormaps.get_cmap()`` or ``pyplot.get_cmap()`` instead.\n",
" cmap = mpl.cm.get_cmap(\"plasma\")\n"
]
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABU0AAAI7CAYAAADYuroHAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAAB1vElEQVR4nO3deXxU5d3///dkkskyS0BBKwaVxRVwqWKN4tpWrFoqamgtatVitVZRA2q9ayvypdg1VGrpIpba2ltrXPGuNbVWrWis6+2CghZBiHJXEJNJJrPP/P7wRwwFYc4hyTkffT0fjzxumZlzrgvnBfd49VxnAsVisSgAAAAAAAAAgCSpzOsJAAAAAAAAAICfsGgKAAAAAAAAAL2waAoAAAAAAAAAvbBoCgAAAAAAAAC9sGgKAAAAAAAAAL2waAoAAAAAAAAAvbBoCgAAAAAAAAC9sGgKAAAAAAAAAL2waAoAAAAAAAAAvbBoCgAAgJL97ne/UyAQ0KpVq/rsnLNmzVIgEOiz85UqEAho1qxZAz4uAAAA/I9FUwAAAOOWLl2qM888U7vuuqsqKys1bNgwTZ06VUuXLnV9zrlz5+ree+/tu0kCAAAAhgSKxWLR60kAAADAnbvvvltnnHGGdthhB33961/XiBEjtGrVKt1888167733dPvtt2vy5MmOzxuJRHT66afrd7/73SaP5/N5ZbNZVVZW9tnVoblcTrlcTlVVVX1yvlIFAgFde+21XG0KAACAzZR7PQEAAAC4s2LFCp111lkaOXKk/vGPf2jo0KE9z1166aU68sgjddZZZ+mll17SyJEj+2TMYDCoYDDYJ+faqLy8XOXlfCwFAACAf7A9HwAAwKgf//jH6u7u1m9+85tNFkwlaciQIfr1r3+tRCKhH/3oR5I+vHfosmXLNGXKFMViMe2444669NJLlUqleo4NBAJKJBK65ZZbFAgEFAgEdM4550ja8j1N99hjD5188sl69NFHdcghh6i6ulrjxo3To48+KumDq2HHjRunqqoqHXzwwXrhhRc2met/3tP0nHPO6Rn3P396XxWaTqd17bXXavTo0aqsrNTw4cN15ZVXKp1Ob3L+dDqtyy+/XEOHDlU0GtWkSZPU1tbm9l87AAAAPgH4n/QBAACMuv/++7XHHnvoyCOP3OLzRx11lPbYYw/9+c9/3uTxKVOmaI899tD111+vp556SvPnz9f777+v3//+95KkP/zhD5o2bZoOPfRQfeMb35AkjRo1aqtz+de//qWvfvWruuCCC3TmmWfqJz/5ib74xS/qV7/6lf7rv/5LF110kSTp+uuv15QpU7R8+XKVlW35f7+/4IIL9LnPfW6Txx588EH98Y9/1E477SRJKhQKmjRpkpYsWaJvfOMb2nffffXyyy9r3rx5ev311ze5H+u0adN066236qtf/aoOP/xw/f3vf9dJJ5201d8PAAAAPtlYNAUAADCoo6ND77zzjr70pS9t9XX777+/Fi9erM7Ozp7HRowYofvuu0+S9K1vfUuxWEwLFizQzJkztf/+++vMM8/UhRdeqJEjR+rMM88saT7Lly/Xk08+qfr6eknSfvvtp4kTJ+r888/XsmXLtNtuu0mSBg8erAsuuED/+Mc/dMwxx2zxXPX19T3nkT5YkL344ov1+c9/XhdccIEk6b//+7/1t7/9TY899pgmTJjQ89qxY8fqwgsv1JNPPqnDDz9cL774om699VZddNFF+sUvftHze546dapeeumlkn5vAAAA+ORhez4AAIBBGxdBo9HoVl+38fl4PN7z2Le+9a1NXnPJJZdIkh544AHX89lvv/02Wej8zGc+I0k67rjjehZMez/+5ptvlnTeRCKhyZMna/Dgwbrtttt67qfa3NysfffdV/vss4/Wr1/f83PcccdJkh555JFNfk/Tp0/f5LyXXXaZi98lAAAAPim40hQAAMCgjYuhva8g3ZItLa7uueeem7xm1KhRKisr2+Q+pU71XhiVpNraWknS8OHDt/j4+++/X9J5zz//fK1YsUJPPvmkdtxxx57H33jjDb322mub3ct1o3fffVeS9NZbb6msrGyz2wvsvffeJY0PAACATyYWTQEAAAyqra3VLrvsss0t5i+99JJ23XVXxWKxj3xN7y9hcmvjFaClPl4sFrd5zhtuuEG33Xabbr31Vh144IGbPFcoFDRu3Dg1NTVt8dj/XKwFAAAAnGDRFAAAwKiTTz5ZN910k5YsWbLJfT03evzxx7Vq1aqe+4Bu9MYbb2jEiBE9v/7Xv/6lQqGgPfbYo+exvlhI3R6PP/64Zs6cqcsuu0xTp07d7PlRo0bpxRdf1Gc/+9mtznX33XdXoVDQihUrNrm6dPny5f0ybwAAAHw8cE9TAAAAo6644gpVV1frggsu0HvvvbfJcxs2bNCFF16ompoaXXHFFZs8t/ELkTb6+c9/Lkn6whe+0PNYOBxWe3t7/0x8G9auXaspU6ZowoQJ+vGPf7zF10yZMkVvv/22brrpps2eSyaTSiQSkj78Pc2fP3+T1/zsZz/r20kDAADgY4UrTQEAAIzac889dcstt2jq1KkaN26cvv71r2vEiBFatWqVbr75Zq1fv1633XbbZvfzXLlypSZNmqQTTjhBra2tuvXWW/XVr35VBxxwQM9rDj74YP3tb39TU1OThg0bphEjRvR8iVN/mz59utatW6crr7xSt99++ybP7b///tp///111lln6Y477tCFF16oRx55REcccYTy+byWLVumO+64Qy0tLTrkkEN04IEH6owzztCCBQvU0dGhww8/XA8//LD+9a9/DcjvBQAAADaxaAoAAGBYQ0OD9tlnH11//fU9C6U77rijjj32WP3Xf/2Xxo4du9kxf/rTn/S9731P3/72t1VeXq6LL754sys6m5qa9I1vfEPXXHONksmkvva1rw3Youm6deuUz+fV2Ni42XPXXnut9t9/f5WVlenee+/VvHnz9Pvf/1733HOPampqNHLkSF166aXaa6+9eo757W9/q6FDh+qPf/yj7r33Xh133HH685//zH1PAQAA8JECxVLuwg8AAADzZs2apeuuu07r1q3TkCFDvJ4OAAAA4Fvc0xQAAAAAAAAAemHRFAAAAAAAAAB6YdEUAAAAAAAAAHrhnqYAAAAAAAAA0AtXmgIAAAAAAABALyyaAgAAAAAAAEAvLJoCAAAAAAAAQC8smgIAAAAAAABALyyaAgAAAAAAAEAvLJoCAAAAAAAAQC8smgIAAAAAAABALyyaAgAAAAAAAEAvLJoCAAAAAAAAQC8smgIAAAAAAABALyyaAgAAAAAAAEAvLJoCAAAAAAAAQC8smgIAAAAAAABALyyaAgAAAAAAAEAvLJoCAAAAAAAAQC8smgIAAAAAAABALyyaAgAAAAAAAEAvLJoCAAAAAAAAQC8smgIAAAAAAABALyyaAgAAAAAAAEAvLJoCAAAAAAAAQC8smgIAAAAAAABALyyaAgAAAAAAAEAvLJoCAAAAAAAAQC8smgIAAAAAAABALyyaAgAAAAAAAEAvLJoCAAAAAAAAQC8smgIAAAAAAABALyyaAgAAAAAAAEAvLJoCAAAA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AAAA4BdszwcAAAAAAACAXlg0BQAAAAAAAIBeWDQFAAAAAAAAgF5YNAUAAAAAAACAXlg0BQAAAAAAAIBeWDQFAAAAAAAAgF5YNAUAAAAAAACAXlg0BQAAAAAAAIBeWDQFAAAAAAAAgF5YNAUAAEC/efTRRxUIBNTe3u7J+A8//LD23Xdf5fP5bb72wQcf1IEHHqhCoTAAMwMAAICfsWgKAACAPnHMMcfosssu2+Sxww8/XGvXrlVtba0nc7ryyit1zTXXKBgMbvO1J5xwgioqKvTHP/5xAGYGAAAAP2PRFAAAAP0mFArpU5/6lAKBwICPvWTJEq1YsUKnnXZaycecc845mj9/fj/OCgAAABawaAoAAIDtds455+ixxx7TDTfcoEAgoEAgoFWrVm22Pf93v/udBg0apP/5n//R3nvvrZqaGp1++unq7u7WLbfcoj322EODBw/W9OnTN9lSn06nNXPmTO26664Kh8P6zGc+o0cffXSrc7r99tv1+c9/XlVVVT2Pvfjiizr22GMVjUYVi8V08MEH69lnn+15/otf/KKeffZZrVixok///QAAAMCWcq8nAAAAAPtuuOEGvf766xo7dqxmz54tSRo6dKhWrVq12Wu7u7s1f/583X777ers7NSpp56qyZMna9CgQXrggQf05ptv6rTTTtMRRxyhL3/5y5Kkiy++WK+++qpuv/12DRs2TPfcc49OOOEEvfzyy9pzzz23OKfHH39cX/3qVzd5bOrUqTrooIP0y1/+UsFgUP/7v/+rioqKnud322037bzzznr88cc1atSoPvq3AwAAAGtYNAUAAMB2q62tVSgUUk1NjT71qU9t9bXZbFa//OUvexYlTz/9dP3hD3/Qv//9b0UiEe2333469thj9cgjj+jLX/6yVq9erUWLFmn16tUaNmyYJGnmzJl68MEHtWjRIs2dO3eL47z11ls9r99o9erVuuKKK7TPPvtI0hYXXIcNG6a33nrL8b8DAAAAfHywaAoAAIABVVNTs8lVnDvvvLP22GMPRSKRTR579913JUkvv/yy8vm89tprr03Ok06nteOOO37kOMlkcpOt+ZLU2NioadOm6Q9/+IM+97nPqaGhYbMrSqurq9Xd3e369wcAAAD7WDQFAADAgOq9HV6SAoHAFh8rFAqSpK6uLgWDQT333HMKBoObvK73Qut/GjJkiN5///1NHps1a5a++tWv6s9//rP+8pe/6Nprr9Xtt9+uyZMn97xmw4YNGjp0qKvfGwAAAD4eWDQFAABAnwiFQpt8eVNfOeigg5TP5/Xuu+/qyCOPdHTcq6++utnje+21l/baay9dfvnlOuOMM7Ro0aKeRdNUKqUVK1booIMO6rP5AwAAwJ4yrycAAACAj4c99thD//znP7Vq1SqtX7++50rR7bXXXntp6tSpOvvss3X33Xdr5cqVevrpp3X99dfrz3/+80ceN3HiRC1ZsqTn18lkUhdffLEeffRRvfXWW3riiSf0zDPPaN999+15zVNPPaXKykrV19f3ydwBAABgE4umAAAA6BMzZ85UMBjUfvvtp6FDh2r16tV9du5Fixbp7LPP1owZM7T33nvrlFNO0TPPPKPddtvtI4+ZOnWqli5dquXLl0uSgsGg3nvvPZ199tnaa6+9NGXKFH3hC1/Qdddd13PMbbfdpqlTp6qmpqbP5g4AAAB7AsVisej1JAAAAID+cMUVVygej+vXv/71Nl+7fv167b333nr22Wc1YsSIAZgdAAAA/IorTQEAAPCx9Z3vfEe77757SbcKWLVqlRYsWMCCKQAAALjSFAAAAAAAAAB640pTAAAAAAAAAOiFRVMAAAAAAAAA6IVFUwAAAAAAAADohUVTAAAAAAAAAOiFRVMAAAAAAAAA6IVFUwAAAAAAAADohUVTAAAAAAAAAOiFRVMAAAAAAAAA6IVFUwAAAAAAAADo5f8DGbK6IZTOtcAAAAAASUVORK5CYII=",
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"draw_timeline(optimized_timeline, \"Optimized\", 15)"
]
}
],
"metadata": {
"colab": {
"collapsed_sections": [],
"name": "data_performance.ipynb",
"toc_visible": true
},
"kernelspec": {
"display_name": "Python 3",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.19"
}
},
"nbformat": 4,
"nbformat_minor": 0
}