From 1b69d04db6f19462e161e6cebee956b142dc6f2e Mon Sep 17 00:00:00 2001 From: Andreas Herten <a.herten@fz-juelich.de> Date: Fri, 8 Nov 2019 15:48:36 +0100 Subject: [PATCH] Update Notebook to 2019 --- ...dsOnPerformanceOptimization-solution.ipynb | 2417 ++++++++++++- .../HandsOnPerformanceOptimization-task.ipynb | 3213 +++++++++++------ .../HandsOnPerformanceOptimization.ipynb | 1841 +++------- .../HandsOnPerformanceOptimization.html | 528 ++- .../HandsOnPerformanceOptimization.ipynb | 471 ++- .../HandsOnPerformanceOptimization.pdf | Bin 54013 -> 85793 bytes .../HandsOnPerformanceOptimization.html | 2492 ++++++++++--- .../HandsOnPerformanceOptimization.ipynb | 2417 ++++++++++++- .../HandsOnPerformanceOptimization.pdf | Bin 74614 -> 129532 bytes 9 files changed, 10086 insertions(+), 3293 deletions(-) diff --git a/3-Optimizing_POWER/Handson/.master/HandsOnPerformanceOptimization-solution.ipynb b/3-Optimizing_POWER/Handson/.master/HandsOnPerformanceOptimization-solution.ipynb index cb7fcfd..5208f9b 100644 --- a/3-Optimizing_POWER/Handson/.master/HandsOnPerformanceOptimization-solution.ipynb +++ b/3-Optimizing_POWER/Handson/.master/HandsOnPerformanceOptimization-solution.ipynb @@ -1 +1,2416 @@ -{"cells": [{"cell_type": "markdown", "metadata": {}, "source": ["# Hands-On Performance Optimization\n", "_Supercomputing 2018 Tutorial \"Application Porting and Optimization on GPU-Accelerated POWER Architectures\", November 12th 2018_\n", "\n", "---"]}, {"cell_type": "markdown", "metadata": {}, "source": ["As for the first task of this tutorial, also this task is primarily designed to be executed as an interactive Jupyter Notebook. However, everything can also be done using an SSH connection to Ascent (or any other POWER9 computer) in your terminal.\n", "\n", "## Jupyter notebook execution\n", "\n", "When using Jupyter, this Notebook will guide you through the steps. Note that if you execute a cell multiple times while optimizng the code the output will be replaced. You can however duplicate the cell you want to execute and keep its output. Check the _edit_ menu above.\n", "\n", "You will always find links to a file browser of the corresponding task subdirectory as well as direct links to the source files you will need to edit as well as the profiling output you need to open locally.\n", "\n", "If you want you also can get a [terminal](/terminals/1) in your browser.\n", "\n", "## Terminal fallback\n", "\n", "The tasks are place in directories named `Task[1-3]`.\n", "\n", "Makefile targets are created to cover everything, from compile, to run and profile. Please take a look at the cells containing the make calls as a guide also for the non-interactive version of this description."]}, {"cell_type": "markdown", "metadata": {}, "source": ["## Setup\n", "\n", "This hands-on session requires of GCC 6.4.0. By loading the `sc18/handson2` module before invoking this Notebook, we took care of also loading GCC 6.4.0 into the environment."]}, {"cell_type": "markdown", "metadata": {}, "source": ["## Tasks<a name=\"top\"></a>\n", "\n", "This session comes with multiple tasks, each one to be found in the respective sub-directory `Task[1-3]`. In each of these directories you will also find Makefiles that are set up so that you can compile and submit all necessary tasks.\n", "\n", "Please choose from the task below.\n", "\n", "\n", "* [Task 1](#task1): Compile Flags \n", "Improve performance of the CPU Jacobi solver with compiler flags such as `Ofast` and profile-directed feedback ([Solution 1](#solution0))\n", "\n", "* [Task 2](#task2): Software Prefetching \n", "Improve performance of the CPU Jacobi solver with software prefetching ([Solution 2](#solution1))\n", "\n", "* [Task 3](#task3): OpenMP \n", "Parallelize the CPU Jacobi solver and determine the right binding to be used for optimal performance ([Solution 3](#solution2))\n", " \n", "* [Suvery](#survey) Please remember to take the survey !\n", " \n", "### Make Targets <a name=\"make\"></a>\n", "\n", "For all tasks we have defined the following make targets. \n", "\n", "* __poisson2d__: \n", " build `poisson2d` binary (default)\n", "* __run__: \n", " run `poisson2d` with default parameters\n"]}, {"cell_type": "markdown", "metadata": {}, "source": ["[Back to Top](#top)\n", "\n", "---"]}, {"cell_type": "markdown", "metadata": {}, "source": ["## Task 1: Compile Flags <a name=\"task1\"></a>\n", "\n", "\n", "### Overview\n", "\n", "The goal of this task is to understand different options available to optimize the performance of the CPU Jacobi solver \n", "\n", "Your task is to:\n", "\n", "* Optimize performance with `-Ofast` flag\n", "* Optimize performance with profile directed feedback \n", "\n", "First, change the working directory to `Task1`."]}, {"cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["/autofs/nccsopen-svm1_home/aherten/SC18-Tutorial/3-Optimizing_POWER/Handson/Task1\n"]}], "source": ["%cd Task1"]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Part A: `-Ofast` vs. `-O3`\n", "\n", "We are to compare the performance of the binary being compiled with `-Ofast` optimization and with `-O3` optimization. Right now, the Makefile specifies `-O3` as the optimization flag. Compile the code using `make` and run it with `make run` in the next two cells."]}, {"cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["/sw/ascent/gcc/6.4.0/bin/gcc -c -std=c99 -mcpu=power9 -O3 -DUSE_DOUBLE -mvsx -maltivec -fopenmp poisson2d_reference.c -o poisson2d_reference.o -lm\n", "/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -O3 -DUSE_DOUBLE -mvsx -maltivec -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d -lm\n"]}], "source": ["!make"]}, {"cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", "Job <5033> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", "Calculate reference solution and time with serial CPU execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "Calculate current execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "1.13user 0.00system 0:01.15elapsed 97%CPU (0avgtext+0avgdata 10944maxresident)k\n", "2560inputs+0outputs (1major+264minor)pagefaults 0swaps\n"]}], "source": ["!make run"]}, {"cell_type": "markdown", "metadata": {}, "source": ["You can use the GNU _perf_ tool to profile the application using the `perf` command (see below) and see the top time-consuming functions."]}, {"cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", "Calculate reference solution and time with serial CPU execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "Calculate current execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "[ perf record: Woken up 1 times to write data ]\n", "[ perf record: Captured and wrote 0.172 MB perf.O3.data (4125 samples) ]\n", "# To display the perf.data header info, please use --header/--header-only options.\n", "#\n", "#\n", "# Total Lost Samples: 0\n", "#\n", "# Samples: 4K of event 'cycles:u'\n", "# Event count (approx.): 3867635297\n", "#\n", "# Overhead Command Shared Object Symbol \n", "# ........ ......... ................. ........................................\n", "#\n", " 72.02% poisson2d poisson2d [.] 00000040.plt_call.fmax@@GLIBC_2.17\n", " 10.16% poisson2d poisson2d [.] poisson2d_reference\n", " 9.99% poisson2d poisson2d [.] main\n", " 4.69% poisson2d libc-2.17.so [.] __memcpy_power7\n", " 2.23% poisson2d libm-2.17.so [.] __fmaxf\n", " 0.75% poisson2d libm-2.17.so [.] __exp_finite\n", " 0.07% poisson2d poisson2d [.] 00000040.plt_call.memcpy@@GLIBC_2.17\n", " 0.02% poisson2d poisson2d [.] check_results\n", " 0.02% poisson2d libm-2.17.so [.] __GI___exp\n", " 0.01% poisson2d ld-2.17.so [.] _dl_relocate_object\n", " 0.01% poisson2d [kernel.kallsyms] [k] arch_local_irq_restore\n", " 0.00% poisson2d ld-2.17.so [.] _dl_new_object\n", " 0.00% poisson2d ld-2.17.so [.] _start\n", "\n", "\n", "#\n", "# (Tip: Show user configuration overrides: perf config --user --list)\n", "#\n"]}], "source": ["# perf record creates a perf.data file \n", "!perf record -o perf.O3.data -e cycles ./poisson2d\n", "# perf report opens the perf.data file \n", "!perf report -i perf.O3.data | cat"]}, {"cell_type": "markdown", "metadata": {}, "source": ["**TASK**: Now change the optimization flag in the [Makefile](/edit/Task1/Makefile) to `-Ofast` and repeat the steps in the following cell. In case you follow along non-interactive, call `make` and `make run` in your shell. (If you are in the Jupyter Notebook, you can actually click the link of the [Makefile](/edit/Task1/Makefile). In other cases, use `vim` which is installed on Ascent.)"]}, {"cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["/sw/ascent/gcc/6.4.0/bin/gcc -c -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec -fopenmp poisson2d_reference.c -o poisson2d_reference.o -lm\n", "/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d -lm\n"]}], "source": ["!make"]}, {"cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", "Job <5034> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", "Calculate reference solution and time with serial CPU execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "Calculate current execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "0.51user 0.00system 0:00.52elapsed 99%CPU (0avgtext+0avgdata 10816maxresident)k\n", "256inputs+0outputs (0major+264minor)pagefaults 0swaps\n"]}], "source": ["!make run"]}, {"cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", "Calculate reference solution and time with serial CPU execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "Calculate current execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "[ perf record: Woken up 1 times to write data ]\n", "[ perf record: Captured and wrote 0.086 MB perf.Ofast.data (1889 samples) ]\n", "# To display the perf.data header info, please use --header/--header-only options.\n", "#\n", "#\n", "# Total Lost Samples: 0\n", "#\n", "# Samples: 1K of event 'cycles:u'\n", "# Event count (approx.): 1765737747\n", "#\n", "# Overhead Command Shared Object Symbol \n", "# ........ ......... ............. .......................\n", "#\n", " 44.65% poisson2d poisson2d [.] main\n", " 43.84% poisson2d poisson2d [.] poisson2d_reference\n", " 10.28% poisson2d libc-2.17.so [.] __memcpy_power7\n", " 1.12% poisson2d libm-2.17.so [.] __exp_finite\n", " 0.05% poisson2d poisson2d [.] check_results\n", " 0.03% poisson2d ld-2.17.so [.] _dl_relocate_object\n", " 0.02% poisson2d libc-2.17.so [.] __readdir64\n", " 0.01% poisson2d ld-2.17.so [.] _dl_new_object\n", " 0.00% poisson2d ld-2.17.so [.] _start\n", "\n", "\n", "#\n", "# (Tip: System-wide collection from all CPUs: perf record -a)\n", "#\n"]}], "source": ["# perf record creates a perf.data file \n", "!perf record -o perf.Ofast.data -e cycles ./poisson2d\n", "# perf report opens the perf.data file \n", "!perf report -i perf.Ofast.data | cat"]}, {"cell_type": "markdown", "metadata": {}, "source": ["If `perf` is unavailable to you on other machines, you can also study the disassembly with `objdump`: `objdump -lSd ./poisson2d > poisson2d.dis` (feel free to experiment with this in the Notebook as well, just prefix the command with a `!` to execute it.)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["#### Interpretation\n", "\n", "Depending on the application requirement, if a high precision of results is not mandatory, the users can compile an application with `-Ofast` which enables `\u2013ffast-math` option that implements the same math function in a relaxed manner very similar to how general mathematical expressions are implemented and avoids the overhead of calling a function from the math library. Comparing the files, you will see that the `-Ofast` binary natively implements the `fmax` function using instructions available in the hardware. The `-O3` binary makes a library call to compute `fmax` to follow a stricter _IEEE_ requirement for accuracy."]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Part B: Profile-directed Feedback\n", "\n", "For the first level of optimization we saw `Ofast` cut the execution time of the `O3` binary by almost half.\n", "\n", "We can optimize the performance further by using profile directed feedback optimization.\n", "\n", "To compile using profile directed feedback with the GCC compiler we need to do the following steps\n", "\n", "1. We need to first build a training binary using `-fprofile-generate`; this instructs the compiler to record hot path information \n", "2. Run the training binary with a smaller input size; you should see a `.gcda` file generated which stores hot path information for further optimization by the compiler \n", "3. build the final binary using `-fprofile-use` which uses the profile information in the `.gcda` file \n", "4. Compare the performance of the final binary with the original `Ofast` binary \n", "\n", "**TASK**: First, search for `TODO1` in the [Makefile](/edit/Task1/Makefile). It defines an additional compilation flag for `gcc`. Insert `-fprofile-generate=FOLDER` there with FOLDER pointing to `$$SC18_DIR_SCRATCH`, your personal write-directory (the double dollar signs are intentional as they are used to escape in the GNU Make syntax).\n", "\n", "After editing, run the following two cells to train your program."]}, {"cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["/sw/ascent/gcc/6.4.0/bin/gcc -c -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec \"-fprofile-generate=$SC18_DIR_SCRATCH\" -fopenmp poisson2d_reference.c -o poisson2d_reference.o -lm\n", "/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec \"-fprofile-generate=$SC18_DIR_SCRATCH\" -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d_train -lm\n"]}], "source": ["!make poisson2d_train"]}, {"cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d_train 200 64 64\n", "Job <5035> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 200 iterations on 64 x 64 mesh\n", "Calculate reference solution and time with serial CPU execution.\n", " 0, 0.248743\n", " 100, 0.124046\n", "Calculate current execution.\n", " 0, 0.248743\n", " 100, 0.124046\n", "0.00user 0.00system 0:00.10elapsed 5%CPU (0avgtext+0avgdata 5248maxresident)k\n", "512inputs+0outputs (0major+115minor)pagefaults 0swaps\n", "mv $SC18_DIR_SCRATCH/*.gcda .\n"]}], "source": ["!make run_train"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Now, a `.gcda` file exists in the directory which can be used for an profile-accelerated subsequent run.\n", "\n", "**TASK**: Edit the [Makefile](/edit/Task1/Makefile) again, this time modifying `TODO2` to be equivalent to `-fprofile-use`. A directory is not needed as we copied the gcda file into the current directory.\n", "\n", "Run the following cells in order to build using the newly added flag and then run with the profile-accelerated version."]}, {"cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["/sw/ascent/gcc/6.4.0/bin/gcc -c -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec \"-fprofile-use\" -fopenmp poisson2d_reference.c -o poisson2d_reference.o -lm\n", "/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec \"-fprofile-use\" -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d_profile -lm\n"]}], "source": ["!make poisson2d_profile"]}, {"cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d_profile\n", "Job <5036> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", "Calculate reference solution and time with serial CPU execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "Calculate current execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "0.47user 0.00system 0:00.48elapsed 98%CPU (0avgtext+0avgdata 10816maxresident)k\n", "256inputs+0outputs (0major+265minor)pagefaults 0swaps\n"]}], "source": ["!make run_profile"]}, {"cell_type": "markdown", "metadata": {}, "source": ["What is your speed-up? Feel free to run with larger problem sizes (mesh; iterations)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["#### References\n", "\n", "1. https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html\n", "2. https://perf.wiki.kernel.org/index.php/Tutorial"]}, {"cell_type": "markdown", "metadata": {}, "source": ["[Back to Top](#top)\n", "\n", "---"]}, {"cell_type": "markdown", "metadata": {}, "source": ["## Task 2:<a name=\"task2\"></a> Software Pretechting\n", "\n", "\n", "### Overview\n", "\n", "Study the difference of program execution time of different optimization levels with and without software prefetching.\n", "\n", "First, change directory to that of Task 2"]}, {"cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["/autofs/nccsopen-svm1_home/aherten/SC18-Tutorial/3-Optimizing_POWER/Handson/Task2\n"]}], "source": ["%cd ../Task2"]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Part A: Running"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Look at the [Makefile](/edit/Task2/Makefile) and work on the TODOs. Please implement compile flags as mentioned in the Makefile target name.\n", "\n", "Afterwards, compile each target with the following cells and submit them to the batch system. Follow along accordingly in the non-interactive version of this Notebook."]}, {"cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["/sw/ascent/gcc/6.4.0/bin/gcc -c -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -O3 -fprefetch-loop-arrays -fopenmp poisson2d_reference.c -o poisson2d_reference.o -lm\n", "/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -O3 -fprefetch-loop-arrays -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d_o3_pref -lm\n", "bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d_o3_pref\n", "Job <5037> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", "Calculate reference solution and time with serial CPU execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "Calculate current execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "1.12user 0.00system 0:01.13elapsed 99%CPU (0avgtext+0avgdata 10880maxresident)k\n", "256inputs+0outputs (0major+265minor)pagefaults 0swaps\n"]}], "source": ["!make run_o3_pref"]}, {"cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast -fprefetch-loop-arrays -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d_ofast_pref -lm\n", "bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d_ofast_pref\n", "Job <5038> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", "Calculate reference solution and time with serial CPU execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "Calculate current execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "0.77user 0.00system 0:00.77elapsed 99%CPU (0avgtext+0avgdata 10816maxresident)k\n", "256inputs+0outputs (0major+264minor)pagefaults 0swaps\n"]}], "source": ["!make run_ofast_pref"]}, {"cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -O3 -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d_o3_nopref -lm\n", "bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d_o3_nopref\n", "Job <5039> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", "Calculate reference solution and time with serial CPU execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "Calculate current execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "1.13user 0.00system 0:01.13elapsed 99%CPU (0avgtext+0avgdata 10944maxresident)k\n", "256inputs+0outputs (0major+266minor)pagefaults 0swaps\n"]}], "source": ["!make run_o3_nopref"]}, {"cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d_ofast_nopref -lm\n", "bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d_ofast_nopref\n", "Job <5040> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", "Calculate reference solution and time with serial CPU execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "Calculate current execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "0.82user 0.00system 0:00.82elapsed 99%CPU (0avgtext+0avgdata 10816maxresident)k\n", "256inputs+0outputs (0major+265minor)pagefaults 0swaps\n"]}], "source": ["!make run_ofast_nopref"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Do you notice the impact difference with optimization levels? It's always important to carefully study the interplay of flags."]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Part B: Analysis of Instructions\n", "\n", "Compilation with the software prefetching flag causes the compiler to generate the `__dcbt` and `__dcbtst` instructions that prefetch memory values to L3.\n", "\n", "Verify it using `objdump -lSd` on each file (`poisson2d_o3_pref`, `poisson2d_ofast_pref`, `poisson2d_o3_nopref`, `poisson2d_ofast_nopref`). You might want to grep for `dcb`."]}, {"cell_type": "code", "execution_count": 19, "metadata": {"exercise": "solution"}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["poisson2d_o3_pref:\n", "poisson2d_ofast_pref:\n", " 10000da0:\tec f1 00 7c \tdcbtst 0,r30\n", " 10000da4:\t2c fa 00 7c \tdcbt 0,r31\n", " 10000da8:\t2c 62 00 7c \tdcbt 0,r12\n", " 10000dac:\t2c b2 00 7c \tdcbt 0,r22\n", " 10000dcc:\t2c e2 00 7c \tdcbt 0,r28\n", " 10000dd0:\t2c ea 00 7c \tdcbt 0,r29\n", " 100010b4:\t2c 62 00 7c \tdcbt 0,r12\n", " 100010b8:\t2c 5a 00 7c \tdcbt 0,r11\n", " 100010c4:\tec 19 00 7c \tdcbtst 0,r3\n", " 100010cc:\t2c 22 00 7c \tdcbt 0,r4\n", " 100010d0:\t2c ea 00 7c \tdcbt 0,r29\n", " 100010d4:\t2c f2 00 7c \tdcbt 0,r30\n", " 100010dc:\t2c fa 00 7c \tdcbt 0,r31\n", "poisson2d_o3_nopref:\n", "poisson2d_ofast_nopref:\n"]}], "source": ["for f in [\"poisson2d_o3_pref\", \"poisson2d_ofast_pref\", \"poisson2d_o3_nopref\", \"poisson2d_ofast_nopref\"]:\n", " print(\"{}:\".format(f))\n", " objdump -lSd $f |\u00a0grep dcb"]}, {"cell_type": "markdown", "metadata": {}, "source": ["If you feel up to the task, you can study the number of L3 cache misses using the corresponding performance counter, `PM_L3_MISS`. Either use your knowledge from Hands-On 1, or use the following call to `perf`, in which we already converted the named counter to a raw counter address."]}, {"cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Job <5048> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", "Calculate reference solution and time with serial CPU execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "Calculate current execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "\n", " Performance counter stats for './poisson2d_ofast_nopref':\n", "\n", " 2829292169 cycles:u \n", " 136018637 r168a4:u \n", "\n", " 0.826136863 seconds time elapsed\n", "\n", "Job <5049> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", "Calculate reference solution and time with serial CPU execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "Calculate current execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "\n", " Performance counter stats for './poisson2d_ofast_pref':\n", "\n", " 2654990243 cycles:u \n", " 128824827 r168a4:u \n", "\n", " 0.775593651 seconds time elapsed\n", "\n"]}], "source": ["for f in [\"poisson2d_ofast_nopref\", \"poisson2d_ofast_pref\"]:\n", " !eval $$SC18_SUBMIT_CMD perf stat -e cycles,r168a4 ./$f\n"]}, {"cell_type": "markdown", "metadata": {}, "source": ["#### References\n", "\n", "1. https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html\n", "2. https://www.gnu.org/software/gcc/projects/prefetch.html"]}, {"cell_type": "markdown", "metadata": {}, "source": ["[Back to Top](#top)\n", "\n", "---"]}, {"cell_type": "markdown", "metadata": {}, "source": ["## Task 3: OpenMP\n", "<a name=\"task3\"></a>\n", "\n", "\n", "### Overview\n", "\n", "We add OpenMP shared-memory parallelism to the application. Also, we study the effect of binding the multi-thread processes to certain cores.\n", "\n", "First, we need to change directory to that of Task3."]}, {"cell_type": "code", "execution_count": 1, "metadata": {"ExecuteTime": {"end_time": "2018-11-07T13:47:57.724441Z", "start_time": "2018-11-07T13:47:57.718745Z"}}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["/autofs/nccsopen-svm1_home/aherten/SC18-Tutorial/3-Optimizing_POWER/Handson/Task3\n"]}], "source": ["%cd ../Task3"]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Part A: Implement OpenMP Pragmas; Compilation\n", "\n", "**Task**: Please add the correct OpenMP pragmas to the source code and compilations flags to enable OpenMP.\n", "\n", "* **pragmas**: Look at the TODOs in [`poisson2d.c`](/edit/Task3/poisson2d.c) to add OpenMP parallelism. The pragmas in question are `#pragma omp parallel for`\n", "* **Compilation**: Please add compilation flags enabling OpenMP in GCC to the [Makefile](/edit/Task3/Makefile). The flag in question is `-fopenmp`.\n", "\n", "Edit the files with the links above if you are running the interactive version of the Notebook or navigate to `poisson2d.c` and `Makefile` yourself in case you run the non-interactive version.\n", "\n", "Afterwards, compile and run the application with the following cells. Non-interactive: Follow along accordingly in the shell."]}, {"cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["/sw/ascent/gcc/6.4.0/bin/gcc -c -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec poisson2d_reference.c -o poisson2d_reference.o -lm\n", "/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d -lm\n"]}], "source": ["!make poisson2d"]}, {"cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", "Job <5052> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", "Calculate reference solution and time with serial CPU execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "Calculate current execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "500x500: Ref: 0.2571 s, This: 0.2946 s, speedup: 0.87\n", "1.48user 0.00system 0:00.56elapsed 263%CPU (0avgtext+0avgdata 9664maxresident)k\n", "0inputs+0outputs (0major+273minor)pagefaults 0swaps\n"]}], "source": ["!make run"]}, {"cell_type": "markdown", "metadata": {}, "source": ["The command to submit a job to the batch system is prepared in an environment variable `$SC18_SUBMIT_CMD`; use it together with `eval`. In the following cell, it is shown how to increase the work of the application."]}, {"cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Job <5344> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 1000 iterations on 1000 x 100 mesh\n", "Calculate reference solution and time with serial CPU execution.\n", " 0, 0.249743\n", " 100, 0.210080\n", " 200, 0.184635\n", " 300, 0.166526\n", " 400, 0.152783\n", " 500, 0.141890\n", " 600, 0.132978\n", " 700, 0.125511\n", " 800, 0.119142\n", " 900, 0.113632\n", "Calculate current execution.\n", " 0, 0.249743\n", " 100, 0.210080\n", " 200, 0.184635\n", " 300, 0.166526\n", " 400, 0.152783\n", " 500, 0.141890\n", " 600, 0.132978\n", " 700, 0.125511\n", " 800, 0.119142\n", " 900, 0.113632\n", "1000x100: Ref: 1.9872 s, This: 0.2385 s, speedup: 8.33\n"]}], "source": ["!eval $SC18_SUBMIT_CMD ./poisson2d 1000 1000"]}, {"cell_type": "markdown", "metadata": {}, "source": ["What is the best performance you can reach by setting the number of threads via `OMP_NUM_THREADS=N` with `N` being the number of threads? Feel free to play around with the command in the following cell, using 1 thread as an example. \n", "We added `--bind none` to prevent `jsrun`, the scheduler of Ascent, from overlaying binding options. Also, we use `-c ALL_CPUS` to make all CPUs on the compute nodes available to you."]}, {"cell_type": "code", "execution_count": 23, "metadata": {"exercise": "solution"}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Threads: 1\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "1000x1000: Ref: 2.3037 s, This: 2.8420 s, speedup: 0.81\n", "Threads: 2\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "1000x1000: Ref: 2.2998 s, This: 1.4320 s, speedup: 1.61\n", "Threads: 4\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "1000x1000: Ref: 2.3135 s, This: 0.7168 s, speedup: 3.23\n", "Threads: 8\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "1000x1000: Ref: 2.3145 s, This: 0.5278 s, speedup: 4.39\n", "Threads: 10\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "1000x1000: Ref: 2.3153 s, This: 0.4848 s, speedup: 4.78\n", "Threads: 20\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "1000x1000: Ref: 2.3190 s, This: 0.2016 s, speedup: 11.50\n", "Threads: 40\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "1000x1000: Ref: 2.3243 s, This: 0.3057 s, speedup: 7.60\n"]}], "source": ["for omp_num in [1, 2, 4, 8, 10, 20, 40]:\n", " print(\"Threads: {}\".format(omp_num))\n", " !eval OMP_NUM_THREADS=$omp_num $$SC18_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 | grep speedup"]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Part B: Bindings\n", "\n", "Different CPU architectures and models come with different configuration of cores. The configuration plays an important role in the run time of the application. We need to optimize for it!\n", "\n", "There are applications which can be used to determine the configuration of the processor. Among those are:\n", "\n", "* `lscpu`: Can be used to determine the number of sockets, number of cores, and numb of threads. It gives a very good overview and is available on most Linux systems.\n", "* `ppc64_cpu --smt`: Specifically for POWER, this tool can give information about the number of simulations threads running per core (*SMT*, Simulataion Multi-Threading).\n", "\n", "Run `ppc64_cpu --smt` to find out about the threading configuration of Ascent!"]}, {"cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Job <5076> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "SMT=4\n"]}], "source": ["!eval $SC18_SUBMIT_CMD ppc64_cpu --smt"]}, {"cell_type": "markdown", "metadata": {}, "source": ["There are more sources information available\n", "\n", "* `/proc/cpuinfo`: Holds information about virtual cores, including model and clock speed. Available on most Linux system. Usually used together with `cat`\n", "* `/sys/devices/system/cpu/cpu0/topology/thread_siblings_list`: Holds information about thread siblings for given CPU core (`cpu0` in this case). Use it to find out which thread is mapped to which core."]}, {"cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["0-3\n", "4-7\n"]}], "source": ["!cat /sys/devices/system/cpu/cpu0/topology/thread_siblings_list\n", "!cat /sys/devices/system/cpu/cpu5/topology/thread_siblings_list"]}, {"cell_type": "markdown", "metadata": {}, "source": ["There are various environment variables available within OpenMP (and GCC) to specify binding of threads to cores. See, for instance, the [online documentation of GCC libgomp](https://gcc.gnu.org/onlinedocs/libgomp/Environment-Variables.html). Examples are `OMP_PLACES` or `GOMP_CPU_AFFINITY`.\n", "\n", "**Task**: Run the application enabled with OpenMP from Part A with different binding configurations. Make sure to at least run a) binding all threads to a single core and b) binding threads to different cores.\n", "\n", "Adapt the following command with your configuration \u2013 or follow along accordingly in the non-interactive version of the Notebook.\n", "\n", "What's your maximum speedup?"]}, {"cell_type": "code", "execution_count": 24, "metadata": {"exercise": "solution"}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Affinity: 0,1,2,3\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", " OMP_PLACES = '{0},{1},{2},{3}'\n", "1000x100: Ref: 1.9854 s, This: 0.2326 s, speedup: 8.53\n", "Affinity: 0,5,9,13\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", " OMP_PLACES = '{0},{5},{9},{13}'\n", "1000x100: Ref: 1.9828 s, This: 0.0833 s, speedup: 23.80\n"]}], "source": ["for affinity in [\"0,1,2,3\", \"0,5,9,13\"]:\n", " print(\"Affinity: {}\".format(affinity))\n", " !eval OMP_DISPLAY_ENV=true GOMP_CPU_AFFINITY=$affinity OMP_NUM_THREADS=4 $$SC18_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 100 | grep \"OMP_PLACES\\|speedup\""]}, {"cell_type": "markdown", "metadata": {}, "source": ["#### References\n", "1. https://gcc.gnu.org/onlinedocs/libgomp/GOMP_005fCPU_005fAFFINITY.html"]}, {"cell_type": "markdown", "metadata": {}, "source": ["[Back to Top](#top)\n", "\n", "---"]}, {"cell_type": "markdown", "metadata": {}, "source": ["# Survey<a name=\"survey\"></a>\n", "\n", "Please rememeber to take some time and fill out the [survey](http://bit.ly/sc18-eval)."]}], "metadata": {"kernelspec": {"display_name": "Python 3", "language": "python", "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.6.7"}}, "nbformat": 4, "nbformat_minor": 2} \ No newline at end of file +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Hands-On Performance Optimization\n", + "_Supercomputing 2019 Tutorial \"Application Porting and Optimization on GPU-Accelerated POWER Architectures\", November 18th 2019_\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As for the first task of this tutorial, also this task is primarily designed to be executed as an interactive Jupyter Notebook. However, everything can also be done using an SSH connection to Ascent (or any other POWER9 computer) in your terminal.\n", + "\n", + "## Jupyter notebook execution\n", + "\n", + "When using Jupyter, this Notebook will guide you through the steps. Note that if you execute a cell multiple times while optimizng the code the output will be replaced. You can however duplicate the cell you want to execute and keep its output. Check the _edit_ menu above.\n", + "\n", + "You will always find links to a file browser of the corresponding task subdirectory as well as direct links to the source files you will need to edit as well as the profiling output you need to open locally.\n", + "\n", + "If you want you also can get a terminal in your browser; just open it via the \u00bbNew Launcher\u00ab button (`+`).\n", + "\n", + "## Terminal fallback\n", + "\n", + "The tasks are place in directories named `Task[1-3]`.\n", + "\n", + "Makefile targets are created to cover everything, from compile, to run and profile. Please take a look at the cells containing the make calls as a guide also for the non-interactive version of this description." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "We are using some very fresh compiler features and use GCC 9.2.0 because of that. It should already be in your environment. Let's check!" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc (GCC) 9.2.0\n", + "Copyright (C) 2019 Free Software Foundation, Inc.\n", + "This is free software; see the source for copying conditions. There is NO\n", + "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n", + "\n" + ] + } + ], + "source": [ + "!gcc --version" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Tasks<a name=\"top\"></a>\n", + "\n", + "This session comes with multiple tasks, each one to be found in the respective sub-directory `Task[1-3]`. In each of these directories you will also find Makefiles that are set up so that you can compile and submit all necessary tasks.\n", + "\n", + "Please choose from the task below.\n", + "\n", + "\n", + "* [Task 1](#task1): __Basic compiler optimization flags and compiler annotations__\n", + "\n", + "Improve performance of the CPU Jacobi solver with compiler flags such as `Ofast` and profile-directed feedback. Learn about compiler annotations.\n", + "\n", + "* [Task 2](#task2): __Optimization via Prefetching controlled by compiler__\n", + "\n", + "Improve performance of the CPU Jacobi solver with software prefetching. Some compilers such as IBM XL define flags that can be used to modify the aggressiveness of the hardware prefetcher. Learn to modify the DSCR value through XL and study the impact on application performance. \n", + "* [Task 3](#task3): __Optimization via OpenMP controlled by compiler and the system__\n", + "\n", + "Parallelize the CPU Jacobi solver and determine the right binding to be used for optimal performance. \n", + " \n", + "* [Suvery](#survey) Please remember to take the survey !\n", + " \n", + "### Make Targets <a name=\"make\"></a>\n", + "\n", + "For all tasks we have defined the following make targets. \n", + "\n", + "* __poisson2d__: \n", + " build `poisson2d` binary (default)\n", + "* __run__: \n", + " run `poisson2d` with default parameters\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[Back to Top](#top)\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Task 1: Basic compiler optimization flags and compiler annotations <a name=\"task1\"></a>\n", + "\n", + "\n", + "### Overview\n", + "\n", + "The goal of this task is to understand different options available to optimize the performance of the CPU Jacobi solver \n", + "\n", + "Your task is to:\n", + "\n", + "* Optimize performance with `-Ofast` flag\n", + "* Verify the cause for performance improvement by viewing perf profiles of O3 and Ofast binaries \n", + "* Optimize performance with profile directed feedback \n", + "* Generate compiler annotations/remarks to understand the optimizations done by the compiler with and without profile directed feedback \n", + "\n", + "\n", + "First, change the working directory to `Task1`." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/autofs/nccsopen-svm1_home/aherten/SC19-Tutorial/3-Optimizing_POWER/Handson/Task1\n" + ] + } + ], + "source": [ + "%cd Task1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Part A: `-Ofast` vs. `-O3`\n", + "\n", + "We are to compare the performance of the binary being compiled with `-Ofast` optimization and with `-O3` optimization. As in the previous task, we use a `Makefile` for compilation. The `Makefile` targets `poisson2d_O3` and `poisson2d_Ofast` are already prepared. \n", + "\n", + "**TASK**: Add `-O3` as the optimization flag for the `poisson2d_O3` target by using the corresponding `CFLAGS` definition. There are notes relating to this Task 1 in the header of the `Makefile`. Compile the code using `make` as indicated below and run with the `Make` targets `run`, `run_perf` and `run_perf_recrep`. " + ] + }, + { + "cell_type": "code", + "execution_count": 84, + "metadata": { + "collapsed": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc -c -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -O3 poisson2d_reference.c -o poisson2d_reference.o -lm\n", + "gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -O3 poisson2d.c poisson2d_reference.o -o poisson2d -lm\n" + ] + } + ], + "source": [ + "!make poisson2d_O3" + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", + "Job <24897> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "4.73user 0.00system 0:04.73elapsed 99%CPU (0avgtext+0avgdata 24256maxresident)k\n", + "256inputs+0outputs (0major+480minor)pagefaults 0swaps\n" + ] + } + ], + "source": [ + "!make run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's have a look at the output of the `Makefile` target `run_perf`. It invokes the GNU _perf_ tool to print out details of the number of instructions executed and the number of cycles taken by POWER9 to execute the program. Feel free to add further counter to this call to _perf_." + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,instructions ./poisson2d\n", + "Job <24898> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "\n", + " Performance counter stats for './poisson2d':\n", + "\n", + " 16264721613 cycles:u \n", + " 28463907825 instructions:u # 1.75 insn per cycle \n", + "\n", + " 4.738444892 seconds time elapsed\n", + "\n" + ] + } + ], + "source": [ + "!make run_perf" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next we run the makefile with target `run_perf_recrep` that prints the top routines of the application in terms of hotness by using a combination of `perf record ./app` and `perf report`. " + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf record -e cycles --output=/gpfs/wolf/trn003/scratch/aherten//cycles.data ./poisson2d\n", + "Job <24899> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "[ perf record: Woken up 3 times to write data ]\n", + "[ perf record: Captured and wrote 0.739 MB /gpfs/wolf/trn003/scratch/aherten//cycles.data (19102 samples) ]\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf report -i /gpfs/wolf/trn003/scratch/aherten//cycles.data --stdio\n", + "Job <24900> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "# To display the perf.data header info, please use --header/--header-only options.\n", + "#\n", + "#\n", + "# Total Lost Samples: 0\n", + "#\n", + "# Samples: 19K of event 'cycles:u'\n", + "# Event count (approx.): 16254596654\n", + "#\n", + "# Overhead Command Shared Object Symbol \n", + "# ........ ......... ............. ........................................\n", + "#\n", + " 65.50% poisson2d poisson2d [.] 00000038.plt_call.fmax@@GLIBC_2.17\n", + " 21.21% poisson2d poisson2d [.] main\n", + " 9.18% poisson2d libc-2.17.so [.] __memcpy_power7\n", + " 3.28% poisson2d libm-2.17.so [.] __fmaxf\n", + " 0.74% poisson2d libm-2.17.so [.] __exp_finite\n", + " 0.04% poisson2d poisson2d [.] 00000038.plt_call.memcpy@@GLIBC_2.17\n", + " 0.01% poisson2d libm-2.17.so [.] __GI___exp\n", + " 0.01% poisson2d ld-2.17.so [.] check_match.10253\n", + " 0.01% poisson2d ld-2.17.so [.] do_lookup_x\n", + " 0.00% poisson2d ld-2.17.so [.] _dl_lookup_symbol_x\n", + " 0.00% poisson2d ld-2.17.so [.] _dl_relocate_object\n", + " 0.00% poisson2d ld-2.17.so [.] strcmp\n", + " 0.00% poisson2d ld-2.17.so [.] _wordcopy_fwd_aligned\n", + " 0.00% poisson2d ld-2.17.so [.] _dl_sysdep_start\n", + " 0.00% poisson2d ld-2.17.so [.] _start\n", + "\n", + "\n", + "#\n", + "# (Tip: Limit to show entries above 5% only: perf report --percent-limit 5)\n", + "#\n" + ] + } + ], + "source": [ + "# run_perf_recrep displays the top hot routines \n", + "!make run_perf_recrep" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**TASK**: Now add the optimization flag `Ofast` to the `CFLAGS` for target `poisson2d_Ofast`. Compile the program with the target `poisson2d_Ofast` and run and analyse it as before with `run`, `run_perf` and `run_perf_recrep`.\n", + "\n", + "What difference do you see?" + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast poisson2d.c poisson2d_reference.o -o poisson2d -lm\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", + "Job <24901> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "2.41user 0.00system 0:02.41elapsed 99%CPU (0avgtext+0avgdata 24256maxresident)k\n", + "256inputs+0outputs (0major+480minor)pagefaults 0swaps\n" + ] + } + ], + "source": [ + "!make poisson2d_Ofast \n", + "!make run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Again, run a `perf`-instrumented version:" + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,instructions ./poisson2d\n", + "Job <24902> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "\n", + " Performance counter stats for './poisson2d':\n", + "\n", + " 8258991976 cycles:u \n", + " 12013091172 instructions:u # 1.45 insn per cycle \n", + "\n", + " 2.408703909 seconds time elapsed\n", + "\n" + ] + } + ], + "source": [ + "!make run_perf" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Generate the list of top routines in terms of hotness:" + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf record -e cycles --output=/gpfs/wolf/trn003/scratch/aherten//cycles.data ./poisson2d\n", + "Job <24903> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "[ perf record: Woken up 2 times to write data ]\n", + "[ perf record: Captured and wrote 0.382 MB /gpfs/wolf/trn003/scratch/aherten//cycles.data (9728 samples) ]\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf report -i /gpfs/wolf/trn003/scratch/aherten//cycles.data --stdio\n", + "Job <24904> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "# To display the perf.data header info, please use --header/--header-only options.\n", + "#\n", + "#\n", + "# Total Lost Samples: 0\n", + "#\n", + "# Samples: 9K of event 'cycles:u'\n", + "# Event count (approx.): 8268811890\n", + "#\n", + "# Overhead Command Shared Object Symbol \n", + "# ........ ......... ............. ........................................\n", + "#\n", + " 81.12% poisson2d poisson2d [.] main\n", + " 17.97% poisson2d libc-2.17.so [.] __memcpy_power7\n", + " 0.79% poisson2d libm-2.17.so [.] __exp_finite\n", + " 0.04% poisson2d poisson2d [.] 00000038.plt_call.memcpy@@GLIBC_2.17\n", + " 0.02% poisson2d ld-2.17.so [.] do_lookup_x\n", + " 0.01% poisson2d libc-2.17.so [.] vfprintf@@GLIBC_2.17\n", + " 0.01% poisson2d libc-2.17.so [.] _dl_addr\n", + " 0.01% poisson2d ld-2.17.so [.] _dl_relocate_object\n", + " 0.01% poisson2d ld-2.17.so [.] check_match.10253\n", + " 0.01% poisson2d ld-2.17.so [.] _dl_lookup_symbol_x\n", + " 0.01% poisson2d ld-2.17.so [.] strcmp\n", + " 0.00% poisson2d ld-2.17.so [.] open_path\n", + " 0.00% poisson2d ld-2.17.so [.] init_tls\n", + " 0.00% poisson2d ld-2.17.so [.] _dl_sysdep_start\n", + " 0.00% poisson2d ld-2.17.so [.] _start\n", + "\n", + "\n", + "#\n", + "# (Tip: For tracepoint events, try: perf report -s trace_fields)\n", + "#\n" + ] + } + ], + "source": [ + "!make run_perf_recrep" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If `perf` is unavailable to you on other machines, you can also study the disassembly with `objdump`: `objdump -lSd ./poisson2d > poisson2d.dis` (feel free to experiment with this in the Notebook as well, just prefix the command with a `!` to execute it.)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Interpretation\n", + "\n", + "Depending on the application requirement, if a high precision of results is not mandatory, one can compile an application with `-Ofast` which enables `\u2013ffast-math` option that implements the same math function in a relaxed manner very similar to how general mathematical expressions are implemented and avoids the overhead of calling a function from the math library. Comparing the files, you will see that the `-Ofast` binary natively implements the `fmax` function using instructions available in the hardware. The `-O3` binary makes a library call to compute `fmax` to follow a stricter _IEEE_ requirement for accuracy." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Part B: Profile-directed Feedback\n", + "\n", + "For the first level of optimization we see that `Ofast` cut the execution time of the `O3` binary by almost half.\n", + "\n", + "We can optimize the performance further by using profile-directed feedback optimization.\n", + "\n", + "To compile using profile-directed feedback with the GCC compiler we need to build the appplication in three stages:\n", + "\n", + "1. Instrument binary;\n", + "2. Run binary with training, gather profile information;\n", + "3. Use profile information to generate optimized binary.\n", + "\n", + "\n", + "Step 1 is achieved by compiling the binary with the correct flag \u2013\u00a0`-fprofile-generate`. In our case, we need to specify an output location, which should be `$(SC19_DIR_SCRATCH)`.\n", + "\n", + "Step 2 consists of a usual, albeit shorter run of the instrumented binary. The can be very short, though the parameters need to be representative of the actual run. After the binary ran, an output file (with file extension `.gcda`) is written to the directory specified during compilation.\n", + "\n", + "For Step 3, the binary is once again compiled, but this time using the `gcda` profile just generated. The according flag is `-fprofile-use`, which we set to `$(SC19_DIR_SCRATCH)` as well.\n", + "\n", + "In our `Makefile` at hand, we prepared the steps already for you in the form of two targets.\n", + "\n", + "* `poisson2d_train`: Will compile the binary with profile-directed feedback\n", + "* `poisson2d_ref`: Will take a generated profile and compile a new, optimized binary\n", + "\n", + "By using dependencies, between these two targets a profile run is launched.\n", + "\n", + "**TASK**: Edit the [Makefile](`Makefile`) and add the `-fprofile-*` flags to the `CFLAGS` of `poisson2d_train` and\n", + "`poisson2d_ref` as outline in the file.\n", + "\n", + "After that, you may launch them with the following cells (`gen_profile` is a meta-target and uses `poisson2d_train` and `poisson2d_ref`). If you need to clean the generated profile, you may use `make clean_profile`." + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast -fprofile-generate=/gpfs/wolf/trn003/scratch/aherten/ poisson2d.c -o poisson2d_train -lm \n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS ./poisson2d_train 100 100 100\n", + "Job <24905> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 100 iterations on 100 x 100 mesh\n", + "Calculate current execution.\n", + " 0, 0.249490\n", + "echo `date` > /gpfs/wolf/trn003/scratch/aherten//.profile_generated\n", + "gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast -fprofile-use=/gpfs/wolf/trn003/scratch/aherten/ poisson2d.c -o poisson2d_ref -lm \n", + "cp poisson2d_ref poisson2d\n" + ] + } + ], + "source": [ + "!make gen_profile" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If the previous cell executed correctly, you now have your optimized executable. Let's see if it even fast than before!" + ] + }, + { + "cell_type": "code", + "execution_count": 80, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", + "Job <24906> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "2.28user 0.01system 0:02.30elapsed 99%CPU (0avgtext+0avgdata 24192maxresident)k\n", + "256inputs+0outputs (0major+479minor)pagefaults 0swaps\n" + ] + } + ], + "source": [ + "!make run" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "exercise": "solution" + }, + "source": [ + "Great! It is! In our tests, this shaved off another 5%." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's also measure instructions and cycles" + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,instructions ./poisson2d\n", + "Job <24907> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "\n", + " Performance counter stats for './poisson2d':\n", + "\n", + " 7925983538 cycles:u \n", + " 12253080719 instructions:u # 1.55 insn per cycle \n", + "\n", + " 2.313471365 seconds time elapsed\n", + "\n" + ] + } + ], + "source": [ + "!make run_perf" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "What is your speed-up? Feel free to run with larger problem sizes (mesh; iterations)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Part C: Compiler annotations/Remarks\n", + "\n", + "Usually, all compilers provide an option to emit annotations or remarks by the compiler. These remarks summarize the optimizations done in detail, the location in source where these optimizations were done. There exist options that also indicate optimizations that were missed and the reason why they could not be done. \n", + "\n", + "To generate compiler annotations using GCC, one uses `-fopt-info-all`. If you only want to see the missed options, use the option `-fopt-info-missed` instead of `-fopt-info-all`. See also the [documentation of GCC regarding the flag](https://gcc.gnu.org/onlinedocs/gcc/Developer-Options.html#index-fopt-info).\n", + "\n", + "**TASK**: Have a looK at the `CFLAGS` of the `Makefile` target `poisson2d_Ofast_info`. Add the flag `-fopt-info-all` to the list of flags. This will print optimisation information to stdout. If you rather want to print to this information to a file, use \u2013\u00a0for example \u2013\u00a0`-fopt-info-all=(SC19_DIR_SCRATCH)/filename`." + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast -fopt-info-all poisson2d.c poisson2d_reference.o -o poisson2d_Ofast_info -lm\n", + "poisson2d.c:62:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "poisson2d.c:61:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "poisson2d.c:56:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "poisson2d.c:52:20: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "poisson2d.c:161:5: missed: not inlinable: main/33 -> free/38, function body not available\n", + "poisson2d.c:159:5: missed: not inlinable: main/33 -> free/38, function body not available\n", + "poisson2d.c:158:5: missed: not inlinable: main/33 -> free/38, function body not available\n", + "poisson2d.c:142:31: missed: not inlinable: main/33 -> printf/36, function body not available\n", + "poisson2d.c:103:5: missed: not inlinable: main/33 -> __builtin_puts/37, function body not available\n", + "poisson2d.c:96:5: missed: not inlinable: main/33 -> printf/36, function body not available\n", + "poisson2d.c:78:29: missed: not inlinable: main/33 -> exp/35, function body not available\n", + "poisson2d.c:68:41: missed: not inlinable: main/33 -> malloc/34, function body not available\n", + "poisson2d.c:67:41: missed: not inlinable: main/33 -> malloc/34, function body not available\n", + "poisson2d.c:65:41: missed: not inlinable: main/33 -> malloc/34, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "Unit growth for small function inlining: 207->207 (0%)\n", + "\n", + "Inlined 4 calls, eliminated 0 functions\n", + "\n", + "consider run-time aliasing test between *_84 and *_87\n", + "consider run-time aliasing test between *_92 and *_97\n", + "consider run-time aliasing test between *_104 and *_107\n", + "consider run-time aliasing test between *_111 and *_115\n", + "poisson2d.c:124:13: optimized: Loop 8 distributed: split to 0 loops and 1 library calls.\n", + "poisson2d.c:90:9: optimized: Loop 10 distributed: split to 0 loops and 1 library calls.\n", + "poisson2d.c:108:25: missed: couldn't vectorize loop\n", + "poisson2d.c:108:25: missed: not vectorized: loop nest containing two or more consecutive inner loops cannot be vectorized\n", + "poisson2d.c:136:9: missed: couldn't vectorize loop\n", + "poisson2d.c:136:9: missed: Loop costings may not be worthwhile.\n", + "poisson2d.c:131:9: missed: couldn't vectorize loop\n", + "poisson2d.c:131:9: missed: Loop costings may not be worthwhile.\n", + "poisson2d.c:122:9: missed: couldn't vectorize loop\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_543, _539, _549);\n", + "poisson2d.c:112:9: missed: couldn't vectorize loop\n", + "poisson2d.c:112:9: missed: not vectorized: control flow in loop.\n", + "poisson2d.c:114:13: optimized: loop vectorized using 16 byte vectors\n", + "poisson2d.c:88:5: missed: couldn't vectorize loop\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_528, 0, _531);\n", + "poisson2d.c:72:5: missed: couldn't vectorize loop\n", + "poisson2d.c:78:27: missed: not vectorized: complicated access pattern.\n", + "poisson2d.c:74:9: missed: couldn't vectorize loop\n", + "poisson2d.c:78:29: missed: not vectorized: relevant stmt not supported: _27 = exp (_21);\n", + "poisson2d.c:43:5: note: vectorized 1 loops in function.\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_528, 0, _531);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_543, _539, _549);\n", + "poisson2d.c:114:13: optimized: loop turned into non-loop; it never loops\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _187 = strtol (_1, 0B, 10);\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _189 = strtol (_2, 0B, 10);\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _193 = strtol (_3, 0B, 10);\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _191 = strtol (_4, 0B, 10);\n", + "poisson2d.c:65:41: missed: statement clobbers memory: A_153 = malloc (_7);\n", + "poisson2d.c:67:41: missed: statement clobbers memory: Anew_155 = malloc (_7);\n", + "poisson2d.c:68:41: missed: statement clobbers memory: rhs_157 = malloc (_7);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_528, 0, _531);\n", + "poisson2d.c:96:5: missed: statement clobbers memory: printf (\"Jacobi relaxation calculation: max %d iterations on %d x %d mesh\\n\", iter_max_130, ny_139, nx_195);\n", + "poisson2d.c:103:5: missed: statement clobbers memory: __builtin_puts (&\"Calculate current execution.\"[0]);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_543, _539, _549);\n", + "poisson2d.c:142:31: missed: statement clobbers memory: printf (\"%5d, %0.6f\\n\", iter_237, error_219);\n", + "poisson2d.c:158:5: missed: statement clobbers memory: free (rhs_202);\n", + "poisson2d.c:159:5: missed: statement clobbers memory: free (Anew_124);\n", + "poisson2d.c:161:5: missed: statement clobbers memory: free (A_123);\n", + "poisson2d.c:65:41: missed: statement clobbers memory: A_144 = malloc (8000000);\n", + "poisson2d.c:67:41: missed: statement clobbers memory: Anew_143 = malloc (8000000);\n", + "poisson2d.c:68:41: missed: statement clobbers memory: rhs_142 = malloc (8000000);\n" + ] + } + ], + "source": [ + "!make poisson2d_Ofast_info" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's compare this with the output during compilation when using profile-directed feedback from Task 1 B.\n", + "\n", + "**TASK**: \n", + "Adapt the `CFLAGS` of `poisson2d_ref_info` to include `-fopt-info-all` **and** the profile input of `-fprofile-use=\u2026` here. *(Be advised: Long output!)*" + ] + }, + { + "cell_type": "code", + "execution_count": 83, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast -fopt-info-all -fprofile-use=/gpfs/wolf/trn003/scratch/aherten/ -Ofast -fprofile-generate=/gpfs/wolf/trn003/scratch/aherten/ poisson2d.c -o poisson2d_train -lm \n", + "poisson2d.c:62:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "poisson2d.c:61:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "poisson2d.c:56:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "poisson2d.c:52:20: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "Increasing alignment of decl: __gcov0.main\n", + "poisson2d.c:164:1: missed: not inlinable: _GLOBAL__sub_D_00100_1_main/48 -> __gcov_exit/55, function body not available\n", + "poisson2d.c:164:1: missed: not inlinable: _GLOBAL__sub_I_00100_0_main/47 -> __gcov_init/54, function body not available\n", + "poisson2d.c:161:5: missed: not inlinable: main/33 -> free/38, function body not available\n", + "poisson2d.c:159:5: missed: not inlinable: main/33 -> free/38, function body not available\n", + "poisson2d.c:158:5: missed: not inlinable: main/33 -> free/38, function body not available\n", + "poisson2d.c:142:31: missed: not inlinable: main/33 -> printf/36, function body not available\n", + "poisson2d.c:103:5: missed: not inlinable: main/33 -> __builtin_puts/37, function body not available\n", + "poisson2d.c:96:5: missed: not inlinable: main/33 -> printf/36, function body not available\n", + "poisson2d.c:78:29: missed: not inlinable: main/33 -> exp/35, function body not available\n", + "poisson2d.c:68:41: missed: not inlinable: main/33 -> malloc/34, function body not available\n", + "poisson2d.c:67:41: missed: not inlinable: main/33 -> malloc/34, function body not available\n", + "poisson2d.c:65:41: missed: not inlinable: main/33 -> malloc/34, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "Unit growth for small function inlining: 295->295 (0%)\n", + "\n", + "Inlined 4 calls, eliminated 0 functions\n", + "\n", + "consider run-time aliasing test between *_84 and *_87\n", + "consider run-time aliasing test between *_92 and *_97\n", + "consider run-time aliasing test between *_104 and *_107\n", + "consider run-time aliasing test between *_111 and *_115\n", + "poisson2d.c:124:13: optimized: Loop 8 distributed: split to 0 loops and 1 library calls.\n", + "poisson2d.c:90:9: optimized: Loop 10 distributed: split to 0 loops and 1 library calls.\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_64, _135, _313);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_632, 0, _239);\n", + "poisson2d.c:108:25: missed: couldn't vectorize loop\n", + "poisson2d.c:108:25: missed: not vectorized: loop nest containing two or more consecutive inner loops cannot be vectorized\n", + "poisson2d.c:136:9: missed: couldn't vectorize loop\n", + "poisson2d.c:136:9: missed: Loop costings may not be worthwhile.\n", + "poisson2d.c:131:9: missed: couldn't vectorize loop\n", + "poisson2d.c:131:9: missed: Loop costings may not be worthwhile.\n", + "poisson2d.c:122:9: missed: couldn't vectorize loop\n", + "poisson2d.c:122:9: missed: not vectorized: control flow in loop.\n", + "poisson2d.c:112:9: missed: couldn't vectorize loop\n", + "poisson2d.c:112:9: missed: not vectorized: control flow in loop.\n", + "poisson2d.c:114:13: optimized: loop vectorized using 16 byte vectors\n", + "poisson2d.c:88:5: missed: couldn't vectorize loop\n", + "poisson2d.c:88:5: missed: not vectorized: control flow in loop.\n", + "poisson2d.c:72:5: missed: couldn't vectorize loop\n", + "poisson2d.c:72:5: missed: not vectorized: control flow in loop.\n", + "poisson2d.c:74:9: missed: couldn't vectorize loop\n", + "poisson2d.c:78:29: missed: not vectorized: relevant stmt not supported: _27 = exp (_21);\n", + "poisson2d.c:43:5: note: vectorized 1 loops in function.\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_632, 0, _239);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_64, _135, _313);\n", + "poisson2d.c:114:13: optimized: loop turned into non-loop; it never loops\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _187 = strtol (_1, 0B, 10);\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _189 = strtol (_2, 0B, 10);\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _193 = strtol (_3, 0B, 10);\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _191 = strtol (_4, 0B, 10);\n", + "poisson2d.c:65:41: missed: statement clobbers memory: A_153 = malloc (_7);\n", + "poisson2d.c:67:41: missed: statement clobbers memory: Anew_155 = malloc (_7);\n", + "poisson2d.c:68:41: missed: statement clobbers memory: rhs_157 = malloc (_7);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_632, 0, _239);\n", + "poisson2d.c:96:5: missed: statement clobbers memory: printf (\"Jacobi relaxation calculation: max %d iterations on %d x %d mesh\\n\", iter_max_337, ny_124, nx_286);\n", + "poisson2d.c:103:5: missed: statement clobbers memory: __builtin_puts (&\"Calculate current execution.\"[0]);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_64, _135, _313);\n", + "poisson2d.c:142:31: missed: statement clobbers memory: printf (\"%5d, %0.6f\\n\", iter_316, error_118);\n", + "poisson2d.c:158:5: missed: statement clobbers memory: free (rhs_127);\n", + "poisson2d.c:159:5: missed: statement clobbers memory: free (Anew_311);\n", + "poisson2d.c:161:5: missed: statement clobbers memory: free (A_122);\n", + "poisson2d.c:65:41: missed: statement clobbers memory: A_129 = malloc (8000000);\n", + "poisson2d.c:67:41: missed: statement clobbers memory: Anew_132 = malloc (8000000);\n", + "poisson2d.c:68:41: missed: statement clobbers memory: rhs_140 = malloc (8000000);\n", + "poisson2d.c:136:9: note: considering unrolling loop 7 at BB 53\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:136:9: optimized: loop unrolled 7 times (header execution count 9800)\n", + "poisson2d.c:131:9: note: considering unrolling loop 6 at BB 50\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:131:9: optimized: loop unrolled 7 times (header execution count 9800)\n", + "poisson2d.c:122:9: note: considering unrolling loop 5 at BB 47\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:122:9: optimized: loop unrolled 3 times (header execution count 9800)\n", + "poisson2d.c:118:25: note: considering unrolling loop 13 at BB 33\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:118:25: optimized: loop unrolled 3 times (header execution count 436550)\n", + "poisson2d.c:118:25: note: considering unrolling loop 9 at BB 30\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:112:9: note: considering unrolling loop 14 at BB 42\n", + "poisson2d.c:43:5: note: considering unrolling loop 4 at BB 40\n", + "poisson2d.c:108:25: note: considering unrolling loop 3 at BB 60\n", + "poisson2d.c:88:5: note: considering unrolling loop 2 at BB 23\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:88:5: optimized: loop unrolled 3 times (header execution count 100)\n", + "poisson2d.c:74:9: note: considering unrolling loop 11 at BB 12\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:74:9: optimized: loop unrolled 3 times (header execution count 9604)\n", + "poisson2d.c:72:5: note: considering unrolling loop 1 at BB 16\n", + "poisson2d.c:164:1: missed: statement clobbers memory: __gcov_init (&*.LPBX0);\n", + "poisson2d.c:164:1: missed: statement clobbers memory: __gcov_exit ();\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS ./poisson2d_train 100 100 100\n", + "Job <24908> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "libgcov profiling error:/gpfs/wolf/trn003/scratch/aherten//#autofs#nccsopen-svm1_home#aherten#SC19-Tutorial#3-Optimizing_POWER#Handson#Task1#poisson2d.gcda:overwriting an existing profile data with a different timestamp\n", + "Jacobi relaxation calculation: max 100 iterations on 100 x 100 mesh\n", + "Calculate current execution.\n", + " 0, 0.249490\n", + "echo `date` > /gpfs/wolf/trn003/scratch/aherten//.profile_generated\n", + "gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast -fopt-info-all -fprofile-use=/gpfs/wolf/trn003/scratch/aherten/ poisson2d.c poisson2d_reference.o -o poisson2d_ref_info -lm\n", + "poisson2d.c:62:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "poisson2d.c:61:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "poisson2d.c:56:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "poisson2d.c:52:20: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "poisson2d.c:161:5: missed: not inlinable: main/33 -> free/38, function body not available\n", + "poisson2d.c:159:5: missed: not inlinable: main/33 -> free/38, function body not available\n", + "poisson2d.c:158:5: missed: not inlinable: main/33 -> free/38, function body not available\n", + "poisson2d.c:142:31: missed: not inlinable: main/33 -> printf/36, function body not available\n", + "poisson2d.c:103:5: missed: not inlinable: main/33 -> __builtin_puts/37, function body not available\n", + "poisson2d.c:96:5: missed: not inlinable: main/33 -> printf/36, function body not available\n", + "poisson2d.c:78:29: missed: not inlinable: main/33 -> exp/35, function body not available\n", + "poisson2d.c:68:41: missed: not inlinable: main/33 -> malloc/34, function body not available\n", + "poisson2d.c:67:41: missed: not inlinable: main/33 -> malloc/34, function body not available\n", + "poisson2d.c:65:41: missed: not inlinable: main/33 -> malloc/34, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "Unit growth for small function inlining: 207->207 (0%)\n", + "\n", + "Inlined 4 calls, eliminated 0 functions\n", + "\n", + "consider run-time aliasing test between *_84 and *_87\n", + "consider run-time aliasing test between *_92 and *_97\n", + "consider run-time aliasing test between *_104 and *_107\n", + "consider run-time aliasing test between *_111 and *_115\n", + "poisson2d.c:124:13: optimized: Loop 8 distributed: split to 0 loops and 1 library calls.\n", + "poisson2d.c:90:9: optimized: Loop 10 distributed: split to 0 loops and 1 library calls.\n", + "poisson2d.c:108:25: missed: couldn't vectorize loop\n", + "poisson2d.c:108:25: missed: not vectorized: loop nest containing two or more consecutive inner loops cannot be vectorized\n", + "poisson2d.c:136:9: missed: couldn't vectorize loop\n", + "poisson2d.c:136:9: missed: Loop costings may not be worthwhile.\n", + "poisson2d.c:131:9: missed: couldn't vectorize loop\n", + "poisson2d.c:131:9: missed: Loop costings may not be worthwhile.\n", + "poisson2d.c:122:9: missed: couldn't vectorize loop\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_539, _535, _544);\n", + "poisson2d.c:112:9: missed: couldn't vectorize loop\n", + "poisson2d.c:112:9: missed: not vectorized: control flow in loop.\n", + "poisson2d.c:114:13: optimized: loop vectorized using 16 byte vectors\n", + "poisson2d.c:88:5: missed: couldn't vectorize loop\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_524, 0, _527);\n", + "poisson2d.c:72:5: missed: couldn't vectorize loop\n", + "poisson2d.c:78:27: missed: not vectorized: complicated access pattern.\n", + "poisson2d.c:74:9: missed: couldn't vectorize loop\n", + "poisson2d.c:78:29: missed: not vectorized: relevant stmt not supported: _27 = exp (_21);\n", + "poisson2d.c:43:5: note: vectorized 1 loops in function.\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_524, 0, _527);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_539, _535, _544);\n", + "poisson2d.c:114:13: optimized: loop turned into non-loop; it never loops\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _187 = strtol (_1, 0B, 10);\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _189 = strtol (_2, 0B, 10);\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _193 = strtol (_3, 0B, 10);\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _191 = strtol (_4, 0B, 10);\n", + "poisson2d.c:65:41: missed: statement clobbers memory: A_153 = malloc (_7);\n", + "poisson2d.c:67:41: missed: statement clobbers memory: Anew_155 = malloc (_7);\n", + "poisson2d.c:68:41: missed: statement clobbers memory: rhs_157 = malloc (_7);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_524, 0, _527);\n", + "poisson2d.c:96:5: missed: statement clobbers memory: printf (\"Jacobi relaxation calculation: max %d iterations on %d x %d mesh\\n\", iter_max_130, ny_139, nx_195);\n", + "poisson2d.c:103:5: missed: statement clobbers memory: __builtin_puts (&\"Calculate current execution.\"[0]);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_539, _535, _544);\n", + "poisson2d.c:142:31: missed: statement clobbers memory: printf (\"%5d, %0.6f\\n\", iter_237, error_219);\n", + "poisson2d.c:158:5: missed: statement clobbers memory: free (rhs_202);\n", + "poisson2d.c:159:5: missed: statement clobbers memory: free (Anew_124);\n", + "poisson2d.c:161:5: missed: statement clobbers memory: free (A_123);\n", + "poisson2d.c:65:41: missed: statement clobbers memory: A_144 = malloc (8000000);\n", + "poisson2d.c:67:41: missed: statement clobbers memory: Anew_143 = malloc (8000000);\n", + "poisson2d.c:68:41: missed: statement clobbers memory: rhs_142 = malloc (8000000);\n", + "poisson2d.c:136:9: note: considering unrolling loop 7 at BB 47\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:136:9: optimized: loop unrolled 7 times (header execution count 9800)\n", + "poisson2d.c:131:9: note: considering unrolling loop 6 at BB 44\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:131:9: optimized: loop unrolled 7 times (header execution count 9800)\n", + "poisson2d.c:122:9: note: considering unrolling loop 5 at BB 40\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:122:9: optimized: loop unrolled 7 times (header execution count 9701)\n", + "poisson2d.c:118:25: note: considering unrolling loop 13 at BB 27\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:118:25: optimized: loop unrolled 3 times (header execution count 436550)\n", + "poisson2d.c:118:25: note: considering unrolling loop 9 at BB 24\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:112:9: note: considering unrolling loop 14 at BB 37\n", + "poisson2d.c:43:5: note: considering unrolling loop 4 at BB 35\n", + "poisson2d.c:108:25: note: considering unrolling loop 3 at BB 51\n", + "poisson2d.c:88:5: note: considering unrolling loop 2 at BB 18\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:88:5: optimized: loop unrolled 7 times (header execution count 99)\n", + "poisson2d.c:74:9: note: considering unrolling loop 11 at BB 9\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:74:9: optimized: loop unrolled 3 times (header execution count 9604)\n", + "poisson2d.c:72:5: note: considering unrolling loop 1 at BB 14\n" + ] + } + ], + "source": [ + "!make poisson2d_ref_info" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Comparing the annotations generated of a plain `-Ofast` optimization level and the one generated at `-Ofast` and profile directed feedback, we observe that many more optimizations are possible due to profile information.\n", + "\n", + "For instance you will see annotations such as\n", + "```\n", + "poisson2d.c:114:25: optimized: loop unrolled 3 times (header execution count 436550)\n", + "```\n", + "\n", + "The execution count indicates the dynamic execution count of the node at runtime. This information determines which paths are hotter and subsequently facilitate additional optimizations." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### References\n", + "\n", + "1. https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html\n", + "2. https://perf.wiki.kernel.org/index.php/Tutorial" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[Back to Top](#top)\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Task 2:<a name=\"task2\"></a> Impact of Prefetching on Performance\n", + "\n", + "\n", + "### Overview\n", + "\n", + "* Study the difference of program execution time of different optimization levels with and without software prefetching.\n", + "* Verify the impact by measuring cache counters with and without prefetching.\n", + "* Learn how to modify contents of DSCR (*Data Stream Control Register*) using IBM XL compiler and study the impact with different values to DSCR. \n", + "\n", + "But first, lets change directory to that of Task 2" + ] + }, + { + "cell_type": "code", + "execution_count": 85, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/autofs/nccsopen-svm1_home/aherten/SC19-Tutorial/3-Optimizing_POWER/Handson/Task2\n" + ] + } + ], + "source": [ + "%cd ../Task2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Part A: Software Prefetching" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**TASK**: Look at the Makefile and work on the TODOs. \n", + "\n", + "- First generate a `-Ofast`-optimised binary and note down the performance in terms of cycles, seconds, and L3 misses. This is our baseline!\n", + "- Modify the `Makefile` to add the option for software prefetching (`-fprefetch-loop-arrays`). Compare performance of `-Ofast` with and without software prefetching" + ] + }, + { + "cell_type": "code", + "execution_count": 97, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "rm -f poisson2d poisson2d*.o\n" + ] + } + ], + "source": [ + "!make clean" + ] + }, + { + "cell_type": "code", + "execution_count": 88, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "make: `poisson2d' is up to date.\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", + "Job <24911> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "2.39user 0.01system 0:02.40elapsed 100%CPU (0avgtext+0avgdata 24256maxresident)k\n", + "0inputs+0outputs (0major+480minor)pagefaults 0swaps\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,r168a4 ./poisson2d\n", + "Job <24912> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "\n", + " Performance counter stats for './poisson2d':\n", + "\n", + " 8271503902 cycles:u \n", + " 481152478 r168a4:u \n", + "\n", + " 2.412224884 seconds time elapsed\n", + "\n" + ] + } + ], + "source": [ + "!make poisson2d CC=gcc\n", + "!make run\n", + "!make l3missstats" + ] + }, + { + "cell_type": "code", + "execution_count": 98, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc -std=c99 -DUSE_DOUBLE -Ofast -mcpu=power9 -mvsx -maltivec -fprefetch-loop-arrays -fprefetch-loop-arrays poisson2d.c -o poisson2d_pref -lm\n", + "cp poisson2d_pref poisson2d\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", + "Job <24919> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "1.92user 0.00system 0:01.93elapsed 99%CPU (0avgtext+0avgdata 24256maxresident)k\n", + "256inputs+0outputs (0major+480minor)pagefaults 0swaps\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,r168a4 ./poisson2d\n", + "Job <24920> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "\n", + " Performance counter stats for './poisson2d':\n", + "\n", + " 6586609284 cycles:u \n", + " 459879452 r168a4:u \n", + "\n", + " 1.925399505 seconds time elapsed\n", + "\n" + ] + } + ], + "source": [ + "!make poisson2d_pref CC=gcc\n", + "!make run\n", + "!make l3missstats" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**TASK**: Repeat the experiment with the `-O3` flag. Have a look at the `Makefile` and the outlined TODO. There's a position to easily adapt `-Ofast`\u2192`-O3`!" + ] + }, + { + "cell_type": "code", + "execution_count": 100, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc -std=c99 -DUSE_DOUBLE -O3 -mcpu=power9 -mvsx -maltivec poisson2d.c -o poisson2d -lm\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", + "Job <24923> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "4.73user 0.00system 0:04.73elapsed 99%CPU (0avgtext+0avgdata 24256maxresident)k\n", + "256inputs+0outputs (0major+479minor)pagefaults 0swaps\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,r168a4 ./poisson2d\n", + "Job <24924> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "\n", + " Performance counter stats for './poisson2d':\n", + "\n", + " 16445764669 cycles:u \n", + " 645094089 r168a4:u \n", + "\n", + " 4.792567763 seconds time elapsed\n", + "\n" + ] + } + ], + "source": [ + "!make poisson2d CC=gcc -B\n", + "!make run\n", + "!make l3missstats" + ] + }, + { + "cell_type": "code", + "execution_count": 101, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc -std=c99 -DUSE_DOUBLE -O3 -mcpu=power9 -mvsx -maltivec -fprefetch-loop-arrays poisson2d.c -o poisson2d_pref -lm\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", + "Job <24925> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "4.74user 0.00system 0:04.74elapsed 99%CPU (0avgtext+0avgdata 24256maxresident)k\n", + "0inputs+0outputs (0major+480minor)pagefaults 0swaps\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,r168a4 ./poisson2d\n", + "Job <24926> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "\n", + " Performance counter stats for './poisson2d':\n", + "\n", + " 16239159454 cycles:u \n", + " 631061431 r168a4:u \n", + "\n", + " 4.730144897 seconds time elapsed\n", + "\n" + ] + } + ], + "source": [ + "!make poisson2d_pref CC=gcc -B\n", + "!make run\n", + "!make l3missstats" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Do you notice the impact difference with optimization levels? At what optimization level does software prefetching help the most?" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "exercise": "solution" + }, + "source": [ + "Observing the results, we see that SW Prefetching seems to help at `-Ofast` but not at `-O3`. We can use the steps described in the the next section to verify that the compiler has not inserted any SW prefetch operations at`-O3` at all. That is because in the `-O3` binary the time is dominated by `__fmax` call which causes the compiler to come to the conclusion that whatever benefit we obtain by adding SW prefetch will be overshadowed by the penalty of `fmax()`\n", + "GCC may add further loop optimizations such as unrolling upon invocation of `\u2013fprefetch-loop-arrays`.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Part B: Analysis of Instructions\n", + "\n", + "Compilation of the `-Ofast` binary with the software prefetching flag causes the compiler to generate the `dcb*` instructions that prefetch memory values to L3." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**TASK**: \n", + "Run `$(SC19_SUBMIT_CMD) objdump -lSd` on each binary file (`-O3`, `-Ofast` with prefetch/no prefetch).\n", + "Look for instructions beginning with `dcb`\n", + "At what optimization levels does the compiler generate software prefetching instructions?" + ] + }, + { + "cell_type": "code", + "execution_count": 114, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc -std=c99 -DUSE_DOUBLE -Ofast -mcpu=power9 -mvsx -maltivec -fprefetch-loop-arrays poisson2d.c -o poisson2d_pref -lm\n" + ] + } + ], + "source": [ + "!make CC=gcc -B poisson2d_pref\n", + "!objdump -lSd ./poisson2d_pref > poisson2d.dis" + ] + }, + { + "cell_type": "code", + "execution_count": 116, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " 10000b28:\t2c d2 00 7c \tdcbt 0,r26\n", + " 10000b30:\t2c ba 00 7c \tdcbt 0,r23\n", + " 10000b38:\t2c b2 00 7c \tdcbt 0,r22\n", + " 10000b50:\t2c d2 00 7c \tdcbt 0,r26\n", + " 10000b58:\tec b9 00 7c \tdcbtst 0,r23\n", + " 10000b80:\t2c d2 00 7c \tdcbt 0,r26\n", + " 10000e64:\t2c 92 00 7c \tdcbt 0,r18\n", + " 10000e68:\t2c 9a 00 7c \tdcbt 0,r19\n", + " 10000e6c:\t2c a2 00 7c \tdcbt 0,r20\n", + " 10000e70:\t2c aa 00 7c \tdcbt 0,r21\n", + " 10000e7c:\t2c b2 00 7c \tdcbt 0,r22\n", + " 10000e80:\t2c d2 00 7c \tdcbt 0,r26\n", + " 10000e94:\tec b9 00 7c \tdcbtst 0,r23\n" + ] + } + ], + "source": [ + "!grep dcb poisson2d.dis" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Part C: Changing Values of DSCR via compiler flags\n", + "\n", + "This task requires using the IBM XL compiler. It should be already in your environment.\n", + "\n", + "\n", + "We saw the impact of software prefetching in the previous subsection. \n", + "In certain cases, tuning the hardware prefetcher through compiler options can also help improve performance. \n", + "In this exercise we shall see some compiler options that can be used to modify the DSCR value which controls aggressiveness of prefetching. It can be also used to turn off hardware prefetching. \n", + "\n", + "IBM XL compiler has an option `-qprefetch=dscr=<val>` that can be used for this purpose.\n", + "Compiling with `-qprefetch=dscr=1` turns off the prefetcher. One can give various values such as `-qprefetch=dscr=4`, `-qprefetch=dscr=7` etc. to control aggressiveness of prefetching.\n", + "\n", + "For this exercise we use `make CC=xlc_r` to illustrate the performance impact.\n", + " \n", + "\n", + "**Task** Generate a XL-compiled binary by compiling using the following cells. After you've generated a baseline, start editing the `Makefile`: Add `qprefetch=dscr=1` to the `CFLAGS` and rebuild the application and note the performance. Which one is faster? \n", + "\n", + "In general, applications benefit with the default settings of hardware DSCR register (`-qprefetch=dscr=0`). However, certain applications also benefit with prefetching turned off. \n", + "\n", + "It is to be noted that DSCR values are highly sensitive to the application. One value that works well for Application A may not help Application B. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Measure performance of the application compiled with XL at default DSCR value" + ] + }, + { + "cell_type": "code", + "execution_count": 117, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "xlc_r -std=c99 -DUSE_DOUBLE -Ofast -qarch=pwr9 -qtune=pwr9 -DINLINE_LIBS poisson2d.c -o poisson2d -lm\n", + " 1500-036: (I) The NOSTRICT option (default at OPT(3)) has the potential to alter the semantics of a program. Please refer to documentation on the STRICT/NOSTRICT option for more information.\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", + "Job <24927> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 50.149062\n", + " 200, 99.849327\n", + " 300, 149.352369\n", + " 400, 198.659746\n", + " 500, 247.773000\n", + " 600, 296.693652\n", + " 700, 345.423208\n", + " 800, 393.963155\n", + " 900, 442.314962\n", + "2.26user 0.00system 0:02.27elapsed 99%CPU (0avgtext+0avgdata 24256maxresident)k\n", + "256inputs+0outputs (0major+477minor)pagefaults 0swaps\n" + ] + } + ], + "source": [ + "!make CC=xlc_r -B poisson2d\n", + "!make run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Measure performance of the application compiled with XL with DSCR value turned off" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "xlc_r -std=c99 -DUSE_DOUBLE -Ofast -qarch=pwr9 -qtune=pwr9 -DINLINE_LIBS -qprefetch=dscr=1 poisson2d.c -o poisson2d_dscr -lm\n", + " 1500-036: (I) The NOSTRICT option (default at OPT(3)) has the potential to alter the semantics of a program. Please refer to documentation on the STRICT/NOSTRICT option for more information.\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", + "Job <24929> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "4.58user 0.00system 0:04.59elapsed 99%CPU (0avgtext+0avgdata 24192maxresident)k\n", + "0inputs+0outputs (0major+476minor)pagefaults 0swaps\n" + ] + } + ], + "source": [ + "!make poisson2d_dscr CC=xlc_r -B\n", + "!make run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Does Hardware prefetcher help this application? How much impact do you see when you turn off the hardware prefetcher? " + ] + }, + { + "cell_type": "markdown", + "metadata": { + "exercise": "solution" + }, + "source": [ + "The DSCR register controls the operation of the HW Prefetcher on POWER9. It can be modified in the command line by `ppc64_cpu --dscr=<value>`. However this needs admin privileges. IBM XL offers a compiler flag to set the value through the compiler. `-qprefetch=dscr=1` turns off the prefetcher. Observing the results we see that the performance without the HW prefetcher is twice as bad as that with default prefetching. So we can conclude that Prefetching helps the Jacobi application. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### References\n", + "\n", + "1. https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html\n", + "2. https://www.gnu.org/software/gcc/projects/prefetch.html\n", + "3. https://openpowerfoundation.org/?resource_lib=power-isa-version-3-0\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[Back to Top](#top)\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Task 3: OpenMP\n", + "<a name=\"task3\"></a>\n", + "\n", + "\n", + "### Overview\n", + "\n", + "We add OpenMP shared-memory parallelism to the application. Also, we study the effect of binding the multi-thread processes to certain cores on the resulting application performance. We do this study for both GCC and XL compilers inorder to learn about the appropriate options that need to be used.\n", + "First, we need to change directory to that of Task3. For Task 3 we modify poisson2d.c to invoke an exact copy of the main jacobi loop which is `poisson2d_reference`. We parallelize only the main loop but not `poisson2d_reference`. The speedup is the performance gain seen in the main loop as compared to the reference loop." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/autofs/nccsopen-svm1_home/aherten/SC19-Tutorial/3-Optimizing_POWER/Handson/Task3\n" + ] + } + ], + "source": [ + "%cd ../Task3" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Part A: Implement OpenMP Pragmas; Compilation\n", + "\n", + "**Task**: Please add the correct OpenMP directives to poisson2d.c and compilations flags in the Makefile to enable OpenMP with GCC and XL compilers.\n", + "\n", + "* **Directives**: Look at the TODOs in [`poisson2d.c`](poisson2d.c) to add OpenMP parallelism. The pragmas in question are `#pragma omp parallel for` (and once it's `#pragma omp parallel for reduction(max:error)` \u2013\u00a0can you guess where?)\n", + "* **Compilation**: Please add compilation flags enabling OpenMP in GCC and XL to the `Makefile`. For GCC, we need to add `-fopenmp` and the application needs to be linked with `-lgomp`. For XL, we need to add `-qsmp=omp` to the list of compilation flags. \n", + "\n", + "Afterwards, compile and run the application with the following commands." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc -c -std=c99 -DUSE_DOUBLE -O3 -mcpu=power9 -mvsx -maltivec -fopenmp -lgomp poisson2d_reference.c -o poisson2d_reference.o -lm\n", + "gcc -std=c99 -DUSE_DOUBLE -O3 -mcpu=power9 -mvsx -maltivec -fopenmp -lgomp poisson2d.c poisson2d_reference.o -o poisson2d -lm \n" + ] + } + ], + "source": [ + "!make poisson2d CC=gcc" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The command to submit a job to the batch system is prepared in an environment variable `$SC19_SUBMIT_CMD`; use it together with `eval`. In the following cell, it is shown how to invoke the application using the batch system. " + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Job <24951> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate reference solution and time with serial CPU execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "1000x1000: Ref: 4.7430 s, This: 3.9363 s, speedup: 1.20\n" + ] + } + ], + "source": [ + "!eval $SC19_SUBMIT_CMD ./poisson2d 1000 1000 1000" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Inorder to run the parallel application, we need to set the number of threads using `OMP_NUM_THREADS`\n", + "What is the best performance you can reach by setting the number of threads via `OMP_NUM_THREADS=N` with `N` being the number of threads? Feel free to play around with the command in the following cell, using 1 thread as an example. \n", + "We added `--bind none` to prevent `jsrun`, the scheduler of Ascent, from overlaying binding options. Also, we use `-c ALL_CPUS` to make all CPUs on the compute nodes available to you." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "1000x1000: Ref: 4.7288 s, This: 4.9791 s, speedup: 0.95\n" + ] + } + ], + "source": [ + "!OMP_NUM_THREADS=1 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep speedup " + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "1000x1000: Ref: 4.7125 s, This: 2.4914 s, speedup: 1.89\n" + ] + } + ], + "source": [ + "!OMP_NUM_THREADS=2 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep speedup " + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "1000x1000: Ref: 2.1065 s, This: 1.3836 s, speedup: 1.52\n" + ] + } + ], + "source": [ + "!OMP_NUM_THREADS=4 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep speedup " + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "1000x1000: Ref: 2.3868 s, This: 0.5272 s, speedup: 4.53\n" + ] + } + ], + "source": [ + "!OMP_NUM_THREADS=8 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep speedup " + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "1000x1000: Ref: 2.3912 s, This: 0.4612 s, speedup: 5.18\n" + ] + } + ], + "source": [ + "!OMP_NUM_THREADS=10 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep speedup " + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "1000x1000: Ref: 2.3864 s, This: 0.4037 s, speedup: 5.91\n" + ] + } + ], + "source": [ + "!OMP_NUM_THREADS=20 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep speedup " + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "1000x1000: Ref: 2.3773 s, This: 0.3045 s, speedup: 7.81\n" + ] + } + ], + "source": [ + "!OMP_NUM_THREADS=40 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep speedup " + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "1000x1000: Ref: 2.3819 s, This: 0.3081 s, speedup: 7.73\n" + ] + } + ], + "source": [ + "!OMP_NUM_THREADS=80 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep speedup " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Part B: Bindings\n", + "\n", + "Different CPU architectures and models come with different configuration of cores. The configuration plays an important role in the run time of the application. We need to optimize for it!\n", + "\n", + "There are applications which can be used to determine the configuration of the processor. Among those are:\n", + "\n", + "* `lscpu`: Can be used to determine the number of sockets, number of cores, and numb of threads. It gives a very good overview and is available on most Linux systems.\n", + "* `ppc64_cpu --smt`: Specifically for POWER, this tool can give information about the number of simulations threads running per core (*SMT*, Simulataion Multi-Threading).\n", + "\n", + "Run `ppc64_cpu --smt` to find out about the threading configuration of Ascent!" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Job <24465> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "SMT=4\n" + ] + } + ], + "source": [ + "!eval $SC19_SUBMIT_CMD ppc64_cpu --smt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are more sources information available\n", + "\n", + "* `/proc/cpuinfo`: Holds information about virtual cores, including model and clock speed. Available on most Linux system. Usually used together with `cat`\n", + "* `/sys/devices/system/cpu/cpu0/topology/thread_siblings_list`: Holds information about thread siblings for given CPU core (`cpu0` in this case). Use it to find out which thread is mapped to which core." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Job <24949> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "0-3\n", + "Job <24950> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "4-7\n" + ] + } + ], + "source": [ + "!$$SC19_SUBMIT_CMD cat /sys/devices/system/cpu/cpu0/topology/thread_siblings_list\n", + "!$$SC19_SUBMIT_CMD cat /sys/devices/system/cpu/cpu5/topology/thread_siblings_list" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are various environment variables available within OpenMP (some specific to GCC) that hold across compilers to specify binding of threads to cores. See, for instance, the [OMP_PLACES environment Variable](https://www.openmp.org/spec-html/5.0/openmpse53.html). We also have a GNU specific variable which can also be used to control affinity - `GOMP_CPU_AFFINITY`. Setting `GOMP_CPU_AFFINITY` is specific to GCC binaries but it internally serves the same function as setting `OMP_PLACES`. \n", + "\n", + "**Task**: Run the application enabled with OpenMP from Part A with different binding configurations. Make sure to at least run a) binding all threads to a single core and b) binding threads to different cores.\n", + "\n", + "Adapt the following command with your configuration \u2013 or follow along accordingly in the non-interactive version of the Notebook.\n", + "\n", + "What's your maximum speedup?" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "exercise": "solution" + }, + "source": [ + "Running with two different configurations 1) Binding all threads to the same core 2) Binding all threads to different cores, we see a higher speedup in case of binding all threads to different cores." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "exercise": "solution" + }, + "source": [ + "Using `OMP_PLACES` for binding, and using some magical Python-Bash interplay:" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Affinity: {0},{1},{2},{3}\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + " OMP_PLACES = '{0},{1},{2},{3}'\n", + "1000x1000: Ref: 4.7315 s, This: 3.9090 s, speedup: 1.21\n", + "Affinity: {0},{5},{9},{13}\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + " OMP_PLACES = '{0},{5},{9},{13}'\n", + "1000x1000: Ref: 4.6485 s, This: 1.2829 s, speedup: 3.62\n" + ] + } + ], + "source": [ + "for affinity in [\"{0},{1},{2},{3}\", \"{0},{5},{9},{13}\"]:\n", + " print(\"Affinity: {}\".format(affinity))\n", + " !eval OMP_DISPLAY_ENV=true OMP_PLACES=$affinity OMP_NUM_THREADS=4 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep \"OMP_PLACES\\|speedup\"" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "exercise": "solution" + }, + "source": [ + "In this case, we carry out the same experiment using `GOMP_CPU_AFFINITY` which essentially sets the same environment variable `OMP_PLACES`. Running with two different configurations 1) Binding all threads to the same core 2) Binding all threads to different cores, we see a higher speedup in case of binding all threads to different cores." + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Affinity: 0,1,2,3\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + " OMP_PLACES = '{0},{1},{2},{3}'\n", + "1000x1000: Ref: 2.3964 s, This: 2.1361 s, speedup: 1.12\n", + "Affinity: 0,5,9,13\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + " OMP_PLACES = '{0},{5},{9},{13}'\n", + "1000x1000: Ref: 2.3925 s, This: 0.7030 s, speedup: 3.40\n" + ] + } + ], + "source": [ + "for affinity in [\"0,1,2,3\", \"0,5,9,13\"]:\n", + " print(\"Affinity: {}\".format(affinity))\n", + " !eval OMP_DISPLAY_ENV=true GOMP_CPU_AFFINITY=$affinity OMP_NUM_THREADS=4 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep \"OMP_PLACES\\|speedup\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Great!\n", + "\n", + "If you still have time: The same experiments can be repeated with the IBM XL compiler. \n", + "The corresponding compiler flag to enable OpenMP parallelism that needs to be used for XL is `-qsmp=omp`\n", + "\n", + "**Task**: In the Makefile add the OpenMP flag and generate XL binaries with OpenMP and run the application with various number of threads and note the performance speedup." + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "xlc_r -c -std=c99 -DUSE_DOUBLE -O3 -qhot -qtune=pwr9 -DINLINE_LIBS -qsmp=omp poisson2d_reference.c -o poisson2d_reference.o -lm \n", + " 1500-036: (I) The NOSTRICT option (default at OPT(3)) has the potential to alter the semantics of a program. Please refer to documentation on the STRICT/NOSTRICT option for more information.\n", + "xlc_r -std=c99 -DUSE_DOUBLE -O3 -qhot -qtune=pwr9 -DINLINE_LIBS -qsmp=omp poisson2d.c poisson2d_reference.o -o poisson2d -lm\n", + " 1500-036: (I) The NOSTRICT option (default at OPT(3)) has the potential to alter the semantics of a program. Please refer to documentation on the STRICT/NOSTRICT option for more information.\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", + "Job <24956> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate reference solution and time with serial CPU execution.\n", + " 0, 0.249995\n", + " 100, 50.149062\n", + " 200, 99.849327\n", + " 300, 149.352369\n", + " 400, 198.659746\n", + " 500, 247.773000\n", + " 600, 296.693652\n", + " 700, 345.423208\n", + " 800, 393.963155\n", + " 900, 442.314962\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 50.149062\n", + " 200, 99.849327\n", + " 300, 149.352369\n", + " 400, 198.659746\n", + " 500, 247.773000\n", + " 600, 296.693652\n", + " 700, 345.423208\n", + " 800, 393.963155\n", + " 900, 442.314962\n", + "1000x1000: Ref: 5.6783 s, This: 2.6528 s, speedup: 2.14\n", + "21.56user 6.18system 0:08.37elapsed 331%CPU (0avgtext+0avgdata 23040maxresident)k\n", + "3200inputs+0outputs (2major+1098minor)pagefaults 0swaps\n" + ] + } + ], + "source": [ + "!make CC=xlc_r -B run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Run the parallel application with varying numbre of threads (`OMP_NUM_THREADS`) and note the performance improvement. " + ] + }, + { + "cell_type": "markdown", + "metadata": { + "exercise": "solution" + }, + "source": [ + "Just as in the GCC binary we see a similar speedup with higher number of threads until a certain point beyond which the benefit tapers off. " + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "1000x1000: Ref: 2.2561 s, This: 2.6432 s, speedup: 0.85\n" + ] + } + ], + "source": [ + "!OMP_NUM_THREADS=1 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep speedup " + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "1000x1000: Ref: 2.3071 s, This: 1.5343 s, speedup: 1.50\n" + ] + } + ], + "source": [ + "!OMP_NUM_THREADS=2 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep speedup " + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "1000x1000: Ref: 2.2617 s, This: 0.6936 s, speedup: 3.26\n" + ] + } + ], + "source": [ + "!OMP_NUM_THREADS=4 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep speedup " + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "1000x1000: Ref: 2.2728 s, This: 0.3402 s, speedup: 6.68\n" + ] + } + ], + "source": [ + "!OMP_NUM_THREADS=8 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep speedup " + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "1000x1000: Ref: 2.1678 s, This: 0.2869 s, speedup: 7.56\n" + ] + } + ], + "source": [ + "!OMP_NUM_THREADS=10 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep speedup " + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "1000x1000: Ref: 2.2813 s, This: 0.1452 s, speedup: 15.71\n" + ] + } + ], + "source": [ + "!OMP_NUM_THREADS=20 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep speedup " + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "1000x1000: Ref: 2.3284 s, This: 0.0981 s, speedup: 23.75\n" + ] + } + ], + "source": [ + "!OMP_NUM_THREADS=40 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep speedup " + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "1000x1000: Ref: 2.2918 s, This: 0.1439 s, speedup: 15.92\n" + ] + } + ], + "source": [ + "!OMP_NUM_THREADS=80 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep speedup " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we repeat the exercise of using the right binding of threads for the XL binary. `OMP_PLACES` pertains to the XL binary as well as it is an OpenMP variable. `GOMP_CPU_AFFINITY` is specific to GCC binary so that cannot be used to set the binding.\n", + "\n", + "**Task**: Run the application enabled with OpenMP from Part A with different binding configurations. Make sure to at least run a) binding all threads to a single core and b) binding threads to different cores.\n", + "\n", + "Adapt the following command with your configuration \u2013 or follow along accordingly in the non-interactive version of the Notebook.\n", + "\n", + "We are mixing Python with Bash (`!`) here, so don't get confused (because of this, if we want to use Bash environment variables, we need to use two `$$`)\n", + "\n", + "What's your maximum speedup?" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Affinity: {0},{1},{2},{3}\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + " OMP_PLACES='{0},{1},{2},{3}' custom\n", + "1000x1000: Ref: 5.9792 s, This: 2.4122 s, speedup: 2.48\n", + "Affinity: {0},{5},{9},{13}\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + " OMP_PLACES='{0},{5},{9},{13}' custom\n", + "1000x1000: Ref: 2.3101 s, This: 0.6884 s, speedup: 3.36\n" + ] + } + ], + "source": [ + "for affinity in [\"{0},{1},{2},{3}\", \"{0},{5},{9},{13}\"]:\n", + " print(\"Affinity: {}\".format(affinity))\n", + " !eval OMP_DISPLAY_ENV=true OMP_PLACES=$affinity OMP_NUM_THREADS=4 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep \"OMP_PLACES\\|speedup\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Likewise we see a higher speedup when we bind the threads to different cores rather than to a single core. This handson illustrates that apart from compiler level tuning, system level tuning is also equally important to obtain performance improvements \n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### References\n", + "1. https://gcc.gnu.org/onlinedocs/libgomp/GOMP_005fCPU_005fAFFINITY.html\n", + "2. https://www.openmp.org/spec-html/5.0/openmpse53.html" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[Back to Top](#top)\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Survey<a name=\"survey\"></a>\n", + "\n", + "Please rememeber to take some time and fill out the [survey](http://bit.ly/sc19-eval)." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "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.7.0" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/3-Optimizing_POWER/Handson/.master/HandsOnPerformanceOptimization-task.ipynb b/3-Optimizing_POWER/Handson/.master/HandsOnPerformanceOptimization-task.ipynb index b9f231f..3b02952 100644 --- a/3-Optimizing_POWER/Handson/.master/HandsOnPerformanceOptimization-task.ipynb +++ b/3-Optimizing_POWER/Handson/.master/HandsOnPerformanceOptimization-task.ipynb @@ -1,1206 +1,2085 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Hands-On Performance Optimization\n", - "_Supercomputing 2018 Tutorial \"Application Porting and Optimization on GPU-Accelerated POWER Architectures\", November 12th 2018_\n", - "\n", - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As for the first task of this tutorial, also this task is primarily designed to be executed as an interactive Jupyter Notebook. However, everything can also be done using an SSH connection to Ascent (or any other POWER9 computer) in your terminal.\n", - "\n", - "## Jupyter notebook execution\n", - "\n", - "When using Jupyter, this Notebook will guide you through the steps. Note that if you execute a cell multiple times while optimizng the code the output will be replaced. You can however duplicate the cell you want to execute and keep its output. Check the _edit_ menu above.\n", - "\n", - "You will always find links to a file browser of the corresponding task subdirectory as well as direct links to the source files you will need to edit as well as the profiling output you need to open locally.\n", - "\n", - "If you want you also can get a [terminal](/terminals/1) in your browser.\n", - "\n", - "## Terminal fallback\n", - "\n", - "The tasks are place in directories named `Task[1-3]`.\n", - "\n", - "Makefile targets are created to cover everything, from compile, to run and profile. Please take a look at the cells containing the make calls as a guide also for the non-interactive version of this description." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Setup\n", - "\n", - "This hands-on session requires of GCC 6.4.0. By loading the `sc18/handson2` module before invoking this Notebook, we took care of also loading GCC 6.4.0 into the environment." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Tasks<a name=\"top\"></a>\n", - "\n", - "This session comes with multiple tasks, each one to be found in the respective sub-directory `Task[1-3]`. In each of these directories you will also find Makefiles that are set up so that you can compile and submit all necessary tasks.\n", - "\n", - "Please choose from the task below.\n", - "\n", - "\n", - "* [Task 1](#task1): Compile Flags \n", - "Improve performance of the CPU Jacobi solver with compiler flags such as `Ofast` and profile-directed feedback ([Solution 1](#solution0))\n", - "\n", - "* [Task 2](#task2): Software Prefetching \n", - "Improve performance of the CPU Jacobi solver with software prefetching ([Solution 2](#solution1))\n", - "\n", - "* [Task 3](#task3): OpenMP \n", - "Parallelize the CPU Jacobi solver and determine the right binding to be used for optimal performance ([Solution 3](#solution2))\n", - " \n", - "* [Suvery](#survey) Please remember to take the survey !\n", - " \n", - "### Make Targets <a name=\"make\"></a>\n", - "\n", - "For all tasks we have defined the following make targets. \n", - "\n", - "* __poisson2d__: \n", - " build `poisson2d` binary (default)\n", - "* __run__: \n", - " run `poisson2d` with default parameters\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[Back to Top](#top)\n", - "\n", - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Task 1: Compile Flags <a name=\"task1\"></a>\n", - "\n", - "\n", - "### Overview\n", - "\n", - "The goal of this task is to understand different options available to optimize the performance of the CPU Jacobi solver \n", - "\n", - "Your task is to:\n", - "\n", - "* Optimize performance with `-Ofast` flag\n", - "* Optimize performance with profile directed feedback \n", - "\n", - "First, change the working directory to `Task1`." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "/autofs/nccsopen-svm1_home/aherten/SC18-Tutorial/3-Optimizing_POWER/Handson/Task1\n" - ] - } - ], - "source": [ - "%cd Task1" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Part A: `-Ofast` vs. `-O3`\n", - "\n", - "We are to compare the performance of the binary being compiled with `-Ofast` optimization and with `-O3` optimization. Right now, the Makefile specifies `-O3` as the optimization flag. Compile the code using `make` and run it with `make run` in the next two cells." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "/sw/ascent/gcc/6.4.0/bin/gcc -c -std=c99 -mcpu=power9 -O3 -DUSE_DOUBLE -mvsx -maltivec -fopenmp poisson2d_reference.c -o poisson2d_reference.o -lm\n", - "/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -O3 -DUSE_DOUBLE -mvsx -maltivec -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d -lm\n" - ] - } - ], - "source": [ - "!make" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", - "Job <5033> is submitted to default queue <batch>.\n", - "<<Waiting for dispatch ...>>\n", - "<<Starting on login1>>\n", - "Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", - "Calculate reference solution and time with serial CPU execution.\n", - " 0, 0.249980\n", - " 100, 0.246028\n", - " 200, 0.242198\n", - " 300, 0.238487\n", - " 400, 0.234887\n", - "Calculate current execution.\n", - " 0, 0.249980\n", - " 100, 0.246028\n", - " 200, 0.242198\n", - " 300, 0.238487\n", - " 400, 0.234887\n", - "1.13user 0.00system 0:01.15elapsed 97%CPU (0avgtext+0avgdata 10944maxresident)k\n", - "2560inputs+0outputs (1major+264minor)pagefaults 0swaps\n" - ] - } - ], - "source": [ - "!make run" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can use the GNU _perf_ tool to profile the application using the `perf` command (see below) and see the top time-consuming functions." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", - "Calculate reference solution and time with serial CPU execution.\n", - " 0, 0.249980\n", - " 100, 0.246028\n", - " 200, 0.242198\n", - " 300, 0.238487\n", - " 400, 0.234887\n", - "Calculate current execution.\n", - " 0, 0.249980\n", - " 100, 0.246028\n", - " 200, 0.242198\n", - " 300, 0.238487\n", - " 400, 0.234887\n", - "[ perf record: Woken up 1 times to write data ]\n", - "[ perf record: Captured and wrote 0.172 MB perf.O3.data (4125 samples) ]\n", - "# To display the perf.data header info, please use --header/--header-only options.\n", - "#\n", - "#\n", - "# Total Lost Samples: 0\n", - "#\n", - "# Samples: 4K of event 'cycles:u'\n", - "# Event count (approx.): 3867635297\n", - "#\n", - "# Overhead Command Shared Object Symbol \n", - "# ........ ......... ................. ........................................\n", - "#\n", - " 72.02% poisson2d poisson2d [.] 00000040.plt_call.fmax@@GLIBC_2.17\n", - " 10.16% poisson2d poisson2d [.] poisson2d_reference\n", - " 9.99% poisson2d poisson2d [.] main\n", - " 4.69% poisson2d libc-2.17.so [.] __memcpy_power7\n", - " 2.23% poisson2d libm-2.17.so [.] __fmaxf\n", - " 0.75% poisson2d libm-2.17.so [.] __exp_finite\n", - " 0.07% poisson2d poisson2d [.] 00000040.plt_call.memcpy@@GLIBC_2.17\n", - " 0.02% poisson2d poisson2d [.] check_results\n", - " 0.02% poisson2d libm-2.17.so [.] __GI___exp\n", - " 0.01% poisson2d ld-2.17.so [.] _dl_relocate_object\n", - " 0.01% poisson2d [kernel.kallsyms] [k] arch_local_irq_restore\n", - " 0.00% poisson2d ld-2.17.so [.] _dl_new_object\n", - " 0.00% poisson2d ld-2.17.so [.] _start\n", - "\n", - "\n", - "#\n", - "# (Tip: Show user configuration overrides: perf config --user --list)\n", - "#\n" - ] - } - ], - "source": [ - "# perf record creates a perf.data file \n", - "!perf record -o perf.O3.data -e cycles ./poisson2d\n", - "# perf report opens the perf.data file \n", - "!perf report -i perf.O3.data | cat" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**TASK**: Now change the optimization flag in the [Makefile](/edit/Task1/Makefile) to `-Ofast` and repeat the steps in the following cell. In case you follow along non-interactive, call `make` and `make run` in your shell. (If you are in the Jupyter Notebook, you can actually click the link of the [Makefile](/edit/Task1/Makefile). In other cases, use `vim` which is installed on Ascent.)" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "/sw/ascent/gcc/6.4.0/bin/gcc -c -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec -fopenmp poisson2d_reference.c -o poisson2d_reference.o -lm\n", - "/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d -lm\n" - ] - } - ], - "source": [ - "!make" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", - "Job <5034> is submitted to default queue <batch>.\n", - "<<Waiting for dispatch ...>>\n", - "<<Starting on login1>>\n", - "Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", - "Calculate reference solution and time with serial CPU execution.\n", - " 0, 0.249980\n", - " 100, 0.246028\n", - " 200, 0.242198\n", - " 300, 0.238487\n", - " 400, 0.234887\n", - "Calculate current execution.\n", - " 0, 0.249980\n", - " 100, 0.246028\n", - " 200, 0.242198\n", - " 300, 0.238487\n", - " 400, 0.234887\n", - "0.51user 0.00system 0:00.52elapsed 99%CPU (0avgtext+0avgdata 10816maxresident)k\n", - "256inputs+0outputs (0major+264minor)pagefaults 0swaps\n" - ] - } - ], - "source": [ - "!make run" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", - "Calculate reference solution and time with serial CPU execution.\n", - " 0, 0.249980\n", - " 100, 0.246028\n", - " 200, 0.242198\n", - " 300, 0.238487\n", - " 400, 0.234887\n", - "Calculate current execution.\n", - " 0, 0.249980\n", - " 100, 0.246028\n", - " 200, 0.242198\n", - " 300, 0.238487\n", - " 400, 0.234887\n", - "[ perf record: Woken up 1 times to write data ]\n", - "[ perf record: Captured and wrote 0.086 MB perf.Ofast.data (1889 samples) ]\n", - "# To display the perf.data header info, please use --header/--header-only options.\n", - "#\n", - "#\n", - "# Total Lost Samples: 0\n", - "#\n", - "# Samples: 1K of event 'cycles:u'\n", - "# Event count (approx.): 1765737747\n", - "#\n", - "# Overhead Command Shared Object Symbol \n", - "# ........ ......... ............. .......................\n", - "#\n", - " 44.65% poisson2d poisson2d [.] main\n", - " 43.84% poisson2d poisson2d [.] poisson2d_reference\n", - " 10.28% poisson2d libc-2.17.so [.] __memcpy_power7\n", - " 1.12% poisson2d libm-2.17.so [.] __exp_finite\n", - " 0.05% poisson2d poisson2d [.] check_results\n", - " 0.03% poisson2d ld-2.17.so [.] _dl_relocate_object\n", - " 0.02% poisson2d libc-2.17.so [.] __readdir64\n", - " 0.01% poisson2d ld-2.17.so [.] _dl_new_object\n", - " 0.00% poisson2d ld-2.17.so [.] _start\n", - "\n", - "\n", - "#\n", - "# (Tip: System-wide collection from all CPUs: perf record -a)\n", - "#\n" - ] - } - ], - "source": [ - "# perf record creates a perf.data file \n", - "!perf record -o perf.Ofast.data -e cycles ./poisson2d\n", - "# perf report opens the perf.data file \n", - "!perf report -i perf.Ofast.data | cat" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If `perf` is unavailable to you on other machines, you can also study the disassembly with `objdump`: `objdump -lSd ./poisson2d > poisson2d.dis` (feel free to experiment with this in the Notebook as well, just prefix the command with a `!` to execute it.)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Interpretation\n", - "\n", - "Depending on the application requirement, if a high precision of results is not mandatory, the users can compile an application with `-Ofast` which enables `\u2013ffast-math` option that implements the same math function in a relaxed manner very similar to how general mathematical expressions are implemented and avoids the overhead of calling a function from the math library. Comparing the files, you will see that the `-Ofast` binary natively implements the `fmax` function using instructions available in the hardware. The `-O3` binary makes a library call to compute `fmax` to follow a stricter _IEEE_ requirement for accuracy." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Part B: Profile-directed Feedback\n", - "\n", - "For the first level of optimization we saw `Ofast` cut the execution time of the `O3` binary by almost half.\n", - "\n", - "We can optimize the performance further by using profile directed feedback optimization.\n", - "\n", - "To compile using profile directed feedback with the GCC compiler we need to do the following steps\n", - "\n", - "1. We need to first build a training binary using `-fprofile-generate`; this instructs the compiler to record hot path information \n", - "2. Run the training binary with a smaller input size; you should see a `.gcda` file generated which stores hot path information for further optimization by the compiler \n", - "3. build the final binary using `-fprofile-use` which uses the profile information in the `.gcda` file \n", - "4. Compare the performance of the final binary with the original `Ofast` binary \n", - "\n", - "**TASK**: First, search for `TODO1` in the [Makefile](/edit/Task1/Makefile). It defines an additional compilation flag for `gcc`. Insert `-fprofile-generate=FOLDER` there with FOLDER pointing to `$$SC18_DIR_SCRATCH`, your personal write-directory (the double dollar signs are intentional as they are used to escape in the GNU Make syntax).\n", - "\n", - "After editing, run the following two cells to train your program." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "/sw/ascent/gcc/6.4.0/bin/gcc -c -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec \"-fprofile-generate=$SC18_DIR_SCRATCH\" -fopenmp poisson2d_reference.c -o poisson2d_reference.o -lm\n", - "/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec \"-fprofile-generate=$SC18_DIR_SCRATCH\" -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d_train -lm\n" - ] - } - ], - "source": [ - "!make poisson2d_train" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d_train 200 64 64\n", - "Job <5035> is submitted to default queue <batch>.\n", - "<<Waiting for dispatch ...>>\n", - "<<Starting on login1>>\n", - "Jacobi relaxation calculation: max 200 iterations on 64 x 64 mesh\n", - "Calculate reference solution and time with serial CPU execution.\n", - " 0, 0.248743\n", - " 100, 0.124046\n", - "Calculate current execution.\n", - " 0, 0.248743\n", - " 100, 0.124046\n", - "0.00user 0.00system 0:00.10elapsed 5%CPU (0avgtext+0avgdata 5248maxresident)k\n", - "512inputs+0outputs (0major+115minor)pagefaults 0swaps\n", - "mv $SC18_DIR_SCRATCH/*.gcda .\n" - ] - } - ], - "source": [ - "!make run_train" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, a `.gcda` file exists in the directory which can be used for an profile-accelerated subsequent run.\n", - "\n", - "**TASK**: Edit the [Makefile](/edit/Task1/Makefile) again, this time modifying `TODO2` to be equivalent to `-fprofile-use`. A directory is not needed as we copied the gcda file into the current directory.\n", - "\n", - "Run the following cells in order to build using the newly added flag and then run with the profile-accelerated version." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "/sw/ascent/gcc/6.4.0/bin/gcc -c -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec \"-fprofile-use\" -fopenmp poisson2d_reference.c -o poisson2d_reference.o -lm\n", - "/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec \"-fprofile-use\" -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d_profile -lm\n" - ] - } - ], - "source": [ - "!make poisson2d_profile" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d_profile\n", - "Job <5036> is submitted to default queue <batch>.\n", - "<<Waiting for dispatch ...>>\n", - "<<Starting on login1>>\n", - "Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", - "Calculate reference solution and time with serial CPU execution.\n", - " 0, 0.249980\n", - " 100, 0.246028\n", - " 200, 0.242198\n", - " 300, 0.238487\n", - " 400, 0.234887\n", - "Calculate current execution.\n", - " 0, 0.249980\n", - " 100, 0.246028\n", - " 200, 0.242198\n", - " 300, 0.238487\n", - " 400, 0.234887\n", - "0.47user 0.00system 0:00.48elapsed 98%CPU (0avgtext+0avgdata 10816maxresident)k\n", - "256inputs+0outputs (0major+265minor)pagefaults 0swaps\n" - ] - } - ], - "source": [ - "!make run_profile" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "What is your speed-up? Feel free to run with larger problem sizes (mesh; iterations)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### References\n", - "\n", - "1. https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html\n", - "2. https://perf.wiki.kernel.org/index.php/Tutorial" - ] - }, + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Hands-On Performance Optimization\n", + "_Supercomputing 2019 Tutorial \"Application Porting and Optimization on GPU-Accelerated POWER Architectures\", November 18th 2019_\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As for the first task of this tutorial, also this task is primarily designed to be executed as an interactive Jupyter Notebook. However, everything can also be done using an SSH connection to Ascent (or any other POWER9 computer) in your terminal.\n", + "\n", + "## Jupyter notebook execution\n", + "\n", + "When using Jupyter, this Notebook will guide you through the steps. Note that if you execute a cell multiple times while optimizng the code the output will be replaced. You can however duplicate the cell you want to execute and keep its output. Check the _edit_ menu above.\n", + "\n", + "You will always find links to a file browser of the corresponding task subdirectory as well as direct links to the source files you will need to edit as well as the profiling output you need to open locally.\n", + "\n", + "If you want you also can get a terminal in your browser; just open it via the \u00bbNew Launcher\u00ab button (`+`).\n", + "\n", + "## Terminal fallback\n", + "\n", + "The tasks are place in directories named `Task[1-3]`.\n", + "\n", + "Makefile targets are created to cover everything, from compile, to run and profile. Please take a look at the cells containing the make calls as a guide also for the non-interactive version of this description." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "We are using some very fresh compiler features and use GCC 9.2.0 because of that. It should already be in your environment. Let's check!" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[Back to Top](#top)\n", - "\n", - "---" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc (GCC) 9.2.0\n", + "Copyright (C) 2019 Free Software Foundation, Inc.\n", + "This is free software; see the source for copying conditions. There is NO\n", + "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n", + "\n" + ] + } + ], + "source": [ + "!gcc --version" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Tasks<a name=\"top\"></a>\n", + "\n", + "This session comes with multiple tasks, each one to be found in the respective sub-directory `Task[1-3]`. In each of these directories you will also find Makefiles that are set up so that you can compile and submit all necessary tasks.\n", + "\n", + "Please choose from the task below.\n", + "\n", + "\n", + "* [Task 1](#task1): __Basic compiler optimization flags and compiler annotations__\n", + "\n", + "Improve performance of the CPU Jacobi solver with compiler flags such as `Ofast` and profile-directed feedback. Learn about compiler annotations.\n", + "\n", + "* [Task 2](#task2): __Optimization via Prefetching controlled by compiler__\n", + "\n", + "Improve performance of the CPU Jacobi solver with software prefetching. Some compilers such as IBM XL define flags that can be used to modify the aggressiveness of the hardware prefetcher. Learn to modify the DSCR value through XL and study the impact on application performance. \n", + "* [Task 3](#task3): __Optimization via OpenMP controlled by compiler and the system__\n", + "\n", + "Parallelize the CPU Jacobi solver and determine the right binding to be used for optimal performance. \n", + " \n", + "* [Suvery](#survey) Please remember to take the survey !\n", + " \n", + "### Make Targets <a name=\"make\"></a>\n", + "\n", + "For all tasks we have defined the following make targets. \n", + "\n", + "* __poisson2d__: \n", + " build `poisson2d` binary (default)\n", + "* __run__: \n", + " run `poisson2d` with default parameters\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[Back to Top](#top)\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Task 1: Basic compiler optimization flags and compiler annotations <a name=\"task1\"></a>\n", + "\n", + "\n", + "### Overview\n", + "\n", + "The goal of this task is to understand different options available to optimize the performance of the CPU Jacobi solver \n", + "\n", + "Your task is to:\n", + "\n", + "* Optimize performance with `-Ofast` flag\n", + "* Verify the cause for performance improvement by viewing perf profiles of O3 and Ofast binaries \n", + "* Optimize performance with profile directed feedback \n", + "* Generate compiler annotations/remarks to understand the optimizations done by the compiler with and without profile directed feedback \n", + "\n", + "\n", + "First, change the working directory to `Task1`." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Task 2:<a name=\"task2\"></a> Software Pretechting\n", - "\n", - "\n", - "### Overview\n", - "\n", - "Study the difference of program execution time of different optimization levels with and without software prefetching.\n", - "\n", - "First, change directory to that of Task 2" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "/autofs/nccsopen-svm1_home/aherten/SC19-Tutorial/3-Optimizing_POWER/Handson/Task1\n" + ] + } + ], + "source": [ + "%cd Task1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Part A: `-Ofast` vs. `-O3`\n", + "\n", + "We are to compare the performance of the binary being compiled with `-Ofast` optimization and with `-O3` optimization. As in the previous task, we use a `Makefile` for compilation. The `Makefile` targets `poisson2d_O3` and `poisson2d_Ofast` are already prepared. \n", + "\n", + "**TASK**: Add `-O3` as the optimization flag for the `poisson2d_O3` target by using the corresponding `CFLAGS` definition. There are notes relating to this Task 1 in the header of the `Makefile`. Compile the code using `make` as indicated below and run with the `Make` targets `run`, `run_perf` and `run_perf_recrep`. " + ] + }, + { + "cell_type": "code", + "execution_count": 84, + "metadata": { + "collapsed": true + }, + "outputs": [ { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "/autofs/nccsopen-svm1_home/aherten/SC18-Tutorial/3-Optimizing_POWER/Handson/Task2\n" - ] - } - ], - "source": [ - "%cd ../Task2" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc -c -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -O3 poisson2d_reference.c -o poisson2d_reference.o -lm\n", + "gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -O3 poisson2d.c poisson2d_reference.o -o poisson2d -lm\n" + ] + } + ], + "source": [ + "!make poisson2d_O3" + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "metadata": {}, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Part A: Running" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", + "Job <24897> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "4.73user 0.00system 0:04.73elapsed 99%CPU (0avgtext+0avgdata 24256maxresident)k\n", + "256inputs+0outputs (0major+480minor)pagefaults 0swaps\n" + ] + } + ], + "source": [ + "!make run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's have a look at the output of the `Makefile` target `run_perf`. It invokes the GNU _perf_ tool to print out details of the number of instructions executed and the number of cycles taken by POWER9 to execute the program. Feel free to add further counter to this call to _perf_." + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "metadata": {}, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Look at the [Makefile](/edit/Task2/Makefile) and work on the TODOs. Please implement compile flags as mentioned in the Makefile target name.\n", - "\n", - "Afterwards, compile each target with the following cells and submit them to the batch system. Follow along accordingly in the non-interactive version of this Notebook." - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,instructions ./poisson2d\n", + "Job <24898> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "\n", + " Performance counter stats for './poisson2d':\n", + "\n", + " 16264721613 cycles:u \n", + " 28463907825 instructions:u # 1.75 insn per cycle \n", + "\n", + " 4.738444892 seconds time elapsed\n", + "\n" + ] + } + ], + "source": [ + "!make run_perf" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next we run the makefile with target `run_perf_recrep` that prints the top routines of the application in terms of hotness by using a combination of `perf record ./app` and `perf report`. " + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "metadata": {}, + "outputs": [ { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "/sw/ascent/gcc/6.4.0/bin/gcc -c -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -O3 -fprefetch-loop-arrays -fopenmp poisson2d_reference.c -o poisson2d_reference.o -lm\n", - "/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -O3 -fprefetch-loop-arrays -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d_o3_pref -lm\n", - "bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d_o3_pref\n", - "Job <5037> is submitted to default queue <batch>.\n", - "<<Waiting for dispatch ...>>\n", - "<<Starting on login1>>\n", - "Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", - "Calculate reference solution and time with serial CPU execution.\n", - " 0, 0.249980\n", - " 100, 0.246028\n", - " 200, 0.242198\n", - " 300, 0.238487\n", - " 400, 0.234887\n", - "Calculate current execution.\n", - " 0, 0.249980\n", - " 100, 0.246028\n", - " 200, 0.242198\n", - " 300, 0.238487\n", - " 400, 0.234887\n", - "1.12user 0.00system 0:01.13elapsed 99%CPU (0avgtext+0avgdata 10880maxresident)k\n", - "256inputs+0outputs (0major+265minor)pagefaults 0swaps\n" - ] - } - ], - "source": [ - "!make run_o3_pref" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf record -e cycles --output=/gpfs/wolf/trn003/scratch/aherten//cycles.data ./poisson2d\n", + "Job <24899> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "[ perf record: Woken up 3 times to write data ]\n", + "[ perf record: Captured and wrote 0.739 MB /gpfs/wolf/trn003/scratch/aherten//cycles.data (19102 samples) ]\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf report -i /gpfs/wolf/trn003/scratch/aherten//cycles.data --stdio\n", + "Job <24900> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "# To display the perf.data header info, please use --header/--header-only options.\n", + "#\n", + "#\n", + "# Total Lost Samples: 0\n", + "#\n", + "# Samples: 19K of event 'cycles:u'\n", + "# Event count (approx.): 16254596654\n", + "#\n", + "# Overhead Command Shared Object Symbol \n", + "# ........ ......... ............. ........................................\n", + "#\n", + " 65.50% poisson2d poisson2d [.] 00000038.plt_call.fmax@@GLIBC_2.17\n", + " 21.21% poisson2d poisson2d [.] main\n", + " 9.18% poisson2d libc-2.17.so [.] __memcpy_power7\n", + " 3.28% poisson2d libm-2.17.so [.] __fmaxf\n", + " 0.74% poisson2d libm-2.17.so [.] __exp_finite\n", + " 0.04% poisson2d poisson2d [.] 00000038.plt_call.memcpy@@GLIBC_2.17\n", + " 0.01% poisson2d libm-2.17.so [.] __GI___exp\n", + " 0.01% poisson2d ld-2.17.so [.] check_match.10253\n", + " 0.01% poisson2d ld-2.17.so [.] do_lookup_x\n", + " 0.00% poisson2d ld-2.17.so [.] _dl_lookup_symbol_x\n", + " 0.00% poisson2d ld-2.17.so [.] _dl_relocate_object\n", + " 0.00% poisson2d ld-2.17.so [.] strcmp\n", + " 0.00% poisson2d ld-2.17.so [.] _wordcopy_fwd_aligned\n", + " 0.00% poisson2d ld-2.17.so [.] _dl_sysdep_start\n", + " 0.00% poisson2d ld-2.17.so [.] _start\n", + "\n", + "\n", + "#\n", + "# (Tip: Limit to show entries above 5% only: perf report --percent-limit 5)\n", + "#\n" + ] + } + ], + "source": [ + "# run_perf_recrep displays the top hot routines \n", + "!make run_perf_recrep" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**TASK**: Now add the optimization flag `Ofast` to the `CFLAGS` for target `poisson2d_Ofast`. Compile the program with the target `poisson2d_Ofast` and run and analyse it as before with `run`, `run_perf` and `run_perf_recrep`.\n", + "\n", + "What difference do you see?" + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "metadata": {}, + "outputs": [ { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast -fprefetch-loop-arrays -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d_ofast_pref -lm\n", - "bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d_ofast_pref\n", - "Job <5038> is submitted to default queue <batch>.\n", - "<<Waiting for dispatch ...>>\n", - "<<Starting on login1>>\n", - "Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", - "Calculate reference solution and time with serial CPU execution.\n", - " 0, 0.249980\n", - " 100, 0.246028\n", - " 200, 0.242198\n", - " 300, 0.238487\n", - " 400, 0.234887\n", - "Calculate current execution.\n", - " 0, 0.249980\n", - " 100, 0.246028\n", - " 200, 0.242198\n", - " 300, 0.238487\n", - " 400, 0.234887\n", - "0.77user 0.00system 0:00.77elapsed 99%CPU (0avgtext+0avgdata 10816maxresident)k\n", - "256inputs+0outputs (0major+264minor)pagefaults 0swaps\n" - ] - } - ], - "source": [ - "!make run_ofast_pref" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast poisson2d.c poisson2d_reference.o -o poisson2d -lm\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", + "Job <24901> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "2.41user 0.00system 0:02.41elapsed 99%CPU (0avgtext+0avgdata 24256maxresident)k\n", + "256inputs+0outputs (0major+480minor)pagefaults 0swaps\n" + ] + } + ], + "source": [ + "!make poisson2d_Ofast \n", + "!make run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Again, run a `perf`-instrumented version:" + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "metadata": {}, + "outputs": [ { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -O3 -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d_o3_nopref -lm\n", - "bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d_o3_nopref\n", - "Job <5039> is submitted to default queue <batch>.\n", - "<<Waiting for dispatch ...>>\n", - "<<Starting on login1>>\n", - "Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", - "Calculate reference solution and time with serial CPU execution.\n", - " 0, 0.249980\n", - " 100, 0.246028\n", - " 200, 0.242198\n", - " 300, 0.238487\n", - " 400, 0.234887\n", - "Calculate current execution.\n", - " 0, 0.249980\n", - " 100, 0.246028\n", - " 200, 0.242198\n", - " 300, 0.238487\n", - " 400, 0.234887\n", - "1.13user 0.00system 0:01.13elapsed 99%CPU (0avgtext+0avgdata 10944maxresident)k\n", - "256inputs+0outputs (0major+266minor)pagefaults 0swaps\n" - ] - } - ], - "source": [ - "!make run_o3_nopref" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,instructions ./poisson2d\n", + "Job <24902> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "\n", + " Performance counter stats for './poisson2d':\n", + "\n", + " 8258991976 cycles:u \n", + " 12013091172 instructions:u # 1.45 insn per cycle \n", + "\n", + " 2.408703909 seconds time elapsed\n", + "\n" + ] + } + ], + "source": [ + "!make run_perf" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Generate the list of top routines in terms of hotness:" + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "metadata": {}, + "outputs": [ { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d_ofast_nopref -lm\n", - "bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d_ofast_nopref\n", - "Job <5040> is submitted to default queue <batch>.\n", - "<<Waiting for dispatch ...>>\n", - "<<Starting on login1>>\n", - "Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", - "Calculate reference solution and time with serial CPU execution.\n", - " 0, 0.249980\n", - " 100, 0.246028\n", - " 200, 0.242198\n", - " 300, 0.238487\n", - " 400, 0.234887\n", - "Calculate current execution.\n", - " 0, 0.249980\n", - " 100, 0.246028\n", - " 200, 0.242198\n", - " 300, 0.238487\n", - " 400, 0.234887\n", - "0.82user 0.00system 0:00.82elapsed 99%CPU (0avgtext+0avgdata 10816maxresident)k\n", - "256inputs+0outputs (0major+265minor)pagefaults 0swaps\n" - ] - } - ], - "source": [ - "!make run_ofast_nopref" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf record -e cycles --output=/gpfs/wolf/trn003/scratch/aherten//cycles.data ./poisson2d\n", + "Job <24903> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "[ perf record: Woken up 2 times to write data ]\n", + "[ perf record: Captured and wrote 0.382 MB /gpfs/wolf/trn003/scratch/aherten//cycles.data (9728 samples) ]\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf report -i /gpfs/wolf/trn003/scratch/aherten//cycles.data --stdio\n", + "Job <24904> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "# To display the perf.data header info, please use --header/--header-only options.\n", + "#\n", + "#\n", + "# Total Lost Samples: 0\n", + "#\n", + "# Samples: 9K of event 'cycles:u'\n", + "# Event count (approx.): 8268811890\n", + "#\n", + "# Overhead Command Shared Object Symbol \n", + "# ........ ......... ............. ........................................\n", + "#\n", + " 81.12% poisson2d poisson2d [.] main\n", + " 17.97% poisson2d libc-2.17.so [.] __memcpy_power7\n", + " 0.79% poisson2d libm-2.17.so [.] __exp_finite\n", + " 0.04% poisson2d poisson2d [.] 00000038.plt_call.memcpy@@GLIBC_2.17\n", + " 0.02% poisson2d ld-2.17.so [.] do_lookup_x\n", + " 0.01% poisson2d libc-2.17.so [.] vfprintf@@GLIBC_2.17\n", + " 0.01% poisson2d libc-2.17.so [.] _dl_addr\n", + " 0.01% poisson2d ld-2.17.so [.] _dl_relocate_object\n", + " 0.01% poisson2d ld-2.17.so [.] check_match.10253\n", + " 0.01% poisson2d ld-2.17.so [.] _dl_lookup_symbol_x\n", + " 0.01% poisson2d ld-2.17.so [.] strcmp\n", + " 0.00% poisson2d ld-2.17.so [.] open_path\n", + " 0.00% poisson2d ld-2.17.so [.] init_tls\n", + " 0.00% poisson2d ld-2.17.so [.] _dl_sysdep_start\n", + " 0.00% poisson2d ld-2.17.so [.] _start\n", + "\n", + "\n", + "#\n", + "# (Tip: For tracepoint events, try: perf report -s trace_fields)\n", + "#\n" + ] + } + ], + "source": [ + "!make run_perf_recrep" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If `perf` is unavailable to you on other machines, you can also study the disassembly with `objdump`: `objdump -lSd ./poisson2d > poisson2d.dis` (feel free to experiment with this in the Notebook as well, just prefix the command with a `!` to execute it.)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Interpretation\n", + "\n", + "Depending on the application requirement, if a high precision of results is not mandatory, one can compile an application with `-Ofast` which enables `\u2013ffast-math` option that implements the same math function in a relaxed manner very similar to how general mathematical expressions are implemented and avoids the overhead of calling a function from the math library. Comparing the files, you will see that the `-Ofast` binary natively implements the `fmax` function using instructions available in the hardware. The `-O3` binary makes a library call to compute `fmax` to follow a stricter _IEEE_ requirement for accuracy." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Part B: Profile-directed Feedback\n", + "\n", + "For the first level of optimization we see that `Ofast` cut the execution time of the `O3` binary by almost half.\n", + "\n", + "We can optimize the performance further by using profile-directed feedback optimization.\n", + "\n", + "To compile using profile-directed feedback with the GCC compiler we need to build the appplication in three stages:\n", + "\n", + "1. Instrument binary;\n", + "2. Run binary with training, gather profile information;\n", + "3. Use profile information to generate optimized binary.\n", + "\n", + "\n", + "Step 1 is achieved by compiling the binary with the correct flag \u2013\u00a0`-fprofile-generate`. In our case, we need to specify an output location, which should be `$(SC19_DIR_SCRATCH)`.\n", + "\n", + "Step 2 consists of a usual, albeit shorter run of the instrumented binary. The can be very short, though the parameters need to be representative of the actual run. After the binary ran, an output file (with file extension `.gcda`) is written to the directory specified during compilation.\n", + "\n", + "For Step 3, the binary is once again compiled, but this time using the `gcda` profile just generated. The according flag is `-fprofile-use`, which we set to `$(SC19_DIR_SCRATCH)` as well.\n", + "\n", + "In our `Makefile` at hand, we prepared the steps already for you in the form of two targets.\n", + "\n", + "* `poisson2d_train`: Will compile the binary with profile-directed feedback\n", + "* `poisson2d_ref`: Will take a generated profile and compile a new, optimized binary\n", + "\n", + "By using dependencies, between these two targets a profile run is launched.\n", + "\n", + "**TASK**: Edit the [Makefile](`Makefile`) and add the `-fprofile-*` flags to the `CFLAGS` of `poisson2d_train` and\n", + "`poisson2d_ref` as outline in the file.\n", + "\n", + "After that, you may launch them with the following cells (`gen_profile` is a meta-target and uses `poisson2d_train` and `poisson2d_ref`). If you need to clean the generated profile, you may use `make clean_profile`." + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "metadata": {}, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Do you notice the impact difference with optimization levels? It's always important to carefully study the interplay of flags." - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast -fprofile-generate=/gpfs/wolf/trn003/scratch/aherten/ poisson2d.c -o poisson2d_train -lm \n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS ./poisson2d_train 100 100 100\n", + "Job <24905> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 100 iterations on 100 x 100 mesh\n", + "Calculate current execution.\n", + " 0, 0.249490\n", + "echo `date` > /gpfs/wolf/trn003/scratch/aherten//.profile_generated\n", + "gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast -fprofile-use=/gpfs/wolf/trn003/scratch/aherten/ poisson2d.c -o poisson2d_ref -lm \n", + "cp poisson2d_ref poisson2d\n" + ] + } + ], + "source": [ + "!make gen_profile" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If the previous cell executed correctly, you now have your optimized executable. Let's see if it even fast than before!" + ] + }, + { + "cell_type": "code", + "execution_count": 80, + "metadata": {}, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Part B: Analysis of Instructions\n", - "\n", - "Compilation with the software prefetching flag causes the compiler to generate the `__dcbt` and `__dcbtst` instructions that prefetch memory values to L3.\n", - "\n", - "Verify it using `objdump -lSd` on each file (`poisson2d_o3_pref`, `poisson2d_ofast_pref`, `poisson2d_o3_nopref`, `poisson2d_ofast_nopref`). You might want to grep for `dcb`." - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", + "Job <24906> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "2.28user 0.01system 0:02.30elapsed 99%CPU (0avgtext+0avgdata 24192maxresident)k\n", + "256inputs+0outputs (0major+479minor)pagefaults 0swaps\n" + ] + } + ], + "source": [ + "!make run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's also measure instructions and cycles" + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "metadata": {}, + "outputs": [ { - "cell_type": "code", - "execution_count": null, - "metadata": { - "exercise": "task" - }, - "outputs": [], - "source": [ - "#!objdump -l\u2026" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,instructions ./poisson2d\n", + "Job <24907> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "\n", + " Performance counter stats for './poisson2d':\n", + "\n", + " 7925983538 cycles:u \n", + " 12253080719 instructions:u # 1.55 insn per cycle \n", + "\n", + " 2.313471365 seconds time elapsed\n", + "\n" + ] + } + ], + "source": [ + "!make run_perf" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "What is your speed-up? Feel free to run with larger problem sizes (mesh; iterations)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Part C: Compiler annotations/Remarks\n", + "\n", + "Usually, all compilers provide an option to emit annotations or remarks by the compiler. These remarks summarize the optimizations done in detail, the location in source where these optimizations were done. There exist options that also indicate optimizations that were missed and the reason why they could not be done. \n", + "\n", + "To generate compiler annotations using GCC, one uses `-fopt-info-all`. If you only want to see the missed options, use the option `-fopt-info-missed` instead of `-fopt-info-all`. See also the [documentation of GCC regarding the flag](https://gcc.gnu.org/onlinedocs/gcc/Developer-Options.html#index-fopt-info).\n", + "\n", + "**TASK**: Have a looK at the `CFLAGS` of the `Makefile` target `poisson2d_Ofast_info`. Add the flag `-fopt-info-all` to the list of flags. This will print optimisation information to stdout. If you rather want to print to this information to a file, use \u2013\u00a0for example \u2013\u00a0`-fopt-info-all=(SC19_DIR_SCRATCH)/filename`." + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "metadata": {}, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If you feel up to the task, you can study the number of L3 cache misses using the corresponding performance counter, `PM_L3_MISS`. Either use your knowledge from Hands-On 1, or use the following call to `perf`, in which we already converted the named counter to a raw counter address." - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast -fopt-info-all poisson2d.c poisson2d_reference.o -o poisson2d_Ofast_info -lm\n", + "poisson2d.c:62:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "poisson2d.c:61:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "poisson2d.c:56:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "poisson2d.c:52:20: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "poisson2d.c:161:5: missed: not inlinable: main/33 -> free/38, function body not available\n", + "poisson2d.c:159:5: missed: not inlinable: main/33 -> free/38, function body not available\n", + "poisson2d.c:158:5: missed: not inlinable: main/33 -> free/38, function body not available\n", + "poisson2d.c:142:31: missed: not inlinable: main/33 -> printf/36, function body not available\n", + "poisson2d.c:103:5: missed: not inlinable: main/33 -> __builtin_puts/37, function body not available\n", + "poisson2d.c:96:5: missed: not inlinable: main/33 -> printf/36, function body not available\n", + "poisson2d.c:78:29: missed: not inlinable: main/33 -> exp/35, function body not available\n", + "poisson2d.c:68:41: missed: not inlinable: main/33 -> malloc/34, function body not available\n", + "poisson2d.c:67:41: missed: not inlinable: main/33 -> malloc/34, function body not available\n", + "poisson2d.c:65:41: missed: not inlinable: main/33 -> malloc/34, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "Unit growth for small function inlining: 207->207 (0%)\n", + "\n", + "Inlined 4 calls, eliminated 0 functions\n", + "\n", + "consider run-time aliasing test between *_84 and *_87\n", + "consider run-time aliasing test between *_92 and *_97\n", + "consider run-time aliasing test between *_104 and *_107\n", + "consider run-time aliasing test between *_111 and *_115\n", + "poisson2d.c:124:13: optimized: Loop 8 distributed: split to 0 loops and 1 library calls.\n", + "poisson2d.c:90:9: optimized: Loop 10 distributed: split to 0 loops and 1 library calls.\n", + "poisson2d.c:108:25: missed: couldn't vectorize loop\n", + "poisson2d.c:108:25: missed: not vectorized: loop nest containing two or more consecutive inner loops cannot be vectorized\n", + "poisson2d.c:136:9: missed: couldn't vectorize loop\n", + "poisson2d.c:136:9: missed: Loop costings may not be worthwhile.\n", + "poisson2d.c:131:9: missed: couldn't vectorize loop\n", + "poisson2d.c:131:9: missed: Loop costings may not be worthwhile.\n", + "poisson2d.c:122:9: missed: couldn't vectorize loop\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_543, _539, _549);\n", + "poisson2d.c:112:9: missed: couldn't vectorize loop\n", + "poisson2d.c:112:9: missed: not vectorized: control flow in loop.\n", + "poisson2d.c:114:13: optimized: loop vectorized using 16 byte vectors\n", + "poisson2d.c:88:5: missed: couldn't vectorize loop\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_528, 0, _531);\n", + "poisson2d.c:72:5: missed: couldn't vectorize loop\n", + "poisson2d.c:78:27: missed: not vectorized: complicated access pattern.\n", + "poisson2d.c:74:9: missed: couldn't vectorize loop\n", + "poisson2d.c:78:29: missed: not vectorized: relevant stmt not supported: _27 = exp (_21);\n", + "poisson2d.c:43:5: note: vectorized 1 loops in function.\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_528, 0, _531);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_543, _539, _549);\n", + "poisson2d.c:114:13: optimized: loop turned into non-loop; it never loops\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _187 = strtol (_1, 0B, 10);\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _189 = strtol (_2, 0B, 10);\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _193 = strtol (_3, 0B, 10);\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _191 = strtol (_4, 0B, 10);\n", + "poisson2d.c:65:41: missed: statement clobbers memory: A_153 = malloc (_7);\n", + "poisson2d.c:67:41: missed: statement clobbers memory: Anew_155 = malloc (_7);\n", + "poisson2d.c:68:41: missed: statement clobbers memory: rhs_157 = malloc (_7);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_528, 0, _531);\n", + "poisson2d.c:96:5: missed: statement clobbers memory: printf (\"Jacobi relaxation calculation: max %d iterations on %d x %d mesh\\n\", iter_max_130, ny_139, nx_195);\n", + "poisson2d.c:103:5: missed: statement clobbers memory: __builtin_puts (&\"Calculate current execution.\"[0]);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_543, _539, _549);\n", + "poisson2d.c:142:31: missed: statement clobbers memory: printf (\"%5d, %0.6f\\n\", iter_237, error_219);\n", + "poisson2d.c:158:5: missed: statement clobbers memory: free (rhs_202);\n", + "poisson2d.c:159:5: missed: statement clobbers memory: free (Anew_124);\n", + "poisson2d.c:161:5: missed: statement clobbers memory: free (A_123);\n", + "poisson2d.c:65:41: missed: statement clobbers memory: A_144 = malloc (8000000);\n", + "poisson2d.c:67:41: missed: statement clobbers memory: Anew_143 = malloc (8000000);\n", + "poisson2d.c:68:41: missed: statement clobbers memory: rhs_142 = malloc (8000000);\n" + ] + } + ], + "source": [ + "!make poisson2d_Ofast_info" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's compare this with the output during compilation when using profile-directed feedback from Task 1 B.\n", + "\n", + "**TASK**: \n", + "Adapt the `CFLAGS` of `poisson2d_ref_info` to include `-fopt-info-all` **and** the profile input of `-fprofile-use=\u2026` here. *(Be advised: Long output!)*" + ] + }, + { + "cell_type": "code", + "execution_count": 83, + "metadata": {}, + "outputs": [ { - "cell_type": "code", - "execution_count": 35, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Job <5048> is submitted to default queue <batch>.\n", - "<<Waiting for dispatch ...>>\n", - "<<Starting on login1>>\n", - "Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", - "Calculate reference solution and time with serial CPU execution.\n", - " 0, 0.249980\n", - " 100, 0.246028\n", - " 200, 0.242198\n", - " 300, 0.238487\n", - " 400, 0.234887\n", - "Calculate current execution.\n", - " 0, 0.249980\n", - " 100, 0.246028\n", - " 200, 0.242198\n", - " 300, 0.238487\n", - " 400, 0.234887\n", - "\n", - " Performance counter stats for './poisson2d_ofast_nopref':\n", - "\n", - " 2829292169 cycles:u \n", - " 136018637 r168a4:u \n", - "\n", - " 0.826136863 seconds time elapsed\n", - "\n", - "Job <5049> is submitted to default queue <batch>.\n", - "<<Waiting for dispatch ...>>\n", - "<<Starting on login1>>\n", - "Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", - "Calculate reference solution and time with serial CPU execution.\n", - " 0, 0.249980\n", - " 100, 0.246028\n", - " 200, 0.242198\n", - " 300, 0.238487\n", - " 400, 0.234887\n", - "Calculate current execution.\n", - " 0, 0.249980\n", - " 100, 0.246028\n", - " 200, 0.242198\n", - " 300, 0.238487\n", - " 400, 0.234887\n", - "\n", - " Performance counter stats for './poisson2d_ofast_pref':\n", - "\n", - " 2654990243 cycles:u \n", - " 128824827 r168a4:u \n", - "\n", - " 0.775593651 seconds time elapsed\n", - "\n" - ] - } - ], - "source": [ - "for f in [\"poisson2d_ofast_nopref\", \"poisson2d_ofast_pref\"]:\n", - " !eval $$SC18_SUBMIT_CMD perf stat -e cycles,r168a4 ./$f\n" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast -fopt-info-all -fprofile-use=/gpfs/wolf/trn003/scratch/aherten/ -Ofast -fprofile-generate=/gpfs/wolf/trn003/scratch/aherten/ poisson2d.c -o poisson2d_train -lm \n", + "poisson2d.c:62:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "poisson2d.c:61:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "poisson2d.c:56:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "poisson2d.c:52:20: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "Increasing alignment of decl: __gcov0.main\n", + "poisson2d.c:164:1: missed: not inlinable: _GLOBAL__sub_D_00100_1_main/48 -> __gcov_exit/55, function body not available\n", + "poisson2d.c:164:1: missed: not inlinable: _GLOBAL__sub_I_00100_0_main/47 -> __gcov_init/54, function body not available\n", + "poisson2d.c:161:5: missed: not inlinable: main/33 -> free/38, function body not available\n", + "poisson2d.c:159:5: missed: not inlinable: main/33 -> free/38, function body not available\n", + "poisson2d.c:158:5: missed: not inlinable: main/33 -> free/38, function body not available\n", + "poisson2d.c:142:31: missed: not inlinable: main/33 -> printf/36, function body not available\n", + "poisson2d.c:103:5: missed: not inlinable: main/33 -> __builtin_puts/37, function body not available\n", + "poisson2d.c:96:5: missed: not inlinable: main/33 -> printf/36, function body not available\n", + "poisson2d.c:78:29: missed: not inlinable: main/33 -> exp/35, function body not available\n", + "poisson2d.c:68:41: missed: not inlinable: main/33 -> malloc/34, function body not available\n", + "poisson2d.c:67:41: missed: not inlinable: main/33 -> malloc/34, function body not available\n", + "poisson2d.c:65:41: missed: not inlinable: main/33 -> malloc/34, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "Unit growth for small function inlining: 295->295 (0%)\n", + "\n", + "Inlined 4 calls, eliminated 0 functions\n", + "\n", + "consider run-time aliasing test between *_84 and *_87\n", + "consider run-time aliasing test between *_92 and *_97\n", + "consider run-time aliasing test between *_104 and *_107\n", + "consider run-time aliasing test between *_111 and *_115\n", + "poisson2d.c:124:13: optimized: Loop 8 distributed: split to 0 loops and 1 library calls.\n", + "poisson2d.c:90:9: optimized: Loop 10 distributed: split to 0 loops and 1 library calls.\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_64, _135, _313);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_632, 0, _239);\n", + "poisson2d.c:108:25: missed: couldn't vectorize loop\n", + "poisson2d.c:108:25: missed: not vectorized: loop nest containing two or more consecutive inner loops cannot be vectorized\n", + "poisson2d.c:136:9: missed: couldn't vectorize loop\n", + "poisson2d.c:136:9: missed: Loop costings may not be worthwhile.\n", + "poisson2d.c:131:9: missed: couldn't vectorize loop\n", + "poisson2d.c:131:9: missed: Loop costings may not be worthwhile.\n", + "poisson2d.c:122:9: missed: couldn't vectorize loop\n", + "poisson2d.c:122:9: missed: not vectorized: control flow in loop.\n", + "poisson2d.c:112:9: missed: couldn't vectorize loop\n", + "poisson2d.c:112:9: missed: not vectorized: control flow in loop.\n", + "poisson2d.c:114:13: optimized: loop vectorized using 16 byte vectors\n", + "poisson2d.c:88:5: missed: couldn't vectorize loop\n", + "poisson2d.c:88:5: missed: not vectorized: control flow in loop.\n", + "poisson2d.c:72:5: missed: couldn't vectorize loop\n", + "poisson2d.c:72:5: missed: not vectorized: control flow in loop.\n", + "poisson2d.c:74:9: missed: couldn't vectorize loop\n", + "poisson2d.c:78:29: missed: not vectorized: relevant stmt not supported: _27 = exp (_21);\n", + "poisson2d.c:43:5: note: vectorized 1 loops in function.\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_632, 0, _239);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_64, _135, _313);\n", + "poisson2d.c:114:13: optimized: loop turned into non-loop; it never loops\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _187 = strtol (_1, 0B, 10);\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _189 = strtol (_2, 0B, 10);\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _193 = strtol (_3, 0B, 10);\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _191 = strtol (_4, 0B, 10);\n", + "poisson2d.c:65:41: missed: statement clobbers memory: A_153 = malloc (_7);\n", + "poisson2d.c:67:41: missed: statement clobbers memory: Anew_155 = malloc (_7);\n", + "poisson2d.c:68:41: missed: statement clobbers memory: rhs_157 = malloc (_7);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_632, 0, _239);\n", + "poisson2d.c:96:5: missed: statement clobbers memory: printf (\"Jacobi relaxation calculation: max %d iterations on %d x %d mesh\\n\", iter_max_337, ny_124, nx_286);\n", + "poisson2d.c:103:5: missed: statement clobbers memory: __builtin_puts (&\"Calculate current execution.\"[0]);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_64, _135, _313);\n", + "poisson2d.c:142:31: missed: statement clobbers memory: printf (\"%5d, %0.6f\\n\", iter_316, error_118);\n", + "poisson2d.c:158:5: missed: statement clobbers memory: free (rhs_127);\n", + "poisson2d.c:159:5: missed: statement clobbers memory: free (Anew_311);\n", + "poisson2d.c:161:5: missed: statement clobbers memory: free (A_122);\n", + "poisson2d.c:65:41: missed: statement clobbers memory: A_129 = malloc (8000000);\n", + "poisson2d.c:67:41: missed: statement clobbers memory: Anew_132 = malloc (8000000);\n", + "poisson2d.c:68:41: missed: statement clobbers memory: rhs_140 = malloc (8000000);\n", + "poisson2d.c:136:9: note: considering unrolling loop 7 at BB 53\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:136:9: optimized: loop unrolled 7 times (header execution count 9800)\n", + "poisson2d.c:131:9: note: considering unrolling loop 6 at BB 50\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:131:9: optimized: loop unrolled 7 times (header execution count 9800)\n", + "poisson2d.c:122:9: note: considering unrolling loop 5 at BB 47\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:122:9: optimized: loop unrolled 3 times (header execution count 9800)\n", + "poisson2d.c:118:25: note: considering unrolling loop 13 at BB 33\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:118:25: optimized: loop unrolled 3 times (header execution count 436550)\n", + "poisson2d.c:118:25: note: considering unrolling loop 9 at BB 30\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:112:9: note: considering unrolling loop 14 at BB 42\n", + "poisson2d.c:43:5: note: considering unrolling loop 4 at BB 40\n", + "poisson2d.c:108:25: note: considering unrolling loop 3 at BB 60\n", + "poisson2d.c:88:5: note: considering unrolling loop 2 at BB 23\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:88:5: optimized: loop unrolled 3 times (header execution count 100)\n", + "poisson2d.c:74:9: note: considering unrolling loop 11 at BB 12\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:74:9: optimized: loop unrolled 3 times (header execution count 9604)\n", + "poisson2d.c:72:5: note: considering unrolling loop 1 at BB 16\n", + "poisson2d.c:164:1: missed: statement clobbers memory: __gcov_init (&*.LPBX0);\n", + "poisson2d.c:164:1: missed: statement clobbers memory: __gcov_exit ();\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS ./poisson2d_train 100 100 100\n", + "Job <24908> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "libgcov profiling error:/gpfs/wolf/trn003/scratch/aherten//#autofs#nccsopen-svm1_home#aherten#SC19-Tutorial#3-Optimizing_POWER#Handson#Task1#poisson2d.gcda:overwriting an existing profile data with a different timestamp\n", + "Jacobi relaxation calculation: max 100 iterations on 100 x 100 mesh\n", + "Calculate current execution.\n", + " 0, 0.249490\n", + "echo `date` > /gpfs/wolf/trn003/scratch/aherten//.profile_generated\n", + "gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast -fopt-info-all -fprofile-use=/gpfs/wolf/trn003/scratch/aherten/ poisson2d.c poisson2d_reference.o -o poisson2d_ref_info -lm\n", + "poisson2d.c:62:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "poisson2d.c:61:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "poisson2d.c:56:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "poisson2d.c:52:20: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "poisson2d.c:161:5: missed: not inlinable: main/33 -> free/38, function body not available\n", + "poisson2d.c:159:5: missed: not inlinable: main/33 -> free/38, function body not available\n", + "poisson2d.c:158:5: missed: not inlinable: main/33 -> free/38, function body not available\n", + "poisson2d.c:142:31: missed: not inlinable: main/33 -> printf/36, function body not available\n", + "poisson2d.c:103:5: missed: not inlinable: main/33 -> __builtin_puts/37, function body not available\n", + "poisson2d.c:96:5: missed: not inlinable: main/33 -> printf/36, function body not available\n", + "poisson2d.c:78:29: missed: not inlinable: main/33 -> exp/35, function body not available\n", + "poisson2d.c:68:41: missed: not inlinable: main/33 -> malloc/34, function body not available\n", + "poisson2d.c:67:41: missed: not inlinable: main/33 -> malloc/34, function body not available\n", + "poisson2d.c:65:41: missed: not inlinable: main/33 -> malloc/34, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "Unit growth for small function inlining: 207->207 (0%)\n", + "\n", + "Inlined 4 calls, eliminated 0 functions\n", + "\n", + "consider run-time aliasing test between *_84 and *_87\n", + "consider run-time aliasing test between *_92 and *_97\n", + "consider run-time aliasing test between *_104 and *_107\n", + "consider run-time aliasing test between *_111 and *_115\n", + "poisson2d.c:124:13: optimized: Loop 8 distributed: split to 0 loops and 1 library calls.\n", + "poisson2d.c:90:9: optimized: Loop 10 distributed: split to 0 loops and 1 library calls.\n", + "poisson2d.c:108:25: missed: couldn't vectorize loop\n", + "poisson2d.c:108:25: missed: not vectorized: loop nest containing two or more consecutive inner loops cannot be vectorized\n", + "poisson2d.c:136:9: missed: couldn't vectorize loop\n", + "poisson2d.c:136:9: missed: Loop costings may not be worthwhile.\n", + "poisson2d.c:131:9: missed: couldn't vectorize loop\n", + "poisson2d.c:131:9: missed: Loop costings may not be worthwhile.\n", + "poisson2d.c:122:9: missed: couldn't vectorize loop\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_539, _535, _544);\n", + "poisson2d.c:112:9: missed: couldn't vectorize loop\n", + "poisson2d.c:112:9: missed: not vectorized: control flow in loop.\n", + "poisson2d.c:114:13: optimized: loop vectorized using 16 byte vectors\n", + "poisson2d.c:88:5: missed: couldn't vectorize loop\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_524, 0, _527);\n", + "poisson2d.c:72:5: missed: couldn't vectorize loop\n", + "poisson2d.c:78:27: missed: not vectorized: complicated access pattern.\n", + "poisson2d.c:74:9: missed: couldn't vectorize loop\n", + "poisson2d.c:78:29: missed: not vectorized: relevant stmt not supported: _27 = exp (_21);\n", + "poisson2d.c:43:5: note: vectorized 1 loops in function.\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_524, 0, _527);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_539, _535, _544);\n", + "poisson2d.c:114:13: optimized: loop turned into non-loop; it never loops\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _187 = strtol (_1, 0B, 10);\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _189 = strtol (_2, 0B, 10);\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _193 = strtol (_3, 0B, 10);\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _191 = strtol (_4, 0B, 10);\n", + "poisson2d.c:65:41: missed: statement clobbers memory: A_153 = malloc (_7);\n", + "poisson2d.c:67:41: missed: statement clobbers memory: Anew_155 = malloc (_7);\n", + "poisson2d.c:68:41: missed: statement clobbers memory: rhs_157 = malloc (_7);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_524, 0, _527);\n", + "poisson2d.c:96:5: missed: statement clobbers memory: printf (\"Jacobi relaxation calculation: max %d iterations on %d x %d mesh\\n\", iter_max_130, ny_139, nx_195);\n", + "poisson2d.c:103:5: missed: statement clobbers memory: __builtin_puts (&\"Calculate current execution.\"[0]);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_539, _535, _544);\n", + "poisson2d.c:142:31: missed: statement clobbers memory: printf (\"%5d, %0.6f\\n\", iter_237, error_219);\n", + "poisson2d.c:158:5: missed: statement clobbers memory: free (rhs_202);\n", + "poisson2d.c:159:5: missed: statement clobbers memory: free (Anew_124);\n", + "poisson2d.c:161:5: missed: statement clobbers memory: free (A_123);\n", + "poisson2d.c:65:41: missed: statement clobbers memory: A_144 = malloc (8000000);\n", + "poisson2d.c:67:41: missed: statement clobbers memory: Anew_143 = malloc (8000000);\n", + "poisson2d.c:68:41: missed: statement clobbers memory: rhs_142 = malloc (8000000);\n", + "poisson2d.c:136:9: note: considering unrolling loop 7 at BB 47\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:136:9: optimized: loop unrolled 7 times (header execution count 9800)\n", + "poisson2d.c:131:9: note: considering unrolling loop 6 at BB 44\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:131:9: optimized: loop unrolled 7 times (header execution count 9800)\n", + "poisson2d.c:122:9: note: considering unrolling loop 5 at BB 40\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:122:9: optimized: loop unrolled 7 times (header execution count 9701)\n", + "poisson2d.c:118:25: note: considering unrolling loop 13 at BB 27\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:118:25: optimized: loop unrolled 3 times (header execution count 436550)\n", + "poisson2d.c:118:25: note: considering unrolling loop 9 at BB 24\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:112:9: note: considering unrolling loop 14 at BB 37\n", + "poisson2d.c:43:5: note: considering unrolling loop 4 at BB 35\n", + "poisson2d.c:108:25: note: considering unrolling loop 3 at BB 51\n", + "poisson2d.c:88:5: note: considering unrolling loop 2 at BB 18\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:88:5: optimized: loop unrolled 7 times (header execution count 99)\n", + "poisson2d.c:74:9: note: considering unrolling loop 11 at BB 9\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:74:9: optimized: loop unrolled 3 times (header execution count 9604)\n", + "poisson2d.c:72:5: note: considering unrolling loop 1 at BB 14\n" + ] + } + ], + "source": [ + "!make poisson2d_ref_info" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Comparing the annotations generated of a plain `-Ofast` optimization level and the one generated at `-Ofast` and profile directed feedback, we observe that many more optimizations are possible due to profile information.\n", + "\n", + "For instance you will see annotations such as\n", + "```\n", + "poisson2d.c:114:25: optimized: loop unrolled 3 times (header execution count 436550)\n", + "```\n", + "\n", + "The execution count indicates the dynamic execution count of the node at runtime. This information determines which paths are hotter and subsequently facilitate additional optimizations." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### References\n", + "\n", + "1. https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html\n", + "2. https://perf.wiki.kernel.org/index.php/Tutorial" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[Back to Top](#top)\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Task 2:<a name=\"task2\"></a> Impact of Prefetching on Performance\n", + "\n", + "\n", + "### Overview\n", + "\n", + "* Study the difference of program execution time of different optimization levels with and without software prefetching.\n", + "* Verify the impact by measuring cache counters with and without prefetching.\n", + "* Learn how to modify contents of DSCR (*Data Stream Control Register*) using IBM XL compiler and study the impact with different values to DSCR. \n", + "\n", + "But first, lets change directory to that of Task 2" + ] + }, + { + "cell_type": "code", + "execution_count": 85, + "metadata": {}, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### References\n", - "\n", - "1. https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html\n", - "2. https://www.gnu.org/software/gcc/projects/prefetch.html" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "/autofs/nccsopen-svm1_home/aherten/SC19-Tutorial/3-Optimizing_POWER/Handson/Task2\n" + ] + } + ], + "source": [ + "%cd ../Task2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Part A: Software Prefetching" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**TASK**: Look at the Makefile and work on the TODOs. \n", + "\n", + "- First generate a `-Ofast`-optimised binary and note down the performance in terms of cycles, seconds, and L3 misses. This is our baseline!\n", + "- Modify the `Makefile` to add the option for software prefetching (`-fprefetch-loop-arrays`). Compare performance of `-Ofast` with and without software prefetching" + ] + }, + { + "cell_type": "code", + "execution_count": 97, + "metadata": {}, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[Back to Top](#top)\n", - "\n", - "---" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "rm -f poisson2d poisson2d*.o\n" + ] + } + ], + "source": [ + "!make clean" + ] + }, + { + "cell_type": "code", + "execution_count": 88, + "metadata": {}, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Task 3: OpenMP\n", - "<a name=\"task3\"></a>\n", - "\n", - "\n", - "### Overview\n", - "\n", - "We add OpenMP shared-memory parallelism to the application. Also, we study the effect of binding the multi-thread processes to certain cores.\n", - "\n", - "First, we need to change directory to that of Task3." - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "make: `poisson2d' is up to date.\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", + "Job <24911> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "2.39user 0.01system 0:02.40elapsed 100%CPU (0avgtext+0avgdata 24256maxresident)k\n", + "0inputs+0outputs (0major+480minor)pagefaults 0swaps\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,r168a4 ./poisson2d\n", + "Job <24912> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "\n", + " Performance counter stats for './poisson2d':\n", + "\n", + " 8271503902 cycles:u \n", + " 481152478 r168a4:u \n", + "\n", + " 2.412224884 seconds time elapsed\n", + "\n" + ] + } + ], + "source": [ + "!make poisson2d CC=gcc\n", + "!make run\n", + "!make l3missstats" + ] + }, + { + "cell_type": "code", + "execution_count": 98, + "metadata": {}, + "outputs": [ { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "ExecuteTime": { - "end_time": "2018-11-07T13:47:57.724441Z", - "start_time": "2018-11-07T13:47:57.718745Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "/autofs/nccsopen-svm1_home/aherten/SC18-Tutorial/3-Optimizing_POWER/Handson/Task3\n" - ] - } - ], - "source": [ - "%cd ../Task3" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc -std=c99 -DUSE_DOUBLE -Ofast -mcpu=power9 -mvsx -maltivec -fprefetch-loop-arrays -fprefetch-loop-arrays poisson2d.c -o poisson2d_pref -lm\n", + "cp poisson2d_pref poisson2d\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", + "Job <24919> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "1.92user 0.00system 0:01.93elapsed 99%CPU (0avgtext+0avgdata 24256maxresident)k\n", + "256inputs+0outputs (0major+480minor)pagefaults 0swaps\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,r168a4 ./poisson2d\n", + "Job <24920> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "\n", + " Performance counter stats for './poisson2d':\n", + "\n", + " 6586609284 cycles:u \n", + " 459879452 r168a4:u \n", + "\n", + " 1.925399505 seconds time elapsed\n", + "\n" + ] + } + ], + "source": [ + "!make poisson2d_pref CC=gcc\n", + "!make run\n", + "!make l3missstats" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**TASK**: Repeat the experiment with the `-O3` flag. Have a look at the `Makefile` and the outlined TODO. There's a position to easily adapt `-Ofast`\u2192`-O3`!" + ] + }, + { + "cell_type": "code", + "execution_count": 100, + "metadata": {}, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Part A: Implement OpenMP Pragmas; Compilation\n", - "\n", - "**Task**: Please add the correct OpenMP pragmas to the source code and compilations flags to enable OpenMP.\n", - "\n", - "* **pragmas**: Look at the TODOs in [`poisson2d.c`](/edit/Task3/poisson2d.c) to add OpenMP parallelism. The pragmas in question are `#pragma omp parallel for`\n", - "* **Compilation**: Please add compilation flags enabling OpenMP in GCC to the [Makefile](/edit/Task3/Makefile). The flag in question is `-fopenmp`.\n", - "\n", - "Edit the files with the links above if you are running the interactive version of the Notebook or navigate to `poisson2d.c` and `Makefile` yourself in case you run the non-interactive version.\n", - "\n", - "Afterwards, compile and run the application with the following cells. Non-interactive: Follow along accordingly in the shell." - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc -std=c99 -DUSE_DOUBLE -O3 -mcpu=power9 -mvsx -maltivec poisson2d.c -o poisson2d -lm\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", + "Job <24923> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "4.73user 0.00system 0:04.73elapsed 99%CPU (0avgtext+0avgdata 24256maxresident)k\n", + "256inputs+0outputs (0major+479minor)pagefaults 0swaps\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,r168a4 ./poisson2d\n", + "Job <24924> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "\n", + " Performance counter stats for './poisson2d':\n", + "\n", + " 16445764669 cycles:u \n", + " 645094089 r168a4:u \n", + "\n", + " 4.792567763 seconds time elapsed\n", + "\n" + ] + } + ], + "source": [ + "!make poisson2d CC=gcc -B\n", + "!make run\n", + "!make l3missstats" + ] + }, + { + "cell_type": "code", + "execution_count": 101, + "metadata": {}, + "outputs": [ { - "cell_type": "code", - "execution_count": 37, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "/sw/ascent/gcc/6.4.0/bin/gcc -c -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec poisson2d_reference.c -o poisson2d_reference.o -lm\n", - "/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d -lm\n" - ] - } - ], - "source": [ - "!make poisson2d" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc -std=c99 -DUSE_DOUBLE -O3 -mcpu=power9 -mvsx -maltivec -fprefetch-loop-arrays poisson2d.c -o poisson2d_pref -lm\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", + "Job <24925> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "4.74user 0.00system 0:04.74elapsed 99%CPU (0avgtext+0avgdata 24256maxresident)k\n", + "0inputs+0outputs (0major+480minor)pagefaults 0swaps\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,r168a4 ./poisson2d\n", + "Job <24926> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "\n", + " Performance counter stats for './poisson2d':\n", + "\n", + " 16239159454 cycles:u \n", + " 631061431 r168a4:u \n", + "\n", + " 4.730144897 seconds time elapsed\n", + "\n" + ] + } + ], + "source": [ + "!make poisson2d_pref CC=gcc -B\n", + "!make run\n", + "!make l3missstats" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Do you notice the impact difference with optimization levels? At what optimization level does software prefetching help the most?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Part B: Analysis of Instructions\n", + "\n", + "Compilation of the `-Ofast` binary with the software prefetching flag causes the compiler to generate the `dcb*` instructions that prefetch memory values to L3." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**TASK**: \n", + "Run `$(SC19_SUBMIT_CMD) objdump -lSd` on each binary file (`-O3`, `-Ofast` with prefetch/no prefetch).\n", + "Look for instructions beginning with `dcb`\n", + "At what optimization levels does the compiler generate software prefetching instructions?" + ] + }, + { + "cell_type": "code", + "execution_count": 114, + "metadata": {}, + "outputs": [ { - "cell_type": "code", - "execution_count": 40, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", - "Job <5052> is submitted to default queue <batch>.\n", - "<<Waiting for dispatch ...>>\n", - "<<Starting on login1>>\n", - "Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", - "Calculate reference solution and time with serial CPU execution.\n", - " 0, 0.249980\n", - " 100, 0.246028\n", - " 200, 0.242198\n", - " 300, 0.238487\n", - " 400, 0.234887\n", - "Calculate current execution.\n", - " 0, 0.249980\n", - " 100, 0.246028\n", - " 200, 0.242198\n", - " 300, 0.238487\n", - " 400, 0.234887\n", - "500x500: Ref: 0.2571 s, This: 0.2946 s, speedup: 0.87\n", - "1.48user 0.00system 0:00.56elapsed 263%CPU (0avgtext+0avgdata 9664maxresident)k\n", - "0inputs+0outputs (0major+273minor)pagefaults 0swaps\n" - ] - } - ], - "source": [ - "!make run" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc -std=c99 -DUSE_DOUBLE -Ofast -mcpu=power9 -mvsx -maltivec -fprefetch-loop-arrays poisson2d.c -o poisson2d_pref -lm\n" + ] + } + ], + "source": [ + "!make CC=gcc -B poisson2d_pref\n", + "!objdump -lSd ./poisson2d_pref > poisson2d.dis" + ] + }, + { + "cell_type": "code", + "execution_count": 116, + "metadata": {}, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The command to submit a job to the batch system is prepared in an environment variable `$SC18_SUBMIT_CMD`; use it together with `eval`. In the following cell, it is shown how to increase the work of the application." - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + " 10000b28:\t2c d2 00 7c \tdcbt 0,r26\n", + " 10000b30:\t2c ba 00 7c \tdcbt 0,r23\n", + " 10000b38:\t2c b2 00 7c \tdcbt 0,r22\n", + " 10000b50:\t2c d2 00 7c \tdcbt 0,r26\n", + " 10000b58:\tec b9 00 7c \tdcbtst 0,r23\n", + " 10000b80:\t2c d2 00 7c \tdcbt 0,r26\n", + " 10000e64:\t2c 92 00 7c \tdcbt 0,r18\n", + " 10000e68:\t2c 9a 00 7c \tdcbt 0,r19\n", + " 10000e6c:\t2c a2 00 7c \tdcbt 0,r20\n", + " 10000e70:\t2c aa 00 7c \tdcbt 0,r21\n", + " 10000e7c:\t2c b2 00 7c \tdcbt 0,r22\n", + " 10000e80:\t2c d2 00 7c \tdcbt 0,r26\n", + " 10000e94:\tec b9 00 7c \tdcbtst 0,r23\n" + ] + } + ], + "source": [ + "!grep dcb poisson2d.dis" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Part C: Changing Values of DSCR via compiler flags\n", + "\n", + "This task requires using the IBM XL compiler. It should be already in your environment.\n", + "\n", + "\n", + "We saw the impact of software prefetching in the previous subsection. \n", + "In certain cases, tuning the hardware prefetcher through compiler options can also help improve performance. \n", + "In this exercise we shall see some compiler options that can be used to modify the DSCR value which controls aggressiveness of prefetching. It can be also used to turn off hardware prefetching. \n", + "\n", + "IBM XL compiler has an option `-qprefetch=dscr=<val>` that can be used for this purpose.\n", + "Compiling with `-qprefetch=dscr=1` turns off the prefetcher. One can give various values such as `-qprefetch=dscr=4`, `-qprefetch=dscr=7` etc. to control aggressiveness of prefetching.\n", + "\n", + "For this exercise we use `make CC=xlc_r` to illustrate the performance impact.\n", + " \n", + "\n", + "**Task** Generate a XL-compiled binary by compiling using the following cells. After you've generated a baseline, start editing the `Makefile`: Add `qprefetch=dscr=1` to the `CFLAGS` and rebuild the application and note the performance. Which one is faster? \n", + "\n", + "In general, applications benefit with the default settings of hardware DSCR register (`-qprefetch=dscr=0`). However, certain applications also benefit with prefetching turned off. \n", + "\n", + "It is to be noted that DSCR values are highly sensitive to the application. One value that works well for Application A may not help Application B. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Measure performance of the application compiled with XL at default DSCR value" + ] + }, + { + "cell_type": "code", + "execution_count": 117, + "metadata": {}, + "outputs": [ { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Job <5344> is submitted to default queue <batch>.\n", - "<<Waiting for dispatch ...>>\n", - "<<Starting on login1>>\n", - "Jacobi relaxation calculation: max 1000 iterations on 1000 x 100 mesh\n", - "Calculate reference solution and time with serial CPU execution.\n", - " 0, 0.249743\n", - " 100, 0.210080\n", - " 200, 0.184635\n", - " 300, 0.166526\n", - " 400, 0.152783\n", - " 500, 0.141890\n", - " 600, 0.132978\n", - " 700, 0.125511\n", - " 800, 0.119142\n", - " 900, 0.113632\n", - "Calculate current execution.\n", - " 0, 0.249743\n", - " 100, 0.210080\n", - " 200, 0.184635\n", - " 300, 0.166526\n", - " 400, 0.152783\n", - " 500, 0.141890\n", - " 600, 0.132978\n", - " 700, 0.125511\n", - " 800, 0.119142\n", - " 900, 0.113632\n", - "1000x100: Ref: 1.9872 s, This: 0.2385 s, speedup: 8.33\n" - ] - } - ], - "source": [ - "!eval $SC18_SUBMIT_CMD ./poisson2d 1000 1000" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "xlc_r -std=c99 -DUSE_DOUBLE -Ofast -qarch=pwr9 -qtune=pwr9 -DINLINE_LIBS poisson2d.c -o poisson2d -lm\n", + " 1500-036: (I) The NOSTRICT option (default at OPT(3)) has the potential to alter the semantics of a program. Please refer to documentation on the STRICT/NOSTRICT option for more information.\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", + "Job <24927> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 50.149062\n", + " 200, 99.849327\n", + " 300, 149.352369\n", + " 400, 198.659746\n", + " 500, 247.773000\n", + " 600, 296.693652\n", + " 700, 345.423208\n", + " 800, 393.963155\n", + " 900, 442.314962\n", + "2.26user 0.00system 0:02.27elapsed 99%CPU (0avgtext+0avgdata 24256maxresident)k\n", + "256inputs+0outputs (0major+477minor)pagefaults 0swaps\n" + ] + } + ], + "source": [ + "!make CC=xlc_r -B poisson2d\n", + "!make run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Measure performance of the application compiled with XL with DSCR value turned off" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "What is the best performance you can reach by setting the number of threads via `OMP_NUM_THREADS=N` with `N` being the number of threads? Feel free to play around with the command in the following cell, using 1 thread as an example. \n", - "We added `--bind none` to prevent `jsrun`, the scheduler of Ascent, from overlaying binding options. Also, we use `-c ALL_CPUS` to make all CPUs on the compute nodes available to you." - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "xlc_r -std=c99 -DUSE_DOUBLE -Ofast -qarch=pwr9 -qtune=pwr9 -DINLINE_LIBS -qprefetch=dscr=1 poisson2d.c -o poisson2d_dscr -lm\n", + " 1500-036: (I) The NOSTRICT option (default at OPT(3)) has the potential to alter the semantics of a program. Please refer to documentation on the STRICT/NOSTRICT option for more information.\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", + "Job <24929> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "4.58user 0.00system 0:04.59elapsed 99%CPU (0avgtext+0avgdata 24192maxresident)k\n", + "0inputs+0outputs (0major+476minor)pagefaults 0swaps\n" + ] + } + ], + "source": [ + "!make poisson2d_dscr CC=xlc_r -B\n", + "!make run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Does Hardware prefetcher help this application? How much impact do you see when you turn off the hardware prefetcher? " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### References\n", + "\n", + "1. https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html\n", + "2. https://www.gnu.org/software/gcc/projects/prefetch.html\n", + "3. https://openpowerfoundation.org/?resource_lib=power-isa-version-3-0\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[Back to Top](#top)\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Task 3: OpenMP\n", + "<a name=\"task3\"></a>\n", + "\n", + "\n", + "### Overview\n", + "\n", + "We add OpenMP shared-memory parallelism to the application. Also, we study the effect of binding the multi-thread processes to certain cores on the resulting application performance. We do this study for both GCC and XL compilers inorder to learn about the appropriate options that need to be used.\n", + "First, we need to change directory to that of Task3. For Task 3 we modify poisson2d.c to invoke an exact copy of the main jacobi loop which is `poisson2d_reference`. We parallelize only the main loop but not `poisson2d_reference`. The speedup is the performance gain seen in the main loop as compared to the reference loop." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ { - "cell_type": "code", - "execution_count": 22, - "metadata": { - "exercise": "task" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Job <5379> is submitted to default queue <batch>.\n", - "<<Waiting for dispatch ...>>\n", - "<<Starting on login1>>\n", - "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", - "Calculate reference solution and time with serial CPU execution.\n", - " 0, 0.249995\n", - " 100, 0.248997\n", - " 200, 0.248007\n", - " 300, 0.247025\n", - " 400, 0.246050\n", - " 500, 0.245084\n", - " 600, 0.244124\n", - " 700, 0.243173\n", - " 800, 0.242228\n", - " 900, 0.241291\n", - "Calculate current execution.\n", - " 0, 0.249995\n", - " 100, 0.248997\n", - " 200, 0.248007\n", - " 300, 0.247025\n", - " 400, 0.246050\n", - " 500, 0.245084\n", - " 600, 0.244124\n", - " 700, 0.243173\n", - " 800, 0.242228\n", - " 900, 0.241291\n", - "1000x1000: Ref: 2.3303 s, This: 2.8446 s, speedup: 0.82\n" - ] - } - ], - "source": [ - "!eval OMP_NUM_THREADS=1 $SC18_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "/autofs/nccsopen-svm1_home/aherten/SC19-Tutorial/3-Optimizing_POWER/Handson/Task3\n" + ] + } + ], + "source": [ + "%cd ../Task3" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Part A: Implement OpenMP Pragmas; Compilation\n", + "\n", + "**Task**: Please add the correct OpenMP directives to poisson2d.c and compilations flags in the Makefile to enable OpenMP with GCC and XL compilers.\n", + "\n", + "* **Directives**: Look at the TODOs in [`poisson2d.c`](poisson2d.c) to add OpenMP parallelism. The pragmas in question are `#pragma omp parallel for` (and once it's `#pragma omp parallel for reduction(max:error)` \u2013\u00a0can you guess where?)\n", + "* **Compilation**: Please add compilation flags enabling OpenMP in GCC and XL to the `Makefile`. For GCC, we need to add `-fopenmp` and the application needs to be linked with `-lgomp`. For XL, we need to add `-qsmp=omp` to the list of compilation flags. \n", + "\n", + "Afterwards, compile and run the application with the following commands." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Part B: Bindings\n", - "\n", - "Different CPU architectures and models come with different configuration of cores. The configuration plays an important role in the run time of the application. We need to optimize for it!\n", - "\n", - "There are applications which can be used to determine the configuration of the processor. Among those are:\n", - "\n", - "* `lscpu`: Can be used to determine the number of sockets, number of cores, and numb of threads. It gives a very good overview and is available on most Linux systems.\n", - "* `ppc64_cpu --smt`: Specifically for POWER, this tool can give information about the number of simulations threads running per core (*SMT*, Simulataion Multi-Threading).\n", - "\n", - "Run `ppc64_cpu --smt` to find out about the threading configuration of Ascent!" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc -c -std=c99 -DUSE_DOUBLE -O3 -mcpu=power9 -mvsx -maltivec -fopenmp -lgomp poisson2d_reference.c -o poisson2d_reference.o -lm\n", + "gcc -std=c99 -DUSE_DOUBLE -O3 -mcpu=power9 -mvsx -maltivec -fopenmp -lgomp poisson2d.c poisson2d_reference.o -o poisson2d -lm \n" + ] + } + ], + "source": [ + "!make poisson2d CC=gcc" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The command to submit a job to the batch system is prepared in an environment variable `$SC19_SUBMIT_CMD`; use it together with `eval`. In the following cell, it is shown how to invoke the application using the batch system. " + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": {}, + "outputs": [ { - "cell_type": "code", - "execution_count": 48, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Job <5076> is submitted to default queue <batch>.\n", - "<<Waiting for dispatch ...>>\n", - "<<Starting on login1>>\n", - "SMT=4\n" - ] - } - ], - "source": [ - "!eval $SC18_SUBMIT_CMD ppc64_cpu --smt" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "Job <24951> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate reference solution and time with serial CPU execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "1000x1000: Ref: 4.7430 s, This: 3.9363 s, speedup: 1.20\n" + ] + } + ], + "source": [ + "!eval $SC19_SUBMIT_CMD ./poisson2d 1000 1000 1000" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Inorder to run the parallel application, we need to set the number of threads using `OMP_NUM_THREADS`\n", + "What is the best performance you can reach by setting the number of threads via `OMP_NUM_THREADS=N` with `N` being the number of threads? Feel free to play around with the command in the following cell, using 1 thread as an example. \n", + "We added `--bind none` to prevent `jsrun`, the scheduler of Ascent, from overlaying binding options. Also, we use `-c ALL_CPUS` to make all CPUs on the compute nodes available to you." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "exercise": "task" + }, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There are more sources information available\n", - "\n", - "* `/proc/cpuinfo`: Holds information about virtual cores, including model and clock speed. Available on most Linux system. Usually used together with `cat`\n", - "* `/sys/devices/system/cpu/cpu0/topology/thread_siblings_list`: Holds information about thread siblings for given CPU core (`cpu0` in this case). Use it to find out which thread is mapped to which core." - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "Job <24945> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "\n", + "libgomp: Invalid value for environment variable OMP_NUM_THREADS\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate reference solution and time with serial CPU execution.\n", + " 0, 0.249995\n", + " 100, 50.149062\n", + " 200, 99.849327\n", + " 300, 149.352369\n", + " 400, 198.659746\n", + " 500, 247.773000\n", + " 600, 296.693652\n", + " 700, 345.423208\n", + " 800, 393.963155\n", + " 900, 442.314962\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "1000x1000: Ref: 2.1046 s, This: 2.4171 s, speedup: 0.87\n" + ] + } + ], + "source": [ + "!eval OMP_NUM_THREADS=N $SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Part B: Bindings\n", + "\n", + "Different CPU architectures and models come with different configuration of cores. The configuration plays an important role in the run time of the application. We need to optimize for it!\n", + "\n", + "There are applications which can be used to determine the configuration of the processor. Among those are:\n", + "\n", + "* `lscpu`: Can be used to determine the number of sockets, number of cores, and numb of threads. It gives a very good overview and is available on most Linux systems.\n", + "* `ppc64_cpu --smt`: Specifically for POWER, this tool can give information about the number of simulations threads running per core (*SMT*, Simulataion Multi-Threading).\n", + "\n", + "Run `ppc64_cpu --smt` to find out about the threading configuration of Ascent!" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "metadata": {}, + "outputs": [ { - "cell_type": "code", - "execution_count": 49, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0-3\n", - "4-7\n" - ] - } - ], - "source": [ - "!cat /sys/devices/system/cpu/cpu0/topology/thread_siblings_list\n", - "!cat /sys/devices/system/cpu/cpu5/topology/thread_siblings_list" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "Job <24465> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "SMT=4\n" + ] + } + ], + "source": [ + "!eval $SC19_SUBMIT_CMD ppc64_cpu --smt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are more sources information available\n", + "\n", + "* `/proc/cpuinfo`: Holds information about virtual cores, including model and clock speed. Available on most Linux system. Usually used together with `cat`\n", + "* `/sys/devices/system/cpu/cpu0/topology/thread_siblings_list`: Holds information about thread siblings for given CPU core (`cpu0` in this case). Use it to find out which thread is mapped to which core." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There are various environment variables available within OpenMP (and GCC) to specify binding of threads to cores. See, for instance, the [online documentation of GCC libgomp](https://gcc.gnu.org/onlinedocs/libgomp/Environment-Variables.html). Examples are `OMP_PLACES` or `GOMP_CPU_AFFINITY`.\n", - "\n", - "**Task**: Run the application enabled with OpenMP from Part A with different binding configurations. Make sure to at least run a) binding all threads to a single core and b) binding threads to different cores.\n", - "\n", - "Adapt the following command with your configuration \u2013 or follow along accordingly in the non-interactive version of the Notebook.\n", - "\n", - "What's your maximum speedup?" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "Job <24949> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "0-3\n", + "Job <24950> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "4-7\n" + ] + } + ], + "source": [ + "!$$SC19_SUBMIT_CMD cat /sys/devices/system/cpu/cpu0/topology/thread_siblings_list\n", + "!$$SC19_SUBMIT_CMD cat /sys/devices/system/cpu/cpu5/topology/thread_siblings_list" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are various environment variables available within OpenMP (some specific to GCC) that hold across compilers to specify binding of threads to cores. See, for instance, the [OMP_PLACES environment Variable](https://www.openmp.org/spec-html/5.0/openmpse53.html). We also have a GNU specific variable which can also be used to control affinity - `GOMP_CPU_AFFINITY`. Setting `GOMP_CPU_AFFINITY` is specific to GCC binaries but it internally serves the same function as setting `OMP_PLACES`. \n", + "\n", + "**Task**: Run the application enabled with OpenMP from Part A with different binding configurations. Make sure to at least run a) binding all threads to a single core and b) binding threads to different cores.\n", + "\n", + "Adapt the following command with your configuration \u2013 or follow along accordingly in the non-interactive version of the Notebook.\n", + "\n", + "What's your maximum speedup?" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": { + "exercise": "task" + }, + "outputs": [ { - "cell_type": "code", - "execution_count": null, - "metadata": { - "exercise": "task" - }, - "outputs": [], - "source": [ - "!eval OMP_DISPLAY_ENV=true GOMP_CPU_AFFINITY=\"X,Y,Z,A\" OMP_NUM_THREADS=4 $$SC18_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 100 | grep \"OMP_PLACES\\|speedup\"" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "/usr/bin/sh: OMP_PLACES}={X},{Y},{Z},{A}: command not found\n" + ] + } + ], + "source": [ + "!eval OMP_DISPLAY_ENV=true OMP_PLACES=\"{X},{Y},{Z},{A}\" OMP_NUM_THREADS=4 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 100 | grep \"OMP_PLACES\\|speedup\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "exercise": "task" + }, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### References\n", - "1. https://gcc.gnu.org/onlinedocs/libgomp/GOMP_005fCPU_005fAFFINITY.html" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n" + ] + } + ], + "source": [ + "!eval OMP_DISPLAY_ENV=true GOMP_CPU_AFFINITY=\"X,Y,Z,A\" OMP_NUM_THREADS=4 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 100 | grep \"OMP_PLACES\\|speedup\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Great!\n", + "\n", + "If you still have time: The same experiments can be repeated with the IBM XL compiler. \n", + "The corresponding compiler flag to enable OpenMP parallelism that needs to be used for XL is `-qsmp=omp`\n", + "\n", + "**Task**: In the Makefile add the OpenMP flag and generate XL binaries with OpenMP and run the application with various number of threads and note the performance speedup." + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[Back to Top](#top)\n", - "\n", - "---" - ] - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "xlc_r -c -std=c99 -DUSE_DOUBLE -O3 -qhot -qtune=pwr9 -DINLINE_LIBS -qsmp=omp poisson2d_reference.c -o poisson2d_reference.o -lm \n", + " 1500-036: (I) The NOSTRICT option (default at OPT(3)) has the potential to alter the semantics of a program. Please refer to documentation on the STRICT/NOSTRICT option for more information.\n", + "xlc_r -std=c99 -DUSE_DOUBLE -O3 -qhot -qtune=pwr9 -DINLINE_LIBS -qsmp=omp poisson2d.c poisson2d_reference.o -o poisson2d -lm\n", + " 1500-036: (I) The NOSTRICT option (default at OPT(3)) has the potential to alter the semantics of a program. Please refer to documentation on the STRICT/NOSTRICT option for more information.\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", + "Job <24956> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate reference solution and time with serial CPU execution.\n", + " 0, 0.249995\n", + " 100, 50.149062\n", + " 200, 99.849327\n", + " 300, 149.352369\n", + " 400, 198.659746\n", + " 500, 247.773000\n", + " 600, 296.693652\n", + " 700, 345.423208\n", + " 800, 393.963155\n", + " 900, 442.314962\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 50.149062\n", + " 200, 99.849327\n", + " 300, 149.352369\n", + " 400, 198.659746\n", + " 500, 247.773000\n", + " 600, 296.693652\n", + " 700, 345.423208\n", + " 800, 393.963155\n", + " 900, 442.314962\n", + "1000x1000: Ref: 5.6783 s, This: 2.6528 s, speedup: 2.14\n", + "21.56user 6.18system 0:08.37elapsed 331%CPU (0avgtext+0avgdata 23040maxresident)k\n", + "3200inputs+0outputs (2major+1098minor)pagefaults 0swaps\n" + ] + } + ], + "source": [ + "!make CC=xlc_r -B run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Run the parallel application with varying numbre of threads (`OMP_NUM_THREADS`) and note the performance improvement. " + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": { + "exercise": "task" + }, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Survey<a name=\"survey\"></a>\n", - "\n", - "Please rememeber to take some time and fill out the [survey](http://bit.ly/sc18-eval)." - ] + "name": "stdout", + "output_type": "stream", + "text": [ + "Job <23926> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate reference solution and time with serial CPU execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "1000x1000: Ref: 2.3932 s, This: 2.7175 s, speedup: 0.88\n" + ] } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "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.6.7" + ], + "source": [ + "!eval OMP_NUM_THREADS=N $SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we repeat the exercise of using the right binding of threads for the XL binary. `OMP_PLACES` pertains to the XL binary as well as it is an OpenMP variable. `GOMP_CPU_AFFINITY` is specific to GCC binary so that cannot be used to set the binding.\n", + "\n", + "**Task**: Run the application enabled with OpenMP from Part A with different binding configurations. Make sure to at least run a) binding all threads to a single core and b) binding threads to different cores.\n", + "\n", + "Adapt the following command with your configuration \u2013 or follow along accordingly in the non-interactive version of the Notebook.\n", + "\n", + "We are mixing Python with Bash (`!`) here, so don't get confused (because of this, if we want to use Bash environment variables, we need to use two `$$`)\n", + "\n", + "What's your maximum speedup?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "exercise": "task" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Affinity: {X},{Y},{Z},{A}\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "1587-117 The string for the OpenMP environment variable 'OMP_PLACES' contains unexpected or invalid text. OpenMP environment variable ignored. \n", + " OMP_PLACES='cores(44)'\n", + "1000x1000: Ref: 2.0988 s, This: 0.6556 s, speedup: 3.20\n", + "Affinity: {P},{Q},{R},{S}\n", + "<<Waiting for dispatch ...>>\n" + ] } + ], + "source": [ + "for affinity in [\"{X},{Y},{Z},{A}\", \"{P},{Q},{R},{S}\"]:\n", + " print(\"Affinity: {}\".format(affinity))\n", + " !eval OMP_DISPLAY_ENV=true OMP_PLACES=$affinity OMP_NUM_THREADS=4 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep \"OMP_PLACES\\|speedup\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Likewise we see a higher speedup when we bind the threads to different cores rather than to a single core. This handson illustrates that apart from compiler level tuning, system level tuning is also equally important to obtain performance improvements \n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### References\n", + "1. https://gcc.gnu.org/onlinedocs/libgomp/GOMP_005fCPU_005fAFFINITY.html\n", + "2. https://www.openmp.org/spec-html/5.0/openmpse53.html" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[Back to Top](#top)\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Survey<a name=\"survey\"></a>\n", + "\n", + "Please rememeber to take some time and fill out the [survey](http://bit.ly/sc19-eval)." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" }, - "nbformat": 4, - "nbformat_minor": 2 + "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.7.0" + } + }, + "nbformat": 4, + "nbformat_minor": 2 } \ No newline at end of file diff --git a/3-Optimizing_POWER/Handson/.master/HandsOnPerformanceOptimization.ipynb b/3-Optimizing_POWER/Handson/.master/HandsOnPerformanceOptimization.ipynb index 673f2cf..8453a7b 100644 --- a/3-Optimizing_POWER/Handson/.master/HandsOnPerformanceOptimization.ipynb +++ b/3-Optimizing_POWER/Handson/.master/HandsOnPerformanceOptimization.ipynb @@ -22,7 +22,7 @@ "\n", "You will always find links to a file browser of the corresponding task subdirectory as well as direct links to the source files you will need to edit as well as the profiling output you need to open locally.\n", "\n", - "If you want you also can get a [terminal](/terminals/1) in your browser.\n", + "If you want you also can get a terminal in your browser; just open it via the »New Launcher« button (`+`).\n", "\n", "## Terminal fallback\n", "\n", @@ -37,7 +37,28 @@ "source": [ "## Setup\n", "\n", - "This hands-on session requires use of GCC 9.2.0. By loading the `sc19/handson2` module before invoking this Notebook, we took care of also loading GCC 9.2.0." + "We are using some very fresh compiler features and use GCC 9.2.0 because of that. It should already be in your environment. Let's check!" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc (GCC) 9.2.0\n", + "Copyright (C) 2019 Free Software Foundation, Inc.\n", + "This is free software; see the source for copying conditions. There is NO\n", + "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n", + "\n" + ] + } + ], + "source": [ + "!gcc --version" ] }, { @@ -107,14 +128,14 @@ }, { "cell_type": "code", - "execution_count": 70, + "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "/autofs/nccsopen-svm1_home/archanaravindar/SC19-Tutorial/Task1/Solutions\n" + "/autofs/nccsopen-svm1_home/aherten/SC19-Tutorial/3-Optimizing_POWER/Handson/Task1\n" ] } ], @@ -128,12 +149,14 @@ "source": [ "### Part A: `-Ofast` vs. `-O3`\n", "\n", - "We are to compare the performance of the binary being compiled with `-Ofast` optimization and with `-O3` optimization. Use `Makefile` for this task. At present, Makefile specifies targets poisson2d_O3 and poisson2d_Ofast. Add `-O3` as the optimization flag for poisson2d_O3 target. Carry out the steps as outlined in `Task1`. Compile the code using `make -f Makefile.gcc` and run with targets 'run', 'runstats' and 'perfgenerate'. " + "We are to compare the performance of the binary being compiled with `-Ofast` optimization and with `-O3` optimization. As in the previous task, we use a `Makefile` for compilation. The `Makefile` targets `poisson2d_O3` and `poisson2d_Ofast` are already prepared. \n", + "\n", + "**TASK**: Add `-O3` as the optimization flag for the `poisson2d_O3` target by using the corresponding `CFLAGS` definition. There are notes relating to this Task 1 in the header of the `Makefile`. Compile the code using `make` as indicated below and run with the `Make` targets `run`, `run_perf` and `run_perf_recrep`. " ] }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 84, "metadata": { "collapsed": true }, @@ -142,8 +165,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "gcc -std=c99 -mcpu=power9 -O3 -DUSE_DOUBLE -mvsx -maltivec poisson2d.c poisson2d_reference.o -o poisson2d_O3 -lm\n", - "cp poisson2d_O3 poisson2d\n" + "gcc -c -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -O3 poisson2d_reference.c -o poisson2d_reference.o -lm\n", + "gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -O3 poisson2d.c poisson2d_reference.o -o poisson2d -lm\n" ] } ], @@ -153,7 +176,7 @@ }, { "cell_type": "code", - "execution_count": 53, + "execution_count": 73, "metadata": {}, "outputs": [ { @@ -161,7 +184,7 @@ "output_type": "stream", "text": [ "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", - "Job <24738> is submitted to default queue <batch>.\n", + "Job <24897> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", @@ -176,8 +199,8 @@ " 700, 0.243173\n", " 800, 0.242228\n", " 900, 0.241291\n", - "4.70user 0.00system 0:04.71elapsed 99%CPU (0avgtext+0avgdata 24192maxresident)k\n", - "256inputs+0outputs (0major+479minor)pagefaults 0swaps\n" + "4.73user 0.00system 0:04.73elapsed 99%CPU (0avgtext+0avgdata 24256maxresident)k\n", + "256inputs+0outputs (0major+480minor)pagefaults 0swaps\n" ] } ], @@ -189,12 +212,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The makefile has been modified to include options 'runstats' that invokes the GNU _perf_ tool to print out details of the number of PPC instructions executed and the number of cycles taken by POWER9 to execute the program. You may wish to add a similar clause to measure other raw events using _perf_." + "Let's have a look at the output of the `Makefile` target `run_perf`. It invokes the GNU _perf_ tool to print out details of the number of instructions executed and the number of cycles taken by POWER9 to execute the program. Feel free to add further counter to this call to _perf_." ] }, { "cell_type": "code", - "execution_count": 55, + "execution_count": 74, "metadata": {}, "outputs": [ { @@ -202,7 +225,7 @@ "output_type": "stream", "text": [ "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,instructions ./poisson2d\n", - "Job <24739> is submitted to default queue <batch>.\n", + "Job <24898> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", @@ -220,37 +243,36 @@ "\n", " Performance counter stats for './poisson2d':\n", "\n", - " 16199088860 cycles:u \n", - " 28463939531 instructions:u # 1.76 insn per cycle \n", + " 16264721613 cycles:u \n", + " 28463907825 instructions:u # 1.75 insn per cycle \n", "\n", - " 4.717812694 seconds time elapsed\n", + " 4.738444892 seconds time elapsed\n", "\n" ] } ], "source": [ - "!make runstats" + "!make run_perf" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The makefile has been modified to include options 'runstats' that invokes the GNU _perf_ tool to print out details of the number of PPC instructions executed and the number of cycles taken by POWER9 to execute the program. You may wish to add a similar clause to measure other raw events using _perf_.\n", - "Next we run the makefile with target `perfgenerate` that prints the top routines of the application in terms of hotness. " + "Next we run the makefile with target `run_perf_recrep` that prints the top routines of the application in terms of hotness by using a combination of `perf record ./app` and `perf report`. " ] }, { "cell_type": "code", - "execution_count": 56, + "execution_count": 75, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf record -e cycles --output=/gpfs/wolf/trn003/scratch/archanaravindar//cycles.data ./poisson2d\n", - "Job <24740> is submitted to default queue <batch>.\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf record -e cycles --output=/gpfs/wolf/trn003/scratch/aherten//cycles.data ./poisson2d\n", + "Job <24899> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", @@ -266,9 +288,9 @@ " 800, 0.242228\n", " 900, 0.241291\n", "[ perf record: Woken up 3 times to write data ]\n", - "[ perf record: Captured and wrote 0.735 MB /gpfs/wolf/trn003/scratch/archanaravindar//cycles.data (18993 samples) ]\n", - "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf report -i /gpfs/wolf/trn003/scratch/archanaravindar//cycles.data --stdio\n", - "Job <24741> is submitted to default queue <batch>.\n", + "[ perf record: Captured and wrote 0.739 MB /gpfs/wolf/trn003/scratch/aherten//cycles.data (19102 samples) ]\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf report -i /gpfs/wolf/trn003/scratch/aherten//cycles.data --stdio\n", + "Job <24900> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "# To display the perf.data header info, please use --header/--header-only options.\n", @@ -276,66 +298,61 @@ "#\n", "# Total Lost Samples: 0\n", "#\n", - "# Samples: 18K of event 'cycles:u'\n", - "# Event count (approx.): 16162306943\n", + "# Samples: 19K of event 'cycles:u'\n", + "# Event count (approx.): 16254596654\n", "#\n", - "# Overhead Command Shared Object Symbol \n", - "# ........ ......... ................ ........................................\n", + "# Overhead Command Shared Object Symbol \n", + "# ........ ......... ............. ........................................\n", "#\n", - " 48.17% poisson2d poisson2d [.] main\n", - " 26.08% poisson2d poisson2d [.] 00000038.plt_call.fmax@@GLIBC_2.17\n", - " 15.84% poisson2d libm-2.17.so [.] __fmaxf\n", - " 9.13% poisson2d libc-2.17.so [.] __memcpy_power7\n", - " 0.72% poisson2d libm-2.17.so [.] __exp_finite\n", - " 0.01% poisson2d poisson2d [.] 00000038.plt_call.memcpy@@GLIBC_2.17\n", - " 0.01% poisson2d libm-2.17.so [.] __GI___exp\n", - " 0.01% poisson2d ld-2.17.so [.] do_lookup_x\n", - " 0.01% poisson2d ld-2.17.so [.] check_match.10253\n", - " 0.01% poisson2d ld-2.17.so [.] _dl_lookup_symbol_x\n", - " 0.01% poisson2d ld-2.17.so [.] strcmp\n", - " 0.00% poisson2d [unknown] [k] 0x000020000002415c\n", - " 0.00% poisson2d ld-2.17.so [.] dl_main\n", - " 0.00% poisson2d ld-2.17.so [.] _start\n", + " 65.50% poisson2d poisson2d [.] 00000038.plt_call.fmax@@GLIBC_2.17\n", + " 21.21% poisson2d poisson2d [.] main\n", + " 9.18% poisson2d libc-2.17.so [.] __memcpy_power7\n", + " 3.28% poisson2d libm-2.17.so [.] __fmaxf\n", + " 0.74% poisson2d libm-2.17.so [.] __exp_finite\n", + " 0.04% poisson2d poisson2d [.] 00000038.plt_call.memcpy@@GLIBC_2.17\n", + " 0.01% poisson2d libm-2.17.so [.] __GI___exp\n", + " 0.01% poisson2d ld-2.17.so [.] check_match.10253\n", + " 0.01% poisson2d ld-2.17.so [.] do_lookup_x\n", + " 0.00% poisson2d ld-2.17.so [.] _dl_lookup_symbol_x\n", + " 0.00% poisson2d ld-2.17.so [.] _dl_relocate_object\n", + " 0.00% poisson2d ld-2.17.so [.] strcmp\n", + " 0.00% poisson2d ld-2.17.so [.] _wordcopy_fwd_aligned\n", + " 0.00% poisson2d ld-2.17.so [.] _dl_sysdep_start\n", + " 0.00% poisson2d ld-2.17.so [.] _start\n", "\n", "\n", "#\n", - "# (Tip: Show individual samples with: perf script)\n", + "# (Tip: Limit to show entries above 5% only: perf report --percent-limit 5)\n", "#\n" ] } ], "source": [ - "# perfgenerate displays the top hot routines \n", - "!make perfgenerate" + "# run_perf_recrep displays the top hot routines \n", + "!make run_perf_recrep" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "**TASK**: Now Add the optimization flag `Ofast` in Makefile for target poisson2d_Ofast. Run the makefile with the targets clean, run, runstats and perfgenerate.\n", - "\n", - "Compare performance of O3 and Ofast binaries using the following makefile options- run, runstats. \n", - "\n", - "Verify by generating the profiles of both O3 and Ofast binaries to understand the cause for performance improvement. \n", + "**TASK**: Now add the optimization flag `Ofast` to the `CFLAGS` for target `poisson2d_Ofast`. Compile the program with the target `poisson2d_Ofast` and run and analyse it as before with `run`, `run_perf` and `run_perf_recrep`.\n", "\n", - "What difference do you see? \n", - "\n" + "What difference do you see?" ] }, { "cell_type": "code", - "execution_count": 58, + "execution_count": 76, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "gcc -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec poisson2d.c poisson2d_reference.o -o poisson2d_Ofast -lm\n", - "cp poisson2d_Ofast poisson2d\n", + "gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast poisson2d.c poisson2d_reference.o -o poisson2d -lm\n", "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", - "Job <24743> is submitted to default queue <batch>.\n", + "Job <24901> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", @@ -350,7 +367,7 @@ " 700, 0.243173\n", " 800, 0.242228\n", " 900, 0.241291\n", - "2.40user 0.00system 0:02.41elapsed 99%CPU (0avgtext+0avgdata 24256maxresident)k\n", + "2.41user 0.00system 0:02.41elapsed 99%CPU (0avgtext+0avgdata 24256maxresident)k\n", "256inputs+0outputs (0major+480minor)pagefaults 0swaps\n" ] } @@ -362,16 +379,14 @@ }, { "cell_type": "markdown", - "metadata": { - "exercise": "Solutions" - }, + "metadata": {}, "source": [ - "Measure cycles, instructions." + "Again, run a `perf`-instrumented version:" ] }, { "cell_type": "code", - "execution_count": 59, + "execution_count": 77, "metadata": {}, "outputs": [ { @@ -379,7 +394,7 @@ "output_type": "stream", "text": [ "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,instructions ./poisson2d\n", - "Job <24744> is submitted to default queue <batch>.\n", + "Job <24902> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", @@ -397,38 +412,36 @@ "\n", " Performance counter stats for './poisson2d':\n", "\n", - " 8261506450 cycles:u \n", - " 12013095395 instructions:u # 1.45 insn per cycle \n", + " 8258991976 cycles:u \n", + " 12013091172 instructions:u # 1.45 insn per cycle \n", "\n", - " 2.413121525 seconds time elapsed\n", + " 2.408703909 seconds time elapsed\n", "\n" ] } ], "source": [ - "!make runstats" + "!make run_perf" ] }, { "cell_type": "markdown", - "metadata": { - "exercise": "Solutions" - }, + "metadata": {}, "source": [ - "Generate the list of Top routines in terms of hotness." + "Generate the list of top routines in terms of hotness:" ] }, { "cell_type": "code", - "execution_count": 60, + "execution_count": 78, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf record -e cycles --output=/gpfs/wolf/trn003/scratch/archanaravindar//cycles.data ./poisson2d\n", - "Job <24745> is submitted to default queue <batch>.\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf record -e cycles --output=/gpfs/wolf/trn003/scratch/aherten//cycles.data ./poisson2d\n", + "Job <24903> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", @@ -444,9 +457,9 @@ " 800, 0.242228\n", " 900, 0.241291\n", "[ perf record: Woken up 2 times to write data ]\n", - "[ perf record: Captured and wrote 0.382 MB /gpfs/wolf/trn003/scratch/archanaravindar//cycles.data (9722 samples) ]\n", - "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf report -i /gpfs/wolf/trn003/scratch/archanaravindar//cycles.data --stdio\n", - "Job <24746> is submitted to default queue <batch>.\n", + "[ perf record: Captured and wrote 0.382 MB /gpfs/wolf/trn003/scratch/aherten//cycles.data (9728 samples) ]\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf report -i /gpfs/wolf/trn003/scratch/aherten//cycles.data --stdio\n", + "Job <24904> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "# To display the perf.data header info, please use --header/--header-only options.\n", @@ -455,36 +468,36 @@ "# Total Lost Samples: 0\n", "#\n", "# Samples: 9K of event 'cycles:u'\n", - "# Event count (approx.): 8264083365\n", + "# Event count (approx.): 8268811890\n", "#\n", "# Overhead Command Shared Object Symbol \n", "# ........ ......... ............. ........................................\n", "#\n", - " 81.24% poisson2d poisson2d [.] main\n", - " 17.83% poisson2d libc-2.17.so [.] __memcpy_power7\n", + " 81.12% poisson2d poisson2d [.] main\n", + " 17.97% poisson2d libc-2.17.so [.] __memcpy_power7\n", " 0.79% poisson2d libm-2.17.so [.] __exp_finite\n", - " 0.06% poisson2d poisson2d [.] 00000038.plt_call.memcpy@@GLIBC_2.17\n", - " 0.02% poisson2d ld-2.17.so [.] check_match.10253\n", - " 0.02% poisson2d ld-2.17.so [.] _dl_lookup_symbol_x\n", - " 0.01% poisson2d ld-2.17.so [.] strcmp\n", + " 0.04% poisson2d poisson2d [.] 00000038.plt_call.memcpy@@GLIBC_2.17\n", + " 0.02% poisson2d ld-2.17.so [.] do_lookup_x\n", " 0.01% poisson2d libc-2.17.so [.] vfprintf@@GLIBC_2.17\n", - " 0.01% poisson2d libc-2.17.so [.] __memset_power8\n", " 0.01% poisson2d libc-2.17.so [.] _dl_addr\n", - " 0.01% poisson2d ld-2.17.so [.] do_lookup_x\n", - " 0.00% poisson2d ld-2.17.so [.] open_verify\n", - " 0.00% poisson2d ld-2.17.so [.] strlen\n", + " 0.01% poisson2d ld-2.17.so [.] _dl_relocate_object\n", + " 0.01% poisson2d ld-2.17.so [.] check_match.10253\n", + " 0.01% poisson2d ld-2.17.so [.] _dl_lookup_symbol_x\n", + " 0.01% poisson2d ld-2.17.so [.] strcmp\n", + " 0.00% poisson2d ld-2.17.so [.] open_path\n", + " 0.00% poisson2d ld-2.17.so [.] init_tls\n", + " 0.00% poisson2d ld-2.17.so [.] _dl_sysdep_start\n", " 0.00% poisson2d ld-2.17.so [.] _start\n", "\n", "\n", "#\n", - "# (Tip: Profiling branch (mis)predictions with: perf record -b / perf report)\n", + "# (Tip: For tracepoint events, try: perf report -s trace_fields)\n", "#\n" ] } ], "source": [ - "# perfgenerate creates a perf.data file \n", - "!make perfgenerate" + "!make run_perf_recrep" ] }, { @@ -500,7 +513,7 @@ "source": [ "#### Interpretation\n", "\n", - "Depending on the application requirement, if a high precision of results is not mandatory, the users can compile an application with `-Ofast` which enables `–ffast-math` option that implements the same math function in a relaxed manner very similar to how general mathematical expressions are implemented and avoids the overhead of calling a function from the math library. Comparing the files, you will see that the `-Ofast` binary natively implements the `fmax` function using instructions available in the hardware. The `-O3` binary makes a library call to compute `fmax` to follow a stricter _IEEE_ requirement for accuracy." + "Depending on the application requirement, if a high precision of results is not mandatory, one can compile an application with `-Ofast` which enables `–ffast-math` option that implements the same math function in a relaxed manner very similar to how general mathematical expressions are implemented and avoids the overhead of calling a function from the math library. Comparing the files, you will see that the `-Ofast` binary natively implements the `fmax` function using instructions available in the hardware. The `-O3` binary makes a library call to compute `fmax` to follow a stricter _IEEE_ requirement for accuracy." ] }, { @@ -511,66 +524,71 @@ "\n", "For the first level of optimization we see that `Ofast` cut the execution time of the `O3` binary by almost half.\n", "\n", - "We can optimize the performance further by using profile directed feedback optimization.\n", + "We can optimize the performance further by using profile-directed feedback optimization.\n", + "\n", + "To compile using profile-directed feedback with the GCC compiler we need to build the appplication in three stages:\n", + "\n", + "1. Instrument binary;\n", + "2. Run binary with training, gather profile information;\n", + "3. Use profile information to generate optimized binary.\n", "\n", - "To compile using profile directed feedback with the GCC compiler we need to build the appplication in three stages- Instrument binary; Run binary with training (smaller input) and gather profile information; Use profile information to generate optimized binary. For this purpose we have defined makefile target runpdf that depends on two binaries- poisson2d_train and poisson2d_ref.\n", "\n", - "make runpdf will inturn make poisson2d_train and poisson2d_ref\n", + "Step 1 is achieved by compiling the binary with the correct flag – `-fprofile-generate`. In our case, we need to specify an output location, which should be `$(SC19_DIR_SCRATCH)`.\n", "\n", - "poisson2d_train: will inturn be built by \n", - " - cleans up old profile data if any (rm \\$(SC19\\_DIR\\_SCRATCH)/*gcda)\n", - " - builds a training binary using -fprofile-generate=$(SC19_DIR_SCRATCH) along with the usual optimization flags \n", - " - This instructs the compiler to record hot path information.\n", - " - Run the training binary with a smaller input size; \n", - " - you should see a `.gcda` file generated which stores hot path information for further optimization by the compiler in the path specified in profile-generate \n", - " option\n", - " \n", - " - poisson2d_ref: will be built using the profile collected in \\$(SC19\\_DIR\\_SCRATCH)/*gcda files \n", - " - This is facilitated by the option -fprofile-use=\\$(SC19\\_DIR\\_SCRATCH) option to be added along with optimization flags\n", - " - Rebuilding the application with this flag builds the final binary that can be run\n", + "Step 2 consists of a usual, albeit shorter run of the instrumented binary. The can be very short, though the parameters need to be representative of the actual run. After the binary ran, an output file (with file extension `.gcda`) is written to the directory specified during compilation.\n", "\n", - "**TASK**: Run the following steps to generate the final optimized binary using the following steps. Compare the performance of the Ofast binary with and without profile directed feedback. Run the program poisson2d with increasing values of xiter, yiter, ziter and compare the performance. " + "For Step 3, the binary is once again compiled, but this time using the `gcda` profile just generated. The according flag is `-fprofile-use`, which we set to `$(SC19_DIR_SCRATCH)` as well.\n", + "\n", + "In our `Makefile` at hand, we prepared the steps already for you in the form of two targets.\n", + "\n", + "* `poisson2d_train`: Will compile the binary with profile-directed feedback\n", + "* `poisson2d_ref`: Will take a generated profile and compile a new, optimized binary\n", + "\n", + "By using dependencies, between these two targets a profile run is launched.\n", + "\n", + "**TASK**: Edit the [Makefile](`Makefile`) and add the `-fprofile-*` flags to the `CFLAGS` of `poisson2d_train` and\n", + "`poisson2d_ref` as outline in the file.\n", + "\n", + "After that, you may launch them with the following cells (`gen_profile` is a meta-target and uses `poisson2d_train` and `poisson2d_ref`). If you need to clean the generated profile, you may use `make clean_profile`." ] }, { "cell_type": "code", - "execution_count": 61, + "execution_count": 79, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "rm -rf /gpfs/wolf/trn003/scratch/archanaravindar//*gcda\n", - "gcc -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec -fprofile-generate=/gpfs/wolf/trn003/scratch/archanaravindar/ poisson2d.c -o poisson2d_train -lm \n", + "gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast -fprofile-generate=/gpfs/wolf/trn003/scratch/aherten/ poisson2d.c -o poisson2d_train -lm \n", "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS ./poisson2d_train 100 100 100\n", - "Job <24747> is submitted to default queue <batch>.\n", + "Job <24905> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 100 iterations on 100 x 100 mesh\n", "Calculate current execution.\n", " 0, 0.249490\n", - "gcc -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec -fprofile-use=/gpfs/wolf/trn003/scratch/archanaravindar/ poisson2d.c -o poisson2d_ref -lm \n", - "cp poisson2d_ref poisson2d\t\n" + "echo `date` > /gpfs/wolf/trn003/scratch/aherten//.profile_generated\n", + "gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast -fprofile-use=/gpfs/wolf/trn003/scratch/aherten/ poisson2d.c -o poisson2d_ref -lm \n", + "cp poisson2d_ref poisson2d\n" ] } ], "source": [ - "!make -B runpdf" + "!make gen_profile" ] }, { "cell_type": "markdown", - "metadata": { - "exercise": "Solutions" - }, + "metadata": {}, "source": [ - "Measure Execution time." + "If the previous cell executed correctly, you now have your optimized executable. Let's see if it even fast than before!" ] }, { "cell_type": "code", - "execution_count": 62, + "execution_count": 80, "metadata": {}, "outputs": [ { @@ -578,7 +596,7 @@ "output_type": "stream", "text": [ "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", - "Job <24748> is submitted to default queue <batch>.\n", + "Job <24906> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", @@ -593,27 +611,34 @@ " 700, 0.243173\n", " 800, 0.242228\n", " 900, 0.241291\n", - "2.30user 0.00system 0:02.30elapsed 99%CPU (0avgtext+0avgdata 24192maxresident)k\n", - "256inputs+0outputs (0major+480minor)pagefaults 0swaps\n" + "2.28user 0.01system 0:02.30elapsed 99%CPU (0avgtext+0avgdata 24192maxresident)k\n", + "256inputs+0outputs (0major+479minor)pagefaults 0swaps\n" ] } ], "source": [ - "!make run " + "!make run" ] }, { "cell_type": "markdown", "metadata": { - "exercise": "Solutions" + "exercise": "solution" }, "source": [ - "Measure cycles, instructions." + "Great! It is! In our tests, this shaved off another 5%." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's also measure instructions and cycles" ] }, { "cell_type": "code", - "execution_count": 64, + "execution_count": 81, "metadata": {}, "outputs": [ { @@ -621,7 +646,7 @@ "output_type": "stream", "text": [ "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,instructions ./poisson2d\n", - "Job <24749> is submitted to default queue <batch>.\n", + "Job <24907> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", @@ -639,16 +664,16 @@ "\n", " Performance counter stats for './poisson2d':\n", "\n", - " 7907873140 cycles:u \n", - " 12253084984 instructions:u # 1.55 insn per cycle \n", + " 7925983538 cycles:u \n", + " 12253080719 instructions:u # 1.55 insn per cycle \n", "\n", - " 2.308084004 seconds time elapsed\n", + " 2.313471365 seconds time elapsed\n", "\n" ] } ], "source": [ - "!make runstats" + "!make run_perf" ] }, { @@ -658,168 +683,29 @@ "What is your speed-up? Feel free to run with larger problem sizes (mesh; iterations)" ] }, - { - "cell_type": "markdown", - "metadata": { - "exercise": "Solutions" - }, - "source": [ - "For the problem size `NX=NY=NITER=1000` you will see that profile directed feedback improves the performance further by 4-5%. " - ] - }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Part C: Compiler annotations/Remarks\n", "\n", - "Usually all compilers provide an option to emit annotations or remarks by the compiler. These remarks summarize the optimizations done in detail, the location in source where these optimizations were done. There exist options that also indicate optimizations that were missed and the reason why they could not be done. \n", - "\n", - "To generate compiler annotations using GCC, start with Makefile.gcc. Add the flag -fopt-info-all=`$(SC19_DIR_SCRATCH)/filename` to CFLAGS. All annotations are stored in opt-record in the $(SC19_DIR_SCRATCH) which can be viewed as a text file. Make target view displays the contents of the file on the output screen.\n", + "Usually, all compilers provide an option to emit annotations or remarks by the compiler. These remarks summarize the optimizations done in detail, the location in source where these optimizations were done. There exist options that also indicate optimizations that were missed and the reason why they could not be done. \n", "\n", - "Specifically, if you want to view only the missed options, use the option -fopt-info-missed instead of -fopt-info-all.\n", + "To generate compiler annotations using GCC, one uses `-fopt-info-all`. If you only want to see the missed options, use the option `-fopt-info-missed` instead of `-fopt-info-all`. See also the [documentation of GCC regarding the flag](https://gcc.gnu.org/onlinedocs/gcc/Developer-Options.html#index-fopt-info).\n", "\n", - "**TASK**: Build the application using Makefile.gcc.record. This makefile generates the compiler annotations in $(SC19_DIR_SCRATCH)/opt-record. Using make file target view or vi, read the compiler annotations file to get an idea of the optimizations done. " - ] - }, - { - "cell_type": "code", - "execution_count": 76, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "gcc -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec -fopt-info-all=/gpfs/wolf/trn003/scratch/archanaravindar//opt-record poisson2d.c poisson2d_reference.o -o poisson2d_Ofast_record -lm \n", - "cp poisson2d_Ofast_record poisson2d\n" - ] - } - ], - "source": [ - "!make -B poisson2d_Ofast_record" + "**TASK**: Have a looK at the `CFLAGS` of the `Makefile` target `poisson2d_Ofast_info`. Add the flag `-fopt-info-all` to the list of flags. This will print optimisation information to stdout. If you rather want to print to this information to a file, use – for example – `-fopt-info-all=(SC19_DIR_SCRATCH)/filename`." ] }, { "cell_type": "code", - "execution_count": 73, + "execution_count": 82, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "poisson2d_reference.c:85:17: missed: not inlinable: check_results/17 -> fprintf/20, function body not available\n", - "poisson2d_reference.c:70:31: missed: not inlinable: poisson2d_reference/16 -> printf/18, function body not available\n", - "Unit growth for small function inlining: 145->145 (0%)\n", - "\n", - "Inlined 0 calls, eliminated 0 functions\n", - "\n", - "consider run-time aliasing test between *_54 and *_57\n", - "consider run-time aliasing test between *_62 and *_67\n", - "consider run-time aliasing test between *_75 and *_78\n", - "consider run-time aliasing test between *_82 and *_87\n", - "poisson2d_reference.c:52:13: optimized: Loop 6 distributed: split to 0 loops and 1 library calls.\n", - "poisson2d_reference.c:41:9: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:41:9: missed: not vectorized: loop nest containing two or more consecutive inner loops cannot be vectorized\n", - "poisson2d_reference.c:64:9: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:64:9: missed: Loop costings may not be worthwhile.\n", - "poisson2d_reference.c:59:9: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:59:9: missed: Loop costings may not be worthwhile.\n", - "poisson2d_reference.c:50:9: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:33:6: missed: statement clobbers memory: __builtin_memcpy (_386, _378, _390);\n", - "poisson2d_reference.c:41:9: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:45:90: missed: not vectorized: complicated access pattern.\n", - "poisson2d_reference.c:43:13: optimized: loop vectorized using 16 byte vectors\n", - "poisson2d_reference.c:33:6: note: vectorized 1 loops in function.\n", - "poisson2d_reference.c:33:6: missed: statement clobbers memory: __builtin_memcpy (_386, _378, _390);\n", - "poisson2d_reference.c:43:13: optimized: loop turned into non-loop; it never loops\n", - "poisson2d_reference.c:33:6: missed: statement clobbers memory: __builtin_memcpy (_386, _378, _390);\n", - "poisson2d_reference.c:70:31: missed: statement clobbers memory: printf (\"%5d, %0.6f\\n\", iter_134, error_144);\n", - "poisson2d_reference.c:81:9: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:81:9: missed: not vectorized: control flow in loop.\n", - "poisson2d_reference.c:83:35: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:83:35: missed: not vectorized: control flow in loop.\n", - "poisson2d_reference.c:76:5: note: vectorized 0 loops in function.\n", - "poisson2d_reference.c:85:17: missed: statement clobbers memory: fprintf (stderr.0_13, \"ERROR: A[%d][%d] = %f does not match %f (reference)\\n\", iy_62, ix_61, _63, _64);\n", - "poisson2d.c:57:14: optimized: Inlining atoi/24 into main/32 (always_inline).\n", - "poisson2d.c:56:14: optimized: Inlining atoi/24 into main/32 (always_inline).\n", - "poisson2d.c:51:14: optimized: Inlining atoi/24 into main/32 (always_inline).\n", - "poisson2d.c:47:20: optimized: Inlining atoi/24 into main/32 (always_inline).\n", - "poisson2d.c:157:5: missed: not inlinable: main/32 -> free/39, function body not available\n", - "poisson2d.c:156:5: missed: not inlinable: main/32 -> free/39, function body not available\n", - "poisson2d.c:155:5: missed: not inlinable: main/32 -> free/39, function body not available\n", - "poisson2d.c:154:5: missed: not inlinable: main/32 -> free/39, function body not available\n", - "poisson2d.c:145:9: missed: not inlinable: main/32 -> check_results/38, function body not available\n", - "poisson2d.c:138:31: missed: not inlinable: main/32 -> printf/35, function body not available\n", - "poisson2d.c:98:5: missed: not inlinable: main/32 -> __builtin_puts/36, function body not available\n", - "poisson2d.c:95:5: missed: not inlinable: main/32 -> poisson2d_reference/37, function body not available\n", - "poisson2d.c:93:5: missed: not inlinable: main/32 -> __builtin_puts/36, function body not available\n", - "poisson2d.c:91:5: missed: not inlinable: main/32 -> printf/35, function body not available\n", - "poisson2d.c:73:29: missed: not inlinable: main/32 -> exp/34, function body not available\n", - "poisson2d.c:63:41: missed: not inlinable: main/32 -> malloc/33, function body not available\n", - "poisson2d.c:62:41: missed: not inlinable: main/32 -> malloc/33, function body not available\n", - "poisson2d.c:61:41: missed: not inlinable: main/32 -> malloc/33, function body not available\n", - "poisson2d.c:60:41: missed: not inlinable: main/32 -> malloc/33, function body not available\n", - "/usr/include/stdlib.h:280:16: missed: not inlinable: main/32 -> strtol/40, function body not available\n", - "/usr/include/stdlib.h:280:16: missed: not inlinable: main/32 -> strtol/40, function body not available\n", - "/usr/include/stdlib.h:280:16: missed: not inlinable: main/32 -> strtol/40, function body not available\n", - "/usr/include/stdlib.h:280:16: missed: not inlinable: main/32 -> strtol/40, function body not available\n", - "Unit growth for small function inlining: 234->234 (0%)\n", - "\n", - "Inlined 4 calls, eliminated 0 functions\n", - "\n", - "consider run-time aliasing test between *_84 and *_87\n", - "consider run-time aliasing test between *_92 and *_97\n", - "consider run-time aliasing test between *_104 and *_107\n", - "consider run-time aliasing test between *_111 and *_115\n", - "poisson2d.c:120:13: optimized: Loop 8 distributed: split to 0 loops and 1 library calls.\n", - "poisson2d.c:85:9: optimized: Loop 10 distributed: split to 0 loops and 1 library calls.\n", - "poisson2d.c:103:25: missed: couldn't vectorize loop\n", - "poisson2d.c:103:25: missed: not vectorized: loop nest containing two or more consecutive inner loops cannot be vectorized\n", - "poisson2d.c:132:9: missed: couldn't vectorize loop\n", - "poisson2d.c:132:9: missed: Loop costings may not be worthwhile.\n", - "poisson2d.c:127:9: missed: couldn't vectorize loop\n", - "poisson2d.c:127:9: missed: Loop costings may not be worthwhile.\n", - "poisson2d.c:118:9: missed: couldn't vectorize loop\n", - "poisson2d.c:38:5: missed: statement clobbers memory: __builtin_memcpy (_557, _553, _563);\n", - "poisson2d.c:107:9: missed: couldn't vectorize loop\n", - "poisson2d.c:107:9: missed: not vectorized: control flow in loop.\n", - "poisson2d.c:110:13: optimized: loop vectorized using 16 byte vectors\n", - "poisson2d.c:83:5: missed: couldn't vectorize loop\n", - "poisson2d.c:38:5: missed: statement clobbers memory: __builtin_memset (_542, 0, _545);\n", - "poisson2d.c:67:5: missed: couldn't vectorize loop\n", - "poisson2d.c:73:27: missed: not vectorized: complicated access pattern.\n", - "poisson2d.c:69:9: missed: couldn't vectorize loop\n", - "poisson2d.c:73:29: missed: not vectorized: relevant stmt not supported: _27 = exp (_21);\n", - "poisson2d.c:38:5: note: vectorized 1 loops in function.\n", - "poisson2d.c:38:5: missed: statement clobbers memory: __builtin_memset (_542, 0, _545);\n", - "poisson2d.c:38:5: missed: statement clobbers memory: __builtin_memcpy (_557, _553, _563);\n", - "poisson2d.c:110:13: optimized: loop turned into non-loop; it never loops\n", - "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _195 = strtol (_1, 0B, 10);\n", - "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _197 = strtol (_2, 0B, 10);\n", - "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _201 = strtol (_3, 0B, 10);\n", - "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _199 = strtol (_4, 0B, 10);\n", - "poisson2d.c:60:41: missed: statement clobbers memory: A_155 = malloc (_7);\n", - "poisson2d.c:61:41: missed: statement clobbers memory: Aref_157 = malloc (_7);\n", - "poisson2d.c:62:41: missed: statement clobbers memory: Anew_159 = malloc (_7);\n", - "poisson2d.c:63:41: missed: statement clobbers memory: rhs_161 = malloc (_7);\n", - "poisson2d.c:38:5: missed: statement clobbers memory: __builtin_memset (_542, 0, _545);\n", - "poisson2d.c:91:5: missed: statement clobbers memory: printf (\"Jacobi relaxation calculation: max %d iterations on %d x %d mesh\\n\", iter_max_239, ny_221, nx_124);\n", - "poisson2d.c:93:5: missed: statement clobbers memory: __builtin_puts (&\"Calculate reference solution and time with serial CPU execution.\"[0]);\n", - "poisson2d.c:95:5: missed: statement clobbers memory: poisson2d_reference (iter_max_239, 1.00000000000000008180305391403130954586231382563710212708e-5, Aref_225, Anew_227, nx_124, ny_221, rhs_236);\n", - "poisson2d.c:98:5: missed: statement clobbers memory: __builtin_puts (&\"Calculate current execution.\"[0]);\n", - "poisson2d.c:38:5: missed: statement clobbers memory: __builtin_memcpy (_557, _553, _563);\n", - "poisson2d.c:138:31: missed: statement clobbers memory: printf (\"%5d, %0.6f\\n\", iter_245, error_242);\n", - "poisson2d.c:145:9: missed: statement clobbers memory: _118 = check_results (1, prephitmp_426, 1, _131, 1.00000000000000008180305391403130954586231382563710212708e-5, A_125, Aref_225, nx_124);\n", - "poisson2d.c:154:5: missed: statement clobbers memory: free (rhs_236);\n", - "poisson2d.c:155:5: missed: statement clobbers memory: free (Anew_227);\n", - "poisson2d.c:156:5: missed: statement clobbers memory: free (Aref_225);\n", - "poisson2d.c:157:5: missed: statement clobbers memory: free (A_125);\n", - "poisson2d.c:60:41: missed: statement clobbers memory: A_146 = malloc (2000000);\n", - "poisson2d.c:61:41: missed: statement clobbers memory: Aref_145 = malloc (2000000);\n", - "poisson2d.c:62:41: missed: statement clobbers memory: Anew_144 = malloc (2000000);\n", - "poisson2d.c:63:41: missed: statement clobbers memory: rhs_130 = malloc (2000000);\n", + "gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast -fopt-info-all poisson2d.c poisson2d_reference.o -o poisson2d_Ofast_info -lm\n", "poisson2d.c:62:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", "poisson2d.c:61:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", "poisson2d.c:56:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", @@ -849,843 +735,78 @@ "poisson2d.c:124:13: optimized: Loop 8 distributed: split to 0 loops and 1 library calls.\n", "poisson2d.c:90:9: optimized: Loop 10 distributed: split to 0 loops and 1 library calls.\n", "poisson2d.c:108:25: missed: couldn't vectorize loop\n", - "poisson2d.c:108:25: missed: not vectorized: loop nest containing two or more consecutive inner loops cannot be vectorized\n", - "poisson2d.c:136:9: missed: couldn't vectorize loop\n", - "poisson2d.c:136:9: missed: Loop costings may not be worthwhile.\n", - "poisson2d.c:131:9: missed: couldn't vectorize loop\n", - "poisson2d.c:131:9: missed: Loop costings may not be worthwhile.\n", - "poisson2d.c:122:9: missed: couldn't vectorize loop\n", - "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_543, _539, _549);\n", - "poisson2d.c:112:9: missed: couldn't vectorize loop\n", - "poisson2d.c:112:9: missed: not vectorized: control flow in loop.\n", - "poisson2d.c:114:13: optimized: loop vectorized using 16 byte vectors\n", - "poisson2d.c:88:5: missed: couldn't vectorize loop\n", - "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_528, 0, _531);\n", - "poisson2d.c:72:5: missed: couldn't vectorize loop\n", - "poisson2d.c:78:27: missed: not vectorized: complicated access pattern.\n", - "poisson2d.c:74:9: missed: couldn't vectorize loop\n", - "poisson2d.c:78:29: missed: not vectorized: relevant stmt not supported: _27 = exp (_21);\n", - "poisson2d.c:43:5: note: vectorized 1 loops in function.\n", - "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_528, 0, _531);\n", - "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_543, _539, _549);\n", - "poisson2d.c:114:13: optimized: loop turned into non-loop; it never loops\n", - "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _187 = strtol (_1, 0B, 10);\n", - "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _189 = strtol (_2, 0B, 10);\n", - "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _193 = strtol (_3, 0B, 10);\n", - "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _191 = strtol (_4, 0B, 10);\n", - "poisson2d.c:65:41: missed: statement clobbers memory: A_153 = malloc (_7);\n", - "poisson2d.c:67:41: missed: statement clobbers memory: Anew_155 = malloc (_7);\n", - "poisson2d.c:68:41: missed: statement clobbers memory: rhs_157 = malloc (_7);\n", - "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_528, 0, _531);\n", - "poisson2d.c:96:5: missed: statement clobbers memory: printf (\"Jacobi relaxation calculation: max %d iterations on %d x %d mesh\\n\", iter_max_130, ny_139, nx_195);\n", - "poisson2d.c:103:5: missed: statement clobbers memory: __builtin_puts (&\"Calculate current execution.\"[0]);\n", - "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_543, _539, _549);\n", - "poisson2d.c:142:31: missed: statement clobbers memory: printf (\"%5d, %0.6f\\n\", iter_237, error_219);\n", - "poisson2d.c:158:5: missed: statement clobbers memory: free (rhs_202);\n", - "poisson2d.c:159:5: missed: statement clobbers memory: free (Anew_124);\n", - "poisson2d.c:161:5: missed: statement clobbers memory: free (A_123);\n", - "poisson2d.c:65:41: missed: statement clobbers memory: A_144 = malloc (8000000);\n", - "poisson2d.c:67:41: missed: statement clobbers memory: Anew_143 = malloc (8000000);\n", - "poisson2d.c:68:41: missed: statement clobbers memory: rhs_142 = malloc (8000000);\n", - "poisson2d.c:62:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", - "poisson2d.c:61:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", - "poisson2d.c:56:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", - "poisson2d.c:52:20: optimized: Inlining atoi/24 into main/33 (always_inline).\n", - "poisson2d.c:161:5: missed: not inlinable: main/33 -> free/38, function body not available\n", - "poisson2d.c:159:5: missed: not inlinable: main/33 -> free/38, function body not available\n", - "poisson2d.c:158:5: missed: not inlinable: main/33 -> free/38, function body not available\n", - "poisson2d.c:142:31: missed: not inlinable: main/33 -> printf/36, function body not available\n", - "poisson2d.c:103:5: missed: not inlinable: main/33 -> __builtin_puts/37, function body not available\n", - "poisson2d.c:96:5: missed: not inlinable: main/33 -> printf/36, function body not available\n", - "poisson2d.c:78:29: missed: not inlinable: main/33 -> exp/35, function body not available\n", - "poisson2d.c:68:41: missed: not inlinable: main/33 -> malloc/34, function body not available\n", - "poisson2d.c:67:41: missed: not inlinable: main/33 -> malloc/34, function body not available\n", - "poisson2d.c:65:41: missed: not inlinable: main/33 -> malloc/34, function body not available\n", - "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", - "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", - "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", - "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", - "Unit growth for small function inlining: 207->207 (0%)\n", - "\n", - "Inlined 4 calls, eliminated 0 functions\n", - "\n", - "consider run-time aliasing test between *_84 and *_87\n", - "consider run-time aliasing test between *_92 and *_97\n", - "consider run-time aliasing test between *_104 and *_107\n", - "consider run-time aliasing test between *_111 and *_115\n", - "poisson2d.c:124:13: optimized: Loop 8 distributed: split to 0 loops and 1 library calls.\n", - "poisson2d.c:90:9: optimized: Loop 10 distributed: split to 0 loops and 1 library calls.\n", - "poisson2d.c:108:25: missed: couldn't vectorize loop\n", - "poisson2d.c:108:25: missed: not vectorized: loop nest containing two or more consecutive inner loops cannot be vectorized\n", - "poisson2d.c:136:9: missed: couldn't vectorize loop\n", - "poisson2d.c:136:9: missed: Loop costings may not be worthwhile.\n", - "poisson2d.c:131:9: missed: couldn't vectorize loop\n", - "poisson2d.c:131:9: missed: Loop costings may not be worthwhile.\n", - "poisson2d.c:122:9: missed: couldn't vectorize loop\n", - "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_543, _539, _549);\n", - "poisson2d.c:112:9: missed: couldn't vectorize loop\n", - "poisson2d.c:112:9: missed: not vectorized: control flow in loop.\n", - "poisson2d.c:114:13: optimized: loop vectorized using 16 byte vectors\n", - "poisson2d.c:88:5: missed: couldn't vectorize loop\n", - "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_528, 0, _531);\n", - "poisson2d.c:72:5: missed: couldn't vectorize loop\n", - "poisson2d.c:78:27: missed: not vectorized: complicated access pattern.\n", - "poisson2d.c:74:9: missed: couldn't vectorize loop\n", - "poisson2d.c:78:29: missed: not vectorized: relevant stmt not supported: _27 = exp (_21);\n", - "poisson2d.c:43:5: note: vectorized 1 loops in function.\n", - "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_528, 0, _531);\n", - "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_543, _539, _549);\n", - "poisson2d.c:114:13: optimized: loop turned into non-loop; it never loops\n", - "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _187 = strtol (_1, 0B, 10);\n", - "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _189 = strtol (_2, 0B, 10);\n", - "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _193 = strtol (_3, 0B, 10);\n", - "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _191 = strtol (_4, 0B, 10);\n", - "poisson2d.c:65:41: missed: statement clobbers memory: A_153 = malloc (_7);\n", - "poisson2d.c:67:41: missed: statement clobbers memory: Anew_155 = malloc (_7);\n", - "poisson2d.c:68:41: missed: statement clobbers memory: rhs_157 = malloc (_7);\n", - "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_528, 0, _531);\n", - "poisson2d.c:96:5: missed: statement clobbers memory: printf (\"Jacobi relaxation calculation: max %d iterations on %d x %d mesh\\n\", iter_max_130, ny_139, nx_195);\n", - "poisson2d.c:103:5: missed: statement clobbers memory: __builtin_puts (&\"Calculate current execution.\"[0]);\n", - "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_543, _539, _549);\n", - "poisson2d.c:142:31: missed: statement clobbers memory: printf (\"%5d, %0.6f\\n\", iter_237, error_219);\n", - "poisson2d.c:158:5: missed: statement clobbers memory: free (rhs_202);\n", - "poisson2d.c:159:5: missed: statement clobbers memory: free (Anew_124);\n", - "poisson2d.c:161:5: missed: statement clobbers memory: free (A_123);\n", - "poisson2d.c:65:41: missed: statement clobbers memory: A_144 = malloc (8000000);\n", - "poisson2d.c:67:41: missed: statement clobbers memory: Anew_143 = malloc (8000000);\n", - "poisson2d.c:68:41: missed: statement clobbers memory: rhs_142 = malloc (8000000);\n" - ] - } - ], - "source": [ - "!cat \"$SC19_DIR_SCRATCH\"/opt-record" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**TASK**: \n", - "Modify Makefile.gcc.use to include the option -fopt-info-all=$(SC19_DIR_SCRATCH)/pgo-opt-record in CFLAGS.\n", - "Build the profile directed feedback binary using the steps below and generate the final optimized binary and also the annotations for the profile directed feedback pass. Compare opt-record and pgo-opt-record and note the differences. which file has greater annotations. How different are the annotations when compared to each other? Can you spot any differences? " - ] - }, - { - "cell_type": "code", - "execution_count": 74, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "rm -rf /gpfs/wolf/trn003/scratch/archanaravindar//*gcda\n", - "gcc -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec -fprofile-generate=/gpfs/wolf/trn003/scratch/archanaravindar/ poisson2d.c -o poisson2d_train -lm \n", - "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS ./poisson2d_train 100 100 100\n", - "Job <24750> is submitted to default queue <batch>.\n", - "<<Waiting for dispatch ...>>\n", - "<<Starting on login1>>\n", - "Jacobi relaxation calculation: max 100 iterations on 100 x 100 mesh\n", - "Calculate current execution.\n", - " 0, 0.249490\n", - "gcc -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec -fprofile-use=/gpfs/wolf/trn003/scratch/archanaravindar/ -fopt-info-all=/gpfs/wolf/trn003/scratch/archanaravindar//pgo-opt-record poisson2d.c -o poisson2d_ref_record -lm \n", - "cp poisson2d_ref_record poisson2d\n" - ] - } - ], - "source": [ - "!make runpdf.record" - ] - }, - { - "cell_type": "code", - "execution_count": 75, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "poisson2d_reference.c:85:17: missed: not inlinable: check_results/17 -> fprintf/20, function body not available\n", - "poisson2d_reference.c:70:31: missed: not inlinable: poisson2d_reference/16 -> printf/18, function body not available\n", - "Unit growth for small function inlining: 145->145 (0%)\n", - "\n", - "Inlined 0 calls, eliminated 0 functions\n", - "\n", - "consider run-time aliasing test between *_54 and *_57\n", - "consider run-time aliasing test between *_62 and *_67\n", - "consider run-time aliasing test between *_75 and *_78\n", - "consider run-time aliasing test between *_82 and *_87\n", - "poisson2d_reference.c:52:13: optimized: Loop 6 distributed: split to 0 loops and 1 library calls.\n", - "poisson2d_reference.c:41:9: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:41:9: missed: not vectorized: loop nest containing two or more consecutive inner loops cannot be vectorized\n", - "poisson2d_reference.c:64:9: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:64:9: missed: Loop costings may not be worthwhile.\n", - "poisson2d_reference.c:59:9: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:59:9: missed: Loop costings may not be worthwhile.\n", - "poisson2d_reference.c:50:9: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:33:6: missed: statement clobbers memory: __builtin_memcpy (_384, _376, _388);\n", - "poisson2d_reference.c:41:9: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:45:90: missed: not vectorized: complicated access pattern.\n", - "poisson2d_reference.c:43:13: optimized: loop vectorized using 16 byte vectors\n", - "poisson2d_reference.c:33:6: note: vectorized 1 loops in function.\n", - "poisson2d_reference.c:33:6: missed: statement clobbers memory: __builtin_memcpy (_384, _376, _388);\n", - "poisson2d_reference.c:43:13: optimized: loop turned into non-loop; it never loops\n", - "poisson2d_reference.c:33:6: missed: statement clobbers memory: __builtin_memcpy (_384, _376, _388);\n", - "poisson2d_reference.c:70:31: missed: statement clobbers memory: printf (\"%5d, %0.6f\\n\", iter_134, error_144);\n", - "poisson2d_reference.c:64:9: note: considering unrolling loop 5 at BB 25\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d_reference.c:64:9: optimized: loop unrolled 7 times (header execution count 9800)\n", - "poisson2d_reference.c:59:9: note: considering unrolling loop 4 at BB 23\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d_reference.c:59:9: optimized: loop unrolled 7 times (header execution count 9800)\n", - "poisson2d_reference.c:50:9: note: considering unrolling loop 3 at BB 21\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d_reference.c:50:9: optimized: loop unrolled 7 times (header execution count 9800)\n", - "poisson2d_reference.c:47:25: note: considering unrolling loop 9 at BB 14\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d_reference.c:47:25: optimized: loop unrolled 3 times (header execution count 432180)\n", - "poisson2d_reference.c:47:25: note: considering unrolling loop 7 at BB 11\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d_reference.c:41:9: note: considering unrolling loop 10 at BB 7\n", - "poisson2d_reference.c:33:6: note: considering unrolling loop 2 at BB 6\n", - "poisson2d_reference.c:37:25: note: considering unrolling loop 1 at BB 30\n", - "poisson2d_reference.c:81:9: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:81:9: missed: not vectorized: control flow in loop.\n", - "poisson2d_reference.c:83:35: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:83:35: missed: not vectorized: control flow in loop.\n", - "poisson2d_reference.c:76:5: note: vectorized 0 loops in function.\n", - "poisson2d_reference.c:85:17: missed: statement clobbers memory: fprintf (stderr.0_13, \"ERROR: A[%d][%d] = %f does not match %f (reference)\\n\", iy_62, ix_61, _63, _64);\n", - "poisson2d_reference.c:81:9: note: considering unrolling loop 3 at BB 5\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d_reference.c:81:9: optimized: loop unrolled 3 times (header execution count 9604)\n", - "poisson2d_reference.c:81:9: note: considering unrolling loop 1 at BB 8\n", - "poisson2d.c:57:14: optimized: Inlining atoi/24 into main/32 (always_inline).\n", - "poisson2d.c:56:14: optimized: Inlining atoi/24 into main/32 (always_inline).\n", - "poisson2d.c:51:14: optimized: Inlining atoi/24 into main/32 (always_inline).\n", - "poisson2d.c:47:20: optimized: Inlining atoi/24 into main/32 (always_inline).\n", - "poisson2d.c:157:5: missed: not inlinable: main/32 -> free/39, function body not available\n", - "poisson2d.c:156:5: missed: not inlinable: main/32 -> free/39, function body not available\n", - "poisson2d.c:155:5: missed: not inlinable: main/32 -> free/39, function body not available\n", - "poisson2d.c:154:5: missed: not inlinable: main/32 -> free/39, function body not available\n", - "poisson2d.c:145:9: missed: not inlinable: main/32 -> check_results/38, function body not available\n", - "poisson2d.c:138:31: missed: not inlinable: main/32 -> printf/35, function body not available\n", - "poisson2d.c:98:5: missed: not inlinable: main/32 -> __builtin_puts/36, function body not available\n", - "poisson2d.c:95:5: missed: not inlinable: main/32 -> poisson2d_reference/37, function body not available\n", - "poisson2d.c:93:5: missed: not inlinable: main/32 -> __builtin_puts/36, function body not available\n", - "poisson2d.c:91:5: missed: not inlinable: main/32 -> printf/35, function body not available\n", - "poisson2d.c:73:29: missed: not inlinable: main/32 -> exp/34, function body not available\n", - "poisson2d.c:63:41: missed: not inlinable: main/32 -> malloc/33, function body not available\n", - "poisson2d.c:62:41: missed: not inlinable: main/32 -> malloc/33, function body not available\n", - "poisson2d.c:61:41: missed: not inlinable: main/32 -> malloc/33, function body not available\n", - "poisson2d.c:60:41: missed: not inlinable: main/32 -> malloc/33, function body not available\n", - "/usr/include/stdlib.h:280:16: missed: not inlinable: main/32 -> strtol/40, function body not available\n", - "/usr/include/stdlib.h:280:16: missed: not inlinable: main/32 -> strtol/40, function body not available\n", - "/usr/include/stdlib.h:280:16: missed: not inlinable: main/32 -> strtol/40, function body not available\n", - "/usr/include/stdlib.h:280:16: missed: not inlinable: main/32 -> strtol/40, function body not available\n", - "Unit growth for small function inlining: 234->234 (0%)\n", - "\n", - "Inlined 4 calls, eliminated 0 functions\n", - "\n", - "consider run-time aliasing test between *_84 and *_87\n", - "consider run-time aliasing test between *_92 and *_97\n", - "consider run-time aliasing test between *_104 and *_107\n", - "consider run-time aliasing test between *_111 and *_115\n", - "poisson2d.c:120:13: optimized: Loop 8 distributed: split to 0 loops and 1 library calls.\n", - "poisson2d.c:85:9: optimized: Loop 10 distributed: split to 0 loops and 1 library calls.\n", - "poisson2d.c:103:25: missed: couldn't vectorize loop\n", - "poisson2d.c:103:25: missed: not vectorized: loop nest containing two or more consecutive inner loops cannot be vectorized\n", - "poisson2d.c:132:9: missed: couldn't vectorize loop\n", - "poisson2d.c:132:9: missed: Loop costings may not be worthwhile.\n", - "poisson2d.c:127:9: missed: couldn't vectorize loop\n", - "poisson2d.c:127:9: missed: Loop costings may not be worthwhile.\n", - "poisson2d.c:118:9: missed: couldn't vectorize loop\n", - "poisson2d.c:38:5: missed: statement clobbers memory: __builtin_memcpy (_553, _549, _558);\n", - "poisson2d.c:107:9: missed: couldn't vectorize loop\n", - "poisson2d.c:107:9: missed: not vectorized: control flow in loop.\n", - "poisson2d.c:110:13: optimized: loop vectorized using 16 byte vectors\n", - "poisson2d.c:83:5: missed: couldn't vectorize loop\n", - "poisson2d.c:38:5: missed: statement clobbers memory: __builtin_memset (_538, 0, _541);\n", - "poisson2d.c:67:5: missed: couldn't vectorize loop\n", - "poisson2d.c:73:27: missed: not vectorized: complicated access pattern.\n", - "poisson2d.c:69:9: missed: couldn't vectorize loop\n", - "poisson2d.c:73:29: missed: not vectorized: relevant stmt not supported: _27 = exp (_21);\n", - "poisson2d.c:38:5: note: vectorized 1 loops in function.\n", - "poisson2d.c:38:5: missed: statement clobbers memory: __builtin_memset (_538, 0, _541);\n", - "poisson2d.c:38:5: missed: statement clobbers memory: __builtin_memcpy (_553, _549, _558);\n", - "poisson2d.c:110:13: optimized: loop turned into non-loop; it never loops\n", - "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _195 = strtol (_1, 0B, 10);\n", - "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _197 = strtol (_2, 0B, 10);\n", - "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _201 = strtol (_3, 0B, 10);\n", - "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _199 = strtol (_4, 0B, 10);\n", - "poisson2d.c:60:41: missed: statement clobbers memory: A_155 = malloc (_7);\n", - "poisson2d.c:61:41: missed: statement clobbers memory: Aref_157 = malloc (_7);\n", - "poisson2d.c:62:41: missed: statement clobbers memory: Anew_159 = malloc (_7);\n", - "poisson2d.c:63:41: missed: statement clobbers memory: rhs_161 = malloc (_7);\n", - "poisson2d.c:38:5: missed: statement clobbers memory: __builtin_memset (_538, 0, _541);\n", - "poisson2d.c:91:5: missed: statement clobbers memory: printf (\"Jacobi relaxation calculation: max %d iterations on %d x %d mesh\\n\", iter_max_239, ny_221, nx_124);\n", - "poisson2d.c:93:5: missed: statement clobbers memory: __builtin_puts (&\"Calculate reference solution and time with serial CPU execution.\"[0]);\n", - "poisson2d.c:95:5: missed: statement clobbers memory: poisson2d_reference (iter_max_239, 1.00000000000000008180305391403130954586231382563710212708e-5, Aref_225, Anew_227, nx_124, ny_221, rhs_236);\n", - "poisson2d.c:98:5: missed: statement clobbers memory: __builtin_puts (&\"Calculate current execution.\"[0]);\n", - "poisson2d.c:38:5: missed: statement clobbers memory: __builtin_memcpy (_553, _549, _558);\n", - "poisson2d.c:138:31: missed: statement clobbers memory: printf (\"%5d, %0.6f\\n\", iter_245, error_242);\n", - "poisson2d.c:145:9: missed: statement clobbers memory: _118 = check_results (1, ix_end_162, 1, _131, 1.00000000000000008180305391403130954586231382563710212708e-5, A_125, Aref_225, nx_124);\n", - "poisson2d.c:154:5: missed: statement clobbers memory: free (rhs_236);\n", - "poisson2d.c:155:5: missed: statement clobbers memory: free (Anew_227);\n", - "poisson2d.c:156:5: missed: statement clobbers memory: free (Aref_225);\n", - "poisson2d.c:157:5: missed: statement clobbers memory: free (A_125);\n", - "poisson2d.c:60:41: missed: statement clobbers memory: A_146 = malloc (2000000);\n", - "poisson2d.c:61:41: missed: statement clobbers memory: Aref_145 = malloc (2000000);\n", - "poisson2d.c:62:41: missed: statement clobbers memory: Anew_144 = malloc (2000000);\n", - "poisson2d.c:63:41: missed: statement clobbers memory: rhs_130 = malloc (2000000);\n", - "poisson2d.c:132:9: note: considering unrolling loop 7 at BB 47\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d.c:132:9: optimized: loop unrolled 7 times (header execution count 9800)\n", - "poisson2d.c:127:9: note: considering unrolling loop 6 at BB 44\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d.c:127:9: optimized: loop unrolled 7 times (header execution count 9800)\n", - "poisson2d.c:118:9: note: considering unrolling loop 5 at BB 40\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d.c:118:9: optimized: loop unrolled 7 times (header execution count 9701)\n", - "poisson2d.c:114:25: note: considering unrolling loop 13 at BB 27\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d.c:114:25: optimized: loop unrolled 3 times (header execution count 436550)\n", - "poisson2d.c:114:25: note: considering unrolling loop 9 at BB 24\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d.c:107:9: note: considering unrolling loop 14 at BB 37\n", - "poisson2d.c:38:5: note: considering unrolling loop 4 at BB 35\n", - "poisson2d.c:103:25: note: considering unrolling loop 3 at BB 51\n", - "poisson2d.c:83:5: note: considering unrolling loop 2 at BB 18\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d.c:83:5: optimized: loop unrolled 7 times (header execution count 99)\n", - "poisson2d.c:69:9: note: considering unrolling loop 11 at BB 9\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d.c:69:9: optimized: loop unrolled 3 times (header execution count 9604)\n", - "poisson2d.c:67:5: note: considering unrolling loop 1 at BB 14\n", - "poisson2d_reference.c:85:17: missed: not inlinable: check_results/17 -> fprintf/20, function body not available\n", - "poisson2d_reference.c:70:31: missed: not inlinable: poisson2d_reference/16 -> printf/18, function body not available\n", - "Unit growth for small function inlining: 145->145 (0%)\n", - "\n", - "Inlined 0 calls, eliminated 0 functions\n", - "\n", - "consider run-time aliasing test between *_54 and *_57\n", - "consider run-time aliasing test between *_62 and *_67\n", - "consider run-time aliasing test between *_75 and *_78\n", - "consider run-time aliasing test between *_82 and *_87\n", - "poisson2d_reference.c:52:13: optimized: Loop 6 distributed: split to 0 loops and 1 library calls.\n", - "poisson2d_reference.c:41:9: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:41:9: missed: not vectorized: loop nest containing two or more consecutive inner loops cannot be vectorized\n", - "poisson2d_reference.c:64:9: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:64:9: missed: Loop costings may not be worthwhile.\n", - "poisson2d_reference.c:59:9: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:59:9: missed: Loop costings may not be worthwhile.\n", - "poisson2d_reference.c:50:9: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:33:6: missed: statement clobbers memory: __builtin_memcpy (_384, _376, _388);\n", - "poisson2d_reference.c:41:9: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:45:90: missed: not vectorized: complicated access pattern.\n", - "poisson2d_reference.c:43:13: optimized: loop vectorized using 16 byte vectors\n", - "poisson2d_reference.c:33:6: note: vectorized 1 loops in function.\n", - "poisson2d_reference.c:33:6: missed: statement clobbers memory: __builtin_memcpy (_384, _376, _388);\n", - "poisson2d_reference.c:43:13: optimized: loop turned into non-loop; it never loops\n", - "poisson2d_reference.c:33:6: missed: statement clobbers memory: __builtin_memcpy (_384, _376, _388);\n", - "poisson2d_reference.c:70:31: missed: statement clobbers memory: printf (\"%5d, %0.6f\\n\", iter_134, error_144);\n", - "poisson2d_reference.c:64:9: note: considering unrolling loop 5 at BB 25\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d_reference.c:64:9: optimized: loop unrolled 7 times (header execution count 9800)\n", - "poisson2d_reference.c:59:9: note: considering unrolling loop 4 at BB 23\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d_reference.c:59:9: optimized: loop unrolled 7 times (header execution count 9800)\n", - "poisson2d_reference.c:50:9: note: considering unrolling loop 3 at BB 21\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d_reference.c:50:9: optimized: loop unrolled 7 times (header execution count 9800)\n", - "poisson2d_reference.c:47:25: note: considering unrolling loop 9 at BB 14\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d_reference.c:47:25: optimized: loop unrolled 3 times (header execution count 432180)\n", - "poisson2d_reference.c:47:25: note: considering unrolling loop 7 at BB 11\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d_reference.c:41:9: note: considering unrolling loop 10 at BB 7\n", - "poisson2d_reference.c:33:6: note: considering unrolling loop 2 at BB 6\n", - "poisson2d_reference.c:37:25: note: considering unrolling loop 1 at BB 30\n", - "poisson2d_reference.c:81:9: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:81:9: missed: not vectorized: control flow in loop.\n", - "poisson2d_reference.c:83:35: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:83:35: missed: not vectorized: control flow in loop.\n", - "poisson2d_reference.c:76:5: note: vectorized 0 loops in function.\n", - "poisson2d_reference.c:85:17: missed: statement clobbers memory: fprintf (stderr.0_13, \"ERROR: A[%d][%d] = %f does not match %f (reference)\\n\", iy_62, ix_61, _63, _64);\n", - "poisson2d_reference.c:81:9: note: considering unrolling loop 3 at BB 5\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d_reference.c:81:9: optimized: loop unrolled 3 times (header execution count 9604)\n", - "poisson2d_reference.c:81:9: note: considering unrolling loop 1 at BB 8\n", - "poisson2d.c:57:14: optimized: Inlining atoi/24 into main/32 (always_inline).\n", - "poisson2d.c:56:14: optimized: Inlining atoi/24 into main/32 (always_inline).\n", - "poisson2d.c:51:14: optimized: Inlining atoi/24 into main/32 (always_inline).\n", - "poisson2d.c:47:20: optimized: Inlining atoi/24 into main/32 (always_inline).\n", - "poisson2d.c:157:5: missed: not inlinable: main/32 -> free/39, function body not available\n", - "poisson2d.c:156:5: missed: not inlinable: main/32 -> free/39, function body not available\n", - "poisson2d.c:155:5: missed: not inlinable: main/32 -> free/39, function body not available\n", - "poisson2d.c:154:5: missed: not inlinable: main/32 -> free/39, function body not available\n", - "poisson2d.c:145:9: missed: not inlinable: main/32 -> check_results/38, function body not available\n", - "poisson2d.c:138:31: missed: not inlinable: main/32 -> printf/35, function body not available\n", - "poisson2d.c:98:5: missed: not inlinable: main/32 -> __builtin_puts/36, function body not available\n", - "poisson2d.c:95:5: missed: not inlinable: main/32 -> poisson2d_reference/37, function body not available\n", - "poisson2d.c:93:5: missed: not inlinable: main/32 -> __builtin_puts/36, function body not available\n", - "poisson2d.c:91:5: missed: not inlinable: main/32 -> printf/35, function body not available\n", - "poisson2d.c:73:29: missed: not inlinable: main/32 -> exp/34, function body not available\n", - "poisson2d.c:63:41: missed: not inlinable: main/32 -> malloc/33, function body not available\n", - "poisson2d.c:62:41: missed: not inlinable: main/32 -> malloc/33, function body not available\n", - "poisson2d.c:61:41: missed: not inlinable: main/32 -> malloc/33, function body not available\n", - "poisson2d.c:60:41: missed: not inlinable: main/32 -> malloc/33, function body not available\n", - "/usr/include/stdlib.h:280:16: missed: not inlinable: main/32 -> strtol/40, function body not available\n", - "/usr/include/stdlib.h:280:16: missed: not inlinable: main/32 -> strtol/40, function body not available\n", - "/usr/include/stdlib.h:280:16: missed: not inlinable: main/32 -> strtol/40, function body not available\n", - "/usr/include/stdlib.h:280:16: missed: not inlinable: main/32 -> strtol/40, function body not available\n", - "Unit growth for small function inlining: 234->234 (0%)\n", - "\n", - "Inlined 4 calls, eliminated 0 functions\n", - "\n", - "consider run-time aliasing test between *_84 and *_87\n", - "consider run-time aliasing test between *_92 and *_97\n", - "consider run-time aliasing test between *_104 and *_107\n", - "consider run-time aliasing test between *_111 and *_115\n", - "poisson2d.c:120:13: optimized: Loop 8 distributed: split to 0 loops and 1 library calls.\n", - "poisson2d.c:85:9: optimized: Loop 10 distributed: split to 0 loops and 1 library calls.\n", - "poisson2d.c:103:25: missed: couldn't vectorize loop\n", - "poisson2d.c:103:25: missed: not vectorized: loop nest containing two or more consecutive inner loops cannot be vectorized\n", - "poisson2d.c:132:9: missed: couldn't vectorize loop\n", - "poisson2d.c:132:9: missed: Loop costings may not be worthwhile.\n", - "poisson2d.c:127:9: missed: couldn't vectorize loop\n", - "poisson2d.c:127:9: missed: Loop costings may not be worthwhile.\n", - "poisson2d.c:118:9: missed: couldn't vectorize loop\n", - "poisson2d.c:38:5: missed: statement clobbers memory: __builtin_memcpy (_553, _549, _558);\n", - "poisson2d.c:107:9: missed: couldn't vectorize loop\n", - "poisson2d.c:107:9: missed: not vectorized: control flow in loop.\n", - "poisson2d.c:110:13: optimized: loop vectorized using 16 byte vectors\n", - "poisson2d.c:83:5: missed: couldn't vectorize loop\n", - "poisson2d.c:38:5: missed: statement clobbers memory: __builtin_memset (_538, 0, _541);\n", - "poisson2d.c:67:5: missed: couldn't vectorize loop\n", - "poisson2d.c:73:27: missed: not vectorized: complicated access pattern.\n", - "poisson2d.c:69:9: missed: couldn't vectorize loop\n", - "poisson2d.c:73:29: missed: not vectorized: relevant stmt not supported: _27 = exp (_21);\n", - "poisson2d.c:38:5: note: vectorized 1 loops in function.\n", - "poisson2d.c:38:5: missed: statement clobbers memory: __builtin_memset (_538, 0, _541);\n", - "poisson2d.c:38:5: missed: statement clobbers memory: __builtin_memcpy (_553, _549, _558);\n", - "poisson2d.c:110:13: optimized: loop turned into non-loop; it never loops\n", - "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _195 = strtol (_1, 0B, 10);\n", - "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _197 = strtol (_2, 0B, 10);\n", - "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _201 = strtol (_3, 0B, 10);\n", - "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _199 = strtol (_4, 0B, 10);\n", - "poisson2d.c:60:41: missed: statement clobbers memory: A_155 = malloc (_7);\n", - "poisson2d.c:61:41: missed: statement clobbers memory: Aref_157 = malloc (_7);\n", - "poisson2d.c:62:41: missed: statement clobbers memory: Anew_159 = malloc (_7);\n", - "poisson2d.c:63:41: missed: statement clobbers memory: rhs_161 = malloc (_7);\n", - "poisson2d.c:38:5: missed: statement clobbers memory: __builtin_memset (_538, 0, _541);\n", - "poisson2d.c:91:5: missed: statement clobbers memory: printf (\"Jacobi relaxation calculation: max %d iterations on %d x %d mesh\\n\", iter_max_239, ny_221, nx_124);\n", - "poisson2d.c:93:5: missed: statement clobbers memory: __builtin_puts (&\"Calculate reference solution and time with serial CPU execution.\"[0]);\n", - "poisson2d.c:95:5: missed: statement clobbers memory: poisson2d_reference (iter_max_239, 1.00000000000000008180305391403130954586231382563710212708e-5, Aref_225, Anew_227, nx_124, ny_221, rhs_236);\n", - "poisson2d.c:98:5: missed: statement clobbers memory: __builtin_puts (&\"Calculate current execution.\"[0]);\n", - "poisson2d.c:38:5: missed: statement clobbers memory: __builtin_memcpy (_553, _549, _558);\n", - "poisson2d.c:138:31: missed: statement clobbers memory: printf (\"%5d, %0.6f\\n\", iter_245, error_242);\n", - "poisson2d.c:145:9: missed: statement clobbers memory: _118 = check_results (1, ix_end_162, 1, _131, 1.00000000000000008180305391403130954586231382563710212708e-5, A_125, Aref_225, nx_124);\n", - "poisson2d.c:154:5: missed: statement clobbers memory: free (rhs_236);\n", - "poisson2d.c:155:5: missed: statement clobbers memory: free (Anew_227);\n", - "poisson2d.c:156:5: missed: statement clobbers memory: free (Aref_225);\n", - "poisson2d.c:157:5: missed: statement clobbers memory: free (A_125);\n", - "poisson2d.c:60:41: missed: statement clobbers memory: A_146 = malloc (2000000);\n", - "poisson2d.c:61:41: missed: statement clobbers memory: Aref_145 = malloc (2000000);\n", - "poisson2d.c:62:41: missed: statement clobbers memory: Anew_144 = malloc (2000000);\n", - "poisson2d.c:63:41: missed: statement clobbers memory: rhs_130 = malloc (2000000);\n", - "poisson2d.c:132:9: note: considering unrolling loop 7 at BB 47\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d.c:132:9: optimized: loop unrolled 7 times (header execution count 9800)\n", - "poisson2d.c:127:9: note: considering unrolling loop 6 at BB 44\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d.c:127:9: optimized: loop unrolled 7 times (header execution count 9800)\n", - "poisson2d.c:118:9: note: considering unrolling loop 5 at BB 40\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d.c:118:9: optimized: loop unrolled 7 times (header execution count 9701)\n", - "poisson2d.c:114:25: note: considering unrolling loop 13 at BB 27\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d.c:114:25: optimized: loop unrolled 3 times (header execution count 436550)\n", - "poisson2d.c:114:25: note: considering unrolling loop 9 at BB 24\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d.c:107:9: note: considering unrolling loop 14 at BB 37\n", - "poisson2d.c:38:5: note: considering unrolling loop 4 at BB 35\n", - "poisson2d.c:103:25: note: considering unrolling loop 3 at BB 51\n", - "poisson2d.c:83:5: note: considering unrolling loop 2 at BB 18\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d.c:83:5: optimized: loop unrolled 7 times (header execution count 99)\n", - "poisson2d.c:69:9: note: considering unrolling loop 11 at BB 9\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d.c:69:9: optimized: loop unrolled 3 times (header execution count 9604)\n", - "poisson2d.c:67:5: note: considering unrolling loop 1 at BB 14\n", - "poisson2d_reference.c:85:17: missed: not inlinable: check_results/17 -> fprintf/20, function body not available\n", - "poisson2d_reference.c:70:31: missed: not inlinable: poisson2d_reference/16 -> printf/18, function body not available\n", - "Unit growth for small function inlining: 145->145 (0%)\n", - "\n", - "Inlined 0 calls, eliminated 0 functions\n", - "\n", - "consider run-time aliasing test between *_54 and *_57\n", - "consider run-time aliasing test between *_62 and *_67\n", - "consider run-time aliasing test between *_75 and *_78\n", - "consider run-time aliasing test between *_82 and *_87\n", - "poisson2d_reference.c:52:13: optimized: Loop 6 distributed: split to 0 loops and 1 library calls.\n", - "poisson2d_reference.c:41:9: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:41:9: missed: not vectorized: loop nest containing two or more consecutive inner loops cannot be vectorized\n", - "poisson2d_reference.c:64:9: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:64:9: missed: Loop costings may not be worthwhile.\n", - "poisson2d_reference.c:59:9: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:59:9: missed: Loop costings may not be worthwhile.\n", - "poisson2d_reference.c:50:9: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:33:6: missed: statement clobbers memory: __builtin_memcpy (_384, _376, _388);\n", - "poisson2d_reference.c:41:9: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:45:90: missed: not vectorized: complicated access pattern.\n", - "poisson2d_reference.c:43:13: optimized: loop vectorized using 16 byte vectors\n", - "poisson2d_reference.c:33:6: note: vectorized 1 loops in function.\n", - "poisson2d_reference.c:33:6: missed: statement clobbers memory: __builtin_memcpy (_384, _376, _388);\n", - "poisson2d_reference.c:43:13: optimized: loop turned into non-loop; it never loops\n", - "poisson2d_reference.c:33:6: missed: statement clobbers memory: __builtin_memcpy (_384, _376, _388);\n", - "poisson2d_reference.c:70:31: missed: statement clobbers memory: printf (\"%5d, %0.6f\\n\", iter_134, error_144);\n", - "poisson2d_reference.c:64:9: note: considering unrolling loop 5 at BB 25\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d_reference.c:64:9: optimized: loop unrolled 7 times (header execution count 9800)\n", - "poisson2d_reference.c:59:9: note: considering unrolling loop 4 at BB 23\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d_reference.c:59:9: optimized: loop unrolled 7 times (header execution count 9800)\n", - "poisson2d_reference.c:50:9: note: considering unrolling loop 3 at BB 21\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d_reference.c:50:9: optimized: loop unrolled 7 times (header execution count 9800)\n", - "poisson2d_reference.c:47:25: note: considering unrolling loop 9 at BB 14\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d_reference.c:47:25: optimized: loop unrolled 3 times (header execution count 432180)\n", - "poisson2d_reference.c:47:25: note: considering unrolling loop 7 at BB 11\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d_reference.c:41:9: note: considering unrolling loop 10 at BB 7\n", - "poisson2d_reference.c:33:6: note: considering unrolling loop 2 at BB 6\n", - "poisson2d_reference.c:37:25: note: considering unrolling loop 1 at BB 30\n", - "poisson2d_reference.c:81:9: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:81:9: missed: not vectorized: control flow in loop.\n", - "poisson2d_reference.c:83:35: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:83:35: missed: not vectorized: control flow in loop.\n", - "poisson2d_reference.c:76:5: note: vectorized 0 loops in function.\n", - "poisson2d_reference.c:85:17: missed: statement clobbers memory: fprintf (stderr.0_13, \"ERROR: A[%d][%d] = %f does not match %f (reference)\\n\", iy_62, ix_61, _63, _64);\n", - "poisson2d_reference.c:81:9: note: considering unrolling loop 3 at BB 5\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d_reference.c:81:9: optimized: loop unrolled 3 times (header execution count 9604)\n", - "poisson2d_reference.c:81:9: note: considering unrolling loop 1 at BB 8\n", - "poisson2d.c:57:14: optimized: Inlining atoi/24 into main/32 (always_inline).\n", - "poisson2d.c:56:14: optimized: Inlining atoi/24 into main/32 (always_inline).\n", - "poisson2d.c:51:14: optimized: Inlining atoi/24 into main/32 (always_inline).\n", - "poisson2d.c:47:20: optimized: Inlining atoi/24 into main/32 (always_inline).\n", - "poisson2d.c:157:5: missed: not inlinable: main/32 -> free/39, function body not available\n", - "poisson2d.c:156:5: missed: not inlinable: main/32 -> free/39, function body not available\n", - "poisson2d.c:155:5: missed: not inlinable: main/32 -> free/39, function body not available\n", - "poisson2d.c:154:5: missed: not inlinable: main/32 -> free/39, function body not available\n", - "poisson2d.c:145:9: missed: not inlinable: main/32 -> check_results/38, function body not available\n", - "poisson2d.c:138:31: missed: not inlinable: main/32 -> printf/35, function body not available\n", - "poisson2d.c:98:5: missed: not inlinable: main/32 -> __builtin_puts/36, function body not available\n", - "poisson2d.c:95:5: missed: not inlinable: main/32 -> poisson2d_reference/37, function body not available\n", - "poisson2d.c:93:5: missed: not inlinable: main/32 -> __builtin_puts/36, function body not available\n", - "poisson2d.c:91:5: missed: not inlinable: main/32 -> printf/35, function body not available\n", - "poisson2d.c:73:29: missed: not inlinable: main/32 -> exp/34, function body not available\n", - "poisson2d.c:63:41: missed: not inlinable: main/32 -> malloc/33, function body not available\n", - "poisson2d.c:62:41: missed: not inlinable: main/32 -> malloc/33, function body not available\n", - "poisson2d.c:61:41: missed: not inlinable: main/32 -> malloc/33, function body not available\n", - "poisson2d.c:60:41: missed: not inlinable: main/32 -> malloc/33, function body not available\n", - "/usr/include/stdlib.h:280:16: missed: not inlinable: main/32 -> strtol/40, function body not available\n", - "/usr/include/stdlib.h:280:16: missed: not inlinable: main/32 -> strtol/40, function body not available\n", - "/usr/include/stdlib.h:280:16: missed: not inlinable: main/32 -> strtol/40, function body not available\n", - "/usr/include/stdlib.h:280:16: missed: not inlinable: main/32 -> strtol/40, function body not available\n", - "Unit growth for small function inlining: 234->234 (0%)\n", - "\n", - "Inlined 4 calls, eliminated 0 functions\n", - "\n", - "consider run-time aliasing test between *_84 and *_87\n", - "consider run-time aliasing test between *_92 and *_97\n", - "consider run-time aliasing test between *_104 and *_107\n", - "consider run-time aliasing test between *_111 and *_115\n", - "poisson2d.c:120:13: optimized: Loop 8 distributed: split to 0 loops and 1 library calls.\n", - "poisson2d.c:85:9: optimized: Loop 10 distributed: split to 0 loops and 1 library calls.\n", - "poisson2d.c:103:25: missed: couldn't vectorize loop\n", - "poisson2d.c:103:25: missed: not vectorized: loop nest containing two or more consecutive inner loops cannot be vectorized\n", - "poisson2d.c:132:9: missed: couldn't vectorize loop\n", - "poisson2d.c:132:9: missed: Loop costings may not be worthwhile.\n", - "poisson2d.c:127:9: missed: couldn't vectorize loop\n", - "poisson2d.c:127:9: missed: Loop costings may not be worthwhile.\n", - "poisson2d.c:118:9: missed: couldn't vectorize loop\n", - "poisson2d.c:38:5: missed: statement clobbers memory: __builtin_memcpy (_553, _549, _558);\n", - "poisson2d.c:107:9: missed: couldn't vectorize loop\n", - "poisson2d.c:107:9: missed: not vectorized: control flow in loop.\n", - "poisson2d.c:110:13: optimized: loop vectorized using 16 byte vectors\n", - "poisson2d.c:83:5: missed: couldn't vectorize loop\n", - "poisson2d.c:38:5: missed: statement clobbers memory: __builtin_memset (_538, 0, _541);\n", - "poisson2d.c:67:5: missed: couldn't vectorize loop\n", - "poisson2d.c:73:27: missed: not vectorized: complicated access pattern.\n", - "poisson2d.c:69:9: missed: couldn't vectorize loop\n", - "poisson2d.c:73:29: missed: not vectorized: relevant stmt not supported: _27 = exp (_21);\n", - "poisson2d.c:38:5: note: vectorized 1 loops in function.\n", - "poisson2d.c:38:5: missed: statement clobbers memory: __builtin_memset (_538, 0, _541);\n", - "poisson2d.c:38:5: missed: statement clobbers memory: __builtin_memcpy (_553, _549, _558);\n", - "poisson2d.c:110:13: optimized: loop turned into non-loop; it never loops\n", - "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _195 = strtol (_1, 0B, 10);\n", - "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _197 = strtol (_2, 0B, 10);\n", - "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _201 = strtol (_3, 0B, 10);\n", - "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _199 = strtol (_4, 0B, 10);\n", - "poisson2d.c:60:41: missed: statement clobbers memory: A_155 = malloc (_7);\n", - "poisson2d.c:61:41: missed: statement clobbers memory: Aref_157 = malloc (_7);\n", - "poisson2d.c:62:41: missed: statement clobbers memory: Anew_159 = malloc (_7);\n", - "poisson2d.c:63:41: missed: statement clobbers memory: rhs_161 = malloc (_7);\n", - "poisson2d.c:38:5: missed: statement clobbers memory: __builtin_memset (_538, 0, _541);\n", - "poisson2d.c:91:5: missed: statement clobbers memory: printf (\"Jacobi relaxation calculation: max %d iterations on %d x %d mesh\\n\", iter_max_239, ny_221, nx_124);\n", - "poisson2d.c:93:5: missed: statement clobbers memory: __builtin_puts (&\"Calculate reference solution and time with serial CPU execution.\"[0]);\n", - "poisson2d.c:95:5: missed: statement clobbers memory: poisson2d_reference (iter_max_239, 1.00000000000000008180305391403130954586231382563710212708e-5, Aref_225, Anew_227, nx_124, ny_221, rhs_236);\n", - "poisson2d.c:98:5: missed: statement clobbers memory: __builtin_puts (&\"Calculate current execution.\"[0]);\n", - "poisson2d.c:38:5: missed: statement clobbers memory: __builtin_memcpy (_553, _549, _558);\n", - "poisson2d.c:138:31: missed: statement clobbers memory: printf (\"%5d, %0.6f\\n\", iter_245, error_242);\n", - "poisson2d.c:145:9: missed: statement clobbers memory: _118 = check_results (1, ix_end_162, 1, _131, 1.00000000000000008180305391403130954586231382563710212708e-5, A_125, Aref_225, nx_124);\n", - "poisson2d.c:154:5: missed: statement clobbers memory: free (rhs_236);\n", - "poisson2d.c:155:5: missed: statement clobbers memory: free (Anew_227);\n", - "poisson2d.c:156:5: missed: statement clobbers memory: free (Aref_225);\n", - "poisson2d.c:157:5: missed: statement clobbers memory: free (A_125);\n", - "poisson2d.c:60:41: missed: statement clobbers memory: A_146 = malloc (2000000);\n", - "poisson2d.c:61:41: missed: statement clobbers memory: Aref_145 = malloc (2000000);\n", - "poisson2d.c:62:41: missed: statement clobbers memory: Anew_144 = malloc (2000000);\n", - "poisson2d.c:63:41: missed: statement clobbers memory: rhs_130 = malloc (2000000);\n", - "poisson2d.c:132:9: note: considering unrolling loop 7 at BB 47\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d.c:132:9: optimized: loop unrolled 7 times (header execution count 9800)\n", - "poisson2d.c:127:9: note: considering unrolling loop 6 at BB 44\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d.c:127:9: optimized: loop unrolled 7 times (header execution count 9800)\n", - "poisson2d.c:118:9: note: considering unrolling loop 5 at BB 40\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d.c:118:9: optimized: loop unrolled 7 times (header execution count 9701)\n", - "poisson2d.c:114:25: note: considering unrolling loop 13 at BB 27\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d.c:114:25: optimized: loop unrolled 3 times (header execution count 436550)\n", - "poisson2d.c:114:25: note: considering unrolling loop 9 at BB 24\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d.c:107:9: note: considering unrolling loop 14 at BB 37\n", - "poisson2d.c:38:5: note: considering unrolling loop 4 at BB 35\n", - "poisson2d.c:103:25: note: considering unrolling loop 3 at BB 51\n", - "poisson2d.c:83:5: note: considering unrolling loop 2 at BB 18\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d.c:83:5: optimized: loop unrolled 7 times (header execution count 99)\n", - "poisson2d.c:69:9: note: considering unrolling loop 11 at BB 9\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d.c:69:9: optimized: loop unrolled 3 times (header execution count 9604)\n", - "poisson2d.c:67:5: note: considering unrolling loop 1 at BB 14\n", - "poisson2d_reference.c:85:17: missed: not inlinable: check_results/17 -> fprintf/20, function body not available\n", - "poisson2d_reference.c:70:31: missed: not inlinable: poisson2d_reference/16 -> printf/18, function body not available\n", - "Unit growth for small function inlining: 145->145 (0%)\n", - "\n", - "Inlined 0 calls, eliminated 0 functions\n", - "\n", - "consider run-time aliasing test between *_54 and *_57\n", - "consider run-time aliasing test between *_62 and *_67\n", - "consider run-time aliasing test between *_75 and *_78\n", - "consider run-time aliasing test between *_82 and *_87\n", - "poisson2d_reference.c:52:13: optimized: Loop 6 distributed: split to 0 loops and 1 library calls.\n", - "poisson2d_reference.c:41:9: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:41:9: missed: not vectorized: loop nest containing two or more consecutive inner loops cannot be vectorized\n", - "poisson2d_reference.c:64:9: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:64:9: missed: Loop costings may not be worthwhile.\n", - "poisson2d_reference.c:59:9: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:59:9: missed: Loop costings may not be worthwhile.\n", - "poisson2d_reference.c:50:9: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:33:6: missed: statement clobbers memory: __builtin_memcpy (_384, _376, _388);\n", - "poisson2d_reference.c:41:9: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:45:90: missed: not vectorized: complicated access pattern.\n", - "poisson2d_reference.c:43:13: optimized: loop vectorized using 16 byte vectors\n", - "poisson2d_reference.c:33:6: note: vectorized 1 loops in function.\n", - "poisson2d_reference.c:33:6: missed: statement clobbers memory: __builtin_memcpy (_384, _376, _388);\n", - "poisson2d_reference.c:43:13: optimized: loop turned into non-loop; it never loops\n", - "poisson2d_reference.c:33:6: missed: statement clobbers memory: __builtin_memcpy (_384, _376, _388);\n", - "poisson2d_reference.c:70:31: missed: statement clobbers memory: printf (\"%5d, %0.6f\\n\", iter_134, error_144);\n", - "poisson2d_reference.c:64:9: note: considering unrolling loop 5 at BB 25\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d_reference.c:64:9: optimized: loop unrolled 7 times (header execution count 9800)\n", - "poisson2d_reference.c:59:9: note: considering unrolling loop 4 at BB 23\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d_reference.c:59:9: optimized: loop unrolled 7 times (header execution count 9800)\n", - "poisson2d_reference.c:50:9: note: considering unrolling loop 3 at BB 21\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d_reference.c:50:9: optimized: loop unrolled 7 times (header execution count 9800)\n", - "poisson2d_reference.c:47:25: note: considering unrolling loop 9 at BB 14\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d_reference.c:47:25: optimized: loop unrolled 3 times (header execution count 432180)\n", - "poisson2d_reference.c:47:25: note: considering unrolling loop 7 at BB 11\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d_reference.c:41:9: note: considering unrolling loop 10 at BB 7\n", - "poisson2d_reference.c:33:6: note: considering unrolling loop 2 at BB 6\n", - "poisson2d_reference.c:37:25: note: considering unrolling loop 1 at BB 30\n", - "poisson2d_reference.c:81:9: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:81:9: missed: not vectorized: control flow in loop.\n", - "poisson2d_reference.c:83:35: missed: couldn't vectorize loop\n", - "poisson2d_reference.c:83:35: missed: not vectorized: control flow in loop.\n", - "poisson2d_reference.c:76:5: note: vectorized 0 loops in function.\n", - "poisson2d_reference.c:85:17: missed: statement clobbers memory: fprintf (stderr.0_13, \"ERROR: A[%d][%d] = %f does not match %f (reference)\\n\", iy_62, ix_61, _63, _64);\n", - "poisson2d_reference.c:81:9: note: considering unrolling loop 3 at BB 5\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d_reference.c:81:9: optimized: loop unrolled 3 times (header execution count 9604)\n", - "poisson2d_reference.c:81:9: note: considering unrolling loop 1 at BB 8\n", - "poisson2d.c:57:14: optimized: Inlining atoi/24 into main/32 (always_inline).\n", - "poisson2d.c:56:14: optimized: Inlining atoi/24 into main/32 (always_inline).\n", - "poisson2d.c:51:14: optimized: Inlining atoi/24 into main/32 (always_inline).\n", - "poisson2d.c:47:20: optimized: Inlining atoi/24 into main/32 (always_inline).\n", - "poisson2d.c:157:5: missed: not inlinable: main/32 -> free/39, function body not available\n", - "poisson2d.c:156:5: missed: not inlinable: main/32 -> free/39, function body not available\n", - "poisson2d.c:155:5: missed: not inlinable: main/32 -> free/39, function body not available\n", - "poisson2d.c:154:5: missed: not inlinable: main/32 -> free/39, function body not available\n", - "poisson2d.c:145:9: missed: not inlinable: main/32 -> check_results/38, function body not available\n", - "poisson2d.c:138:31: missed: not inlinable: main/32 -> printf/35, function body not available\n", - "poisson2d.c:98:5: missed: not inlinable: main/32 -> __builtin_puts/36, function body not available\n", - "poisson2d.c:95:5: missed: not inlinable: main/32 -> poisson2d_reference/37, function body not available\n", - "poisson2d.c:93:5: missed: not inlinable: main/32 -> __builtin_puts/36, function body not available\n", - "poisson2d.c:91:5: missed: not inlinable: main/32 -> printf/35, function body not available\n", - "poisson2d.c:73:29: missed: not inlinable: main/32 -> exp/34, function body not available\n", - "poisson2d.c:63:41: missed: not inlinable: main/32 -> malloc/33, function body not available\n", - "poisson2d.c:62:41: missed: not inlinable: main/32 -> malloc/33, function body not available\n", - "poisson2d.c:61:41: missed: not inlinable: main/32 -> malloc/33, function body not available\n", - "poisson2d.c:60:41: missed: not inlinable: main/32 -> malloc/33, function body not available\n", - "/usr/include/stdlib.h:280:16: missed: not inlinable: main/32 -> strtol/40, function body not available\n", - "/usr/include/stdlib.h:280:16: missed: not inlinable: main/32 -> strtol/40, function body not available\n", - "/usr/include/stdlib.h:280:16: missed: not inlinable: main/32 -> strtol/40, function body not available\n", - "/usr/include/stdlib.h:280:16: missed: not inlinable: main/32 -> strtol/40, function body not available\n", - "Unit growth for small function inlining: 234->234 (0%)\n", - "\n", - "Inlined 4 calls, eliminated 0 functions\n", - "\n", - "consider run-time aliasing test between *_84 and *_87\n", - "consider run-time aliasing test between *_92 and *_97\n", - "consider run-time aliasing test between *_104 and *_107\n", - "consider run-time aliasing test between *_111 and *_115\n", - "poisson2d.c:120:13: optimized: Loop 8 distributed: split to 0 loops and 1 library calls.\n", - "poisson2d.c:85:9: optimized: Loop 10 distributed: split to 0 loops and 1 library calls.\n", - "poisson2d.c:103:25: missed: couldn't vectorize loop\n", - "poisson2d.c:103:25: missed: not vectorized: loop nest containing two or more consecutive inner loops cannot be vectorized\n", - "poisson2d.c:132:9: missed: couldn't vectorize loop\n", - "poisson2d.c:132:9: missed: Loop costings may not be worthwhile.\n", - "poisson2d.c:127:9: missed: couldn't vectorize loop\n", - "poisson2d.c:127:9: missed: Loop costings may not be worthwhile.\n", - "poisson2d.c:118:9: missed: couldn't vectorize loop\n", - "poisson2d.c:38:5: missed: statement clobbers memory: __builtin_memcpy (_553, _549, _558);\n", - "poisson2d.c:107:9: missed: couldn't vectorize loop\n", - "poisson2d.c:107:9: missed: not vectorized: control flow in loop.\n", - "poisson2d.c:110:13: optimized: loop vectorized using 16 byte vectors\n", - "poisson2d.c:83:5: missed: couldn't vectorize loop\n", - "poisson2d.c:38:5: missed: statement clobbers memory: __builtin_memset (_538, 0, _541);\n", - "poisson2d.c:67:5: missed: couldn't vectorize loop\n", - "poisson2d.c:73:27: missed: not vectorized: complicated access pattern.\n", - "poisson2d.c:69:9: missed: couldn't vectorize loop\n", - "poisson2d.c:73:29: missed: not vectorized: relevant stmt not supported: _27 = exp (_21);\n", - "poisson2d.c:38:5: note: vectorized 1 loops in function.\n", - "poisson2d.c:38:5: missed: statement clobbers memory: __builtin_memset (_538, 0, _541);\n", - "poisson2d.c:38:5: missed: statement clobbers memory: __builtin_memcpy (_553, _549, _558);\n", - "poisson2d.c:110:13: optimized: loop turned into non-loop; it never loops\n", - "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _195 = strtol (_1, 0B, 10);\n", - "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _197 = strtol (_2, 0B, 10);\n", - "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _201 = strtol (_3, 0B, 10);\n", - "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _199 = strtol (_4, 0B, 10);\n", - "poisson2d.c:60:41: missed: statement clobbers memory: A_155 = malloc (_7);\n", - "poisson2d.c:61:41: missed: statement clobbers memory: Aref_157 = malloc (_7);\n", - "poisson2d.c:62:41: missed: statement clobbers memory: Anew_159 = malloc (_7);\n", - "poisson2d.c:63:41: missed: statement clobbers memory: rhs_161 = malloc (_7);\n", - "poisson2d.c:38:5: missed: statement clobbers memory: __builtin_memset (_538, 0, _541);\n", - "poisson2d.c:91:5: missed: statement clobbers memory: printf (\"Jacobi relaxation calculation: max %d iterations on %d x %d mesh\\n\", iter_max_239, ny_221, nx_124);\n", - "poisson2d.c:93:5: missed: statement clobbers memory: __builtin_puts (&\"Calculate reference solution and time with serial CPU execution.\"[0]);\n", - "poisson2d.c:95:5: missed: statement clobbers memory: poisson2d_reference (iter_max_239, 1.00000000000000008180305391403130954586231382563710212708e-5, Aref_225, Anew_227, nx_124, ny_221, rhs_236);\n", - "poisson2d.c:98:5: missed: statement clobbers memory: __builtin_puts (&\"Calculate current execution.\"[0]);\n", - "poisson2d.c:38:5: missed: statement clobbers memory: __builtin_memcpy (_553, _549, _558);\n", - "poisson2d.c:138:31: missed: statement clobbers memory: printf (\"%5d, %0.6f\\n\", iter_245, error_242);\n", - "poisson2d.c:145:9: missed: statement clobbers memory: _118 = check_results (1, ix_end_162, 1, _131, 1.00000000000000008180305391403130954586231382563710212708e-5, A_125, Aref_225, nx_124);\n", - "poisson2d.c:154:5: missed: statement clobbers memory: free (rhs_236);\n", - "poisson2d.c:155:5: missed: statement clobbers memory: free (Anew_227);\n", - "poisson2d.c:156:5: missed: statement clobbers memory: free (Aref_225);\n", - "poisson2d.c:157:5: missed: statement clobbers memory: free (A_125);\n", - "poisson2d.c:60:41: missed: statement clobbers memory: A_146 = malloc (2000000);\n", - "poisson2d.c:61:41: missed: statement clobbers memory: Aref_145 = malloc (2000000);\n", - "poisson2d.c:62:41: missed: statement clobbers memory: Anew_144 = malloc (2000000);\n", - "poisson2d.c:63:41: missed: statement clobbers memory: rhs_130 = malloc (2000000);\n", - "poisson2d.c:132:9: note: considering unrolling loop 7 at BB 47\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d.c:132:9: optimized: loop unrolled 7 times (header execution count 9800)\n", - "poisson2d.c:127:9: note: considering unrolling loop 6 at BB 44\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d.c:127:9: optimized: loop unrolled 7 times (header execution count 9800)\n", - "poisson2d.c:118:9: note: considering unrolling loop 5 at BB 40\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d.c:118:9: optimized: loop unrolled 7 times (header execution count 9701)\n", - "poisson2d.c:114:25: note: considering unrolling loop 13 at BB 27\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d.c:114:25: optimized: loop unrolled 3 times (header execution count 436550)\n", - "poisson2d.c:114:25: note: considering unrolling loop 9 at BB 24\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d.c:107:9: note: considering unrolling loop 14 at BB 37\n", - "poisson2d.c:38:5: note: considering unrolling loop 4 at BB 35\n", - "poisson2d.c:103:25: note: considering unrolling loop 3 at BB 51\n", - "poisson2d.c:83:5: note: considering unrolling loop 2 at BB 18\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d.c:83:5: optimized: loop unrolled 7 times (header execution count 99)\n", - "poisson2d.c:69:9: note: considering unrolling loop 11 at BB 9\n", - "considering unrolling loop with constant number of iterations\n", - "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d.c:69:9: optimized: loop unrolled 3 times (header execution count 9604)\n", - "poisson2d.c:67:5: note: considering unrolling loop 1 at BB 14\n", + "poisson2d.c:108:25: missed: not vectorized: loop nest containing two or more consecutive inner loops cannot be vectorized\n", + "poisson2d.c:136:9: missed: couldn't vectorize loop\n", + "poisson2d.c:136:9: missed: Loop costings may not be worthwhile.\n", + "poisson2d.c:131:9: missed: couldn't vectorize loop\n", + "poisson2d.c:131:9: missed: Loop costings may not be worthwhile.\n", + "poisson2d.c:122:9: missed: couldn't vectorize loop\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_543, _539, _549);\n", + "poisson2d.c:112:9: missed: couldn't vectorize loop\n", + "poisson2d.c:112:9: missed: not vectorized: control flow in loop.\n", + "poisson2d.c:114:13: optimized: loop vectorized using 16 byte vectors\n", + "poisson2d.c:88:5: missed: couldn't vectorize loop\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_528, 0, _531);\n", + "poisson2d.c:72:5: missed: couldn't vectorize loop\n", + "poisson2d.c:78:27: missed: not vectorized: complicated access pattern.\n", + "poisson2d.c:74:9: missed: couldn't vectorize loop\n", + "poisson2d.c:78:29: missed: not vectorized: relevant stmt not supported: _27 = exp (_21);\n", + "poisson2d.c:43:5: note: vectorized 1 loops in function.\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_528, 0, _531);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_543, _539, _549);\n", + "poisson2d.c:114:13: optimized: loop turned into non-loop; it never loops\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _187 = strtol (_1, 0B, 10);\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _189 = strtol (_2, 0B, 10);\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _193 = strtol (_3, 0B, 10);\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _191 = strtol (_4, 0B, 10);\n", + "poisson2d.c:65:41: missed: statement clobbers memory: A_153 = malloc (_7);\n", + "poisson2d.c:67:41: missed: statement clobbers memory: Anew_155 = malloc (_7);\n", + "poisson2d.c:68:41: missed: statement clobbers memory: rhs_157 = malloc (_7);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_528, 0, _531);\n", + "poisson2d.c:96:5: missed: statement clobbers memory: printf (\"Jacobi relaxation calculation: max %d iterations on %d x %d mesh\\n\", iter_max_130, ny_139, nx_195);\n", + "poisson2d.c:103:5: missed: statement clobbers memory: __builtin_puts (&\"Calculate current execution.\"[0]);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_543, _539, _549);\n", + "poisson2d.c:142:31: missed: statement clobbers memory: printf (\"%5d, %0.6f\\n\", iter_237, error_219);\n", + "poisson2d.c:158:5: missed: statement clobbers memory: free (rhs_202);\n", + "poisson2d.c:159:5: missed: statement clobbers memory: free (Anew_124);\n", + "poisson2d.c:161:5: missed: statement clobbers memory: free (A_123);\n", + "poisson2d.c:65:41: missed: statement clobbers memory: A_144 = malloc (8000000);\n", + "poisson2d.c:67:41: missed: statement clobbers memory: Anew_143 = malloc (8000000);\n", + "poisson2d.c:68:41: missed: statement clobbers memory: rhs_142 = malloc (8000000);\n" + ] + } + ], + "source": [ + "!make poisson2d_Ofast_info" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's compare this with the output during compilation when using profile-directed feedback from Task 1 B.\n", + "\n", + "**TASK**: \n", + "Adapt the `CFLAGS` of `poisson2d_ref_info` to include `-fopt-info-all` **and** the profile input of `-fprofile-use=…` here. *(Be advised: Long output!)*" + ] + }, + { + "cell_type": "code", + "execution_count": 83, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast -fopt-info-all -fprofile-use=/gpfs/wolf/trn003/scratch/aherten/ -Ofast -fprofile-generate=/gpfs/wolf/trn003/scratch/aherten/ poisson2d.c -o poisson2d_train -lm \n", "poisson2d.c:62:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", "poisson2d.c:61:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", "poisson2d.c:56:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", "poisson2d.c:52:20: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "Increasing alignment of decl: __gcov0.main\n", + "poisson2d.c:164:1: missed: not inlinable: _GLOBAL__sub_D_00100_1_main/48 -> __gcov_exit/55, function body not available\n", + "poisson2d.c:164:1: missed: not inlinable: _GLOBAL__sub_I_00100_0_main/47 -> __gcov_init/54, function body not available\n", "poisson2d.c:161:5: missed: not inlinable: main/33 -> free/38, function body not available\n", "poisson2d.c:159:5: missed: not inlinable: main/33 -> free/38, function body not available\n", "poisson2d.c:158:5: missed: not inlinable: main/33 -> free/38, function body not available\n", @@ -1700,7 +821,7 @@ "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", - "Unit growth for small function inlining: 207->207 (0%)\n", + "Unit growth for small function inlining: 295->295 (0%)\n", "\n", "Inlined 4 calls, eliminated 0 functions\n", "\n", @@ -1710,6 +831,8 @@ "consider run-time aliasing test between *_111 and *_115\n", "poisson2d.c:124:13: optimized: Loop 8 distributed: split to 0 loops and 1 library calls.\n", "poisson2d.c:90:9: optimized: Loop 10 distributed: split to 0 loops and 1 library calls.\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_64, _135, _313);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_632, 0, _239);\n", "poisson2d.c:108:25: missed: couldn't vectorize loop\n", "poisson2d.c:108:25: missed: not vectorized: loop nest containing two or more consecutive inner loops cannot be vectorized\n", "poisson2d.c:136:9: missed: couldn't vectorize loop\n", @@ -1717,19 +840,19 @@ "poisson2d.c:131:9: missed: couldn't vectorize loop\n", "poisson2d.c:131:9: missed: Loop costings may not be worthwhile.\n", "poisson2d.c:122:9: missed: couldn't vectorize loop\n", - "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_539, _535, _544);\n", + "poisson2d.c:122:9: missed: not vectorized: control flow in loop.\n", "poisson2d.c:112:9: missed: couldn't vectorize loop\n", "poisson2d.c:112:9: missed: not vectorized: control flow in loop.\n", "poisson2d.c:114:13: optimized: loop vectorized using 16 byte vectors\n", "poisson2d.c:88:5: missed: couldn't vectorize loop\n", - "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_524, 0, _527);\n", + "poisson2d.c:88:5: missed: not vectorized: control flow in loop.\n", "poisson2d.c:72:5: missed: couldn't vectorize loop\n", - "poisson2d.c:78:27: missed: not vectorized: complicated access pattern.\n", + "poisson2d.c:72:5: missed: not vectorized: control flow in loop.\n", "poisson2d.c:74:9: missed: couldn't vectorize loop\n", "poisson2d.c:78:29: missed: not vectorized: relevant stmt not supported: _27 = exp (_21);\n", "poisson2d.c:43:5: note: vectorized 1 loops in function.\n", - "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_524, 0, _527);\n", - "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_539, _535, _544);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_632, 0, _239);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_64, _135, _313);\n", "poisson2d.c:114:13: optimized: loop turned into non-loop; it never loops\n", "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _187 = strtol (_1, 0B, 10);\n", "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _189 = strtol (_2, 0B, 10);\n", @@ -1738,48 +861,60 @@ "poisson2d.c:65:41: missed: statement clobbers memory: A_153 = malloc (_7);\n", "poisson2d.c:67:41: missed: statement clobbers memory: Anew_155 = malloc (_7);\n", "poisson2d.c:68:41: missed: statement clobbers memory: rhs_157 = malloc (_7);\n", - "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_524, 0, _527);\n", - "poisson2d.c:96:5: missed: statement clobbers memory: printf (\"Jacobi relaxation calculation: max %d iterations on %d x %d mesh\\n\", iter_max_130, ny_139, nx_195);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_632, 0, _239);\n", + "poisson2d.c:96:5: missed: statement clobbers memory: printf (\"Jacobi relaxation calculation: max %d iterations on %d x %d mesh\\n\", iter_max_337, ny_124, nx_286);\n", "poisson2d.c:103:5: missed: statement clobbers memory: __builtin_puts (&\"Calculate current execution.\"[0]);\n", - "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_539, _535, _544);\n", - "poisson2d.c:142:31: missed: statement clobbers memory: printf (\"%5d, %0.6f\\n\", iter_237, error_219);\n", - "poisson2d.c:158:5: missed: statement clobbers memory: free (rhs_202);\n", - "poisson2d.c:159:5: missed: statement clobbers memory: free (Anew_124);\n", - "poisson2d.c:161:5: missed: statement clobbers memory: free (A_123);\n", - "poisson2d.c:65:41: missed: statement clobbers memory: A_144 = malloc (8000000);\n", - "poisson2d.c:67:41: missed: statement clobbers memory: Anew_143 = malloc (8000000);\n", - "poisson2d.c:68:41: missed: statement clobbers memory: rhs_142 = malloc (8000000);\n", - "poisson2d.c:136:9: note: considering unrolling loop 7 at BB 47\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_64, _135, _313);\n", + "poisson2d.c:142:31: missed: statement clobbers memory: printf (\"%5d, %0.6f\\n\", iter_316, error_118);\n", + "poisson2d.c:158:5: missed: statement clobbers memory: free (rhs_127);\n", + "poisson2d.c:159:5: missed: statement clobbers memory: free (Anew_311);\n", + "poisson2d.c:161:5: missed: statement clobbers memory: free (A_122);\n", + "poisson2d.c:65:41: missed: statement clobbers memory: A_129 = malloc (8000000);\n", + "poisson2d.c:67:41: missed: statement clobbers memory: Anew_132 = malloc (8000000);\n", + "poisson2d.c:68:41: missed: statement clobbers memory: rhs_140 = malloc (8000000);\n", + "poisson2d.c:136:9: note: considering unrolling loop 7 at BB 53\n", "considering unrolling loop with constant number of iterations\n", "considering unrolling loop with runtime-computable number of iterations\n", "poisson2d.c:136:9: optimized: loop unrolled 7 times (header execution count 9800)\n", - "poisson2d.c:131:9: note: considering unrolling loop 6 at BB 44\n", + "poisson2d.c:131:9: note: considering unrolling loop 6 at BB 50\n", "considering unrolling loop with constant number of iterations\n", "considering unrolling loop with runtime-computable number of iterations\n", "poisson2d.c:131:9: optimized: loop unrolled 7 times (header execution count 9800)\n", - "poisson2d.c:122:9: note: considering unrolling loop 5 at BB 40\n", + "poisson2d.c:122:9: note: considering unrolling loop 5 at BB 47\n", "considering unrolling loop with constant number of iterations\n", "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d.c:122:9: optimized: loop unrolled 7 times (header execution count 9701)\n", - "poisson2d.c:118:25: note: considering unrolling loop 13 at BB 27\n", + "poisson2d.c:122:9: optimized: loop unrolled 3 times (header execution count 9800)\n", + "poisson2d.c:118:25: note: considering unrolling loop 13 at BB 33\n", "considering unrolling loop with constant number of iterations\n", "considering unrolling loop with runtime-computable number of iterations\n", "poisson2d.c:118:25: optimized: loop unrolled 3 times (header execution count 436550)\n", - "poisson2d.c:118:25: note: considering unrolling loop 9 at BB 24\n", + "poisson2d.c:118:25: note: considering unrolling loop 9 at BB 30\n", "considering unrolling loop with constant number of iterations\n", "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d.c:112:9: note: considering unrolling loop 14 at BB 37\n", - "poisson2d.c:43:5: note: considering unrolling loop 4 at BB 35\n", - "poisson2d.c:108:25: note: considering unrolling loop 3 at BB 51\n", - "poisson2d.c:88:5: note: considering unrolling loop 2 at BB 18\n", + "poisson2d.c:112:9: note: considering unrolling loop 14 at BB 42\n", + "poisson2d.c:43:5: note: considering unrolling loop 4 at BB 40\n", + "poisson2d.c:108:25: note: considering unrolling loop 3 at BB 60\n", + "poisson2d.c:88:5: note: considering unrolling loop 2 at BB 23\n", "considering unrolling loop with constant number of iterations\n", "considering unrolling loop with runtime-computable number of iterations\n", - "poisson2d.c:88:5: optimized: loop unrolled 7 times (header execution count 99)\n", - "poisson2d.c:74:9: note: considering unrolling loop 11 at BB 9\n", + "poisson2d.c:88:5: optimized: loop unrolled 3 times (header execution count 100)\n", + "poisson2d.c:74:9: note: considering unrolling loop 11 at BB 12\n", "considering unrolling loop with constant number of iterations\n", "considering unrolling loop with runtime-computable number of iterations\n", "poisson2d.c:74:9: optimized: loop unrolled 3 times (header execution count 9604)\n", - "poisson2d.c:72:5: note: considering unrolling loop 1 at BB 14\n", + "poisson2d.c:72:5: note: considering unrolling loop 1 at BB 16\n", + "poisson2d.c:164:1: missed: statement clobbers memory: __gcov_init (&*.LPBX0);\n", + "poisson2d.c:164:1: missed: statement clobbers memory: __gcov_exit ();\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS ./poisson2d_train 100 100 100\n", + "Job <24908> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "libgcov profiling error:/gpfs/wolf/trn003/scratch/aherten//#autofs#nccsopen-svm1_home#aherten#SC19-Tutorial#3-Optimizing_POWER#Handson#Task1#poisson2d.gcda:overwriting an existing profile data with a different timestamp\n", + "Jacobi relaxation calculation: max 100 iterations on 100 x 100 mesh\n", + "Calculate current execution.\n", + " 0, 0.249490\n", + "echo `date` > /gpfs/wolf/trn003/scratch/aherten//.profile_generated\n", + "gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast -fopt-info-all -fprofile-use=/gpfs/wolf/trn003/scratch/aherten/ poisson2d.c poisson2d_reference.o -o poisson2d_ref_info -lm\n", "poisson2d.c:62:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", "poisson2d.c:61:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", "poisson2d.c:56:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", @@ -1882,20 +1017,21 @@ } ], "source": [ - "!cat \"$SC19_DIR_SCRATCH\"/pgo-opt-record" + "!make poisson2d_ref_info" ] }, { "cell_type": "markdown", - "metadata": { - "exercise": "solution" - }, + "metadata": {}, "source": [ - "Comparing the annotations generated at plain Ofast optimization level and the one generated at Ofast + profile directed feedback, we observe that many more optimizations are possible due to profile information. For instance you will see annotations such as \n", + "Comparing the annotations generated of a plain `-Ofast` optimization level and the one generated at `-Ofast` and profile directed feedback, we observe that many more optimizations are possible due to profile information.\n", "\n", - "`poisson2d.c:114:25: optimized: loop unrolled 3 times (header execution count 436550)`\n", + "For instance you will see annotations such as\n", + "```\n", + "poisson2d.c:114:25: optimized: loop unrolled 3 times (header execution count 436550)\n", + "```\n", "\n", - "The execution count indicates the dynamic execution count of the node at runtime. This information determines which paths are hotter and subsequently facilitate additional optimizations. \n" + "The execution count indicates the dynamic execution count of the node at runtime. This information determines which paths are hotter and subsequently facilitate additional optimizations." ] }, { @@ -1926,25 +1062,23 @@ "\n", "### Overview\n", "\n", - "Study the difference of program execution time of different optimization levels with and without software prefetching.\n", - "\n", - "Verify the impact by measuring cache counters with and without prefetching.\n", - "\n", - "Learn how to modify contents of DSCR (data stream control register) using IBM XL compiler and study the impact with different values to DSCR. \n", + "* Study the difference of program execution time of different optimization levels with and without software prefetching.\n", + "* Verify the impact by measuring cache counters with and without prefetching.\n", + "* Learn how to modify contents of DSCR (*Data Stream Control Register*) using IBM XL compiler and study the impact with different values to DSCR. \n", "\n", - "First, change directory to that of Task 2" + "But first, lets change directory to that of Task 2" ] }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 85, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "/autofs/nccsopen-svm1_home/archanaravindar/SC19-Tutorial/Task2\n" + "/autofs/nccsopen-svm1_home/aherten/SC19-Tutorial/3-Optimizing_POWER/Handson/Task2\n" ] } ], @@ -1956,7 +1090,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Part A: Building with SW Prefetch flag and Running Application." + "### Part A: Software Prefetching" ] }, { @@ -1965,22 +1099,39 @@ "source": [ "**TASK**: Look at the Makefile and work on the TODOs. \n", "\n", - "- First generate Ofast binary and note down the performance in terms of cycles, seconds and L3 Misses. \n", - "- Modify Makefile to add software prefetching option (`-fprefetch-loop-arrays`). Compare performance of Ofast binary with and without SW prefetching and note down the performance." + "- First generate a `-Ofast`-optimised binary and note down the performance in terms of cycles, seconds, and L3 misses. This is our baseline!\n", + "- Modify the `Makefile` to add the option for software prefetching (`-fprefetch-loop-arrays`). Compare performance of `-Ofast` with and without software prefetching" ] }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 97, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "rm -f poisson2d poisson2d*.o\n" + ] + } + ], + "source": [ + "!make clean" + ] + }, + { + "cell_type": "code", + "execution_count": 88, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "gcc -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec poisson2d.c -o poisson2d -lm\n", + "make: `poisson2d' is up to date.\n", "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", - "Job <24714> is submitted to default queue <batch>.\n", + "Job <24911> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", @@ -1995,10 +1146,10 @@ " 700, 0.243173\n", " 800, 0.242228\n", " 900, 0.241291\n", - "2.40user 0.00system 0:02.40elapsed 99%CPU (0avgtext+0avgdata 24256maxresident)k\n", - "256inputs+0outputs (0major+479minor)pagefaults 0swaps\n", + "2.39user 0.01system 0:02.40elapsed 100%CPU (0avgtext+0avgdata 24256maxresident)k\n", + "0inputs+0outputs (0major+480minor)pagefaults 0swaps\n", "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,r168a4 ./poisson2d\n", - "Job <24715> is submitted to default queue <batch>.\n", + "Job <24912> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", @@ -2016,15 +1167,33 @@ "\n", " Performance counter stats for './poisson2d':\n", "\n", - " 8264018720 cycles:u \n", - " 483312952 r168a4:u \n", + " 8271503902 cycles:u \n", + " 481152478 r168a4:u \n", "\n", - " 2.412880428 seconds time elapsed\n", - "\n", - "gcc -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec -fprefetch-loop-arrays poisson2d.c -o poisson2d_pref -lm\n", + " 2.412224884 seconds time elapsed\n", + "\n" + ] + } + ], + "source": [ + "!make poisson2d CC=gcc\n", + "!make run\n", + "!make l3missstats" + ] + }, + { + "cell_type": "code", + "execution_count": 98, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc -std=c99 -DUSE_DOUBLE -Ofast -mcpu=power9 -mvsx -maltivec -fprefetch-loop-arrays -fprefetch-loop-arrays poisson2d.c -o poisson2d_pref -lm\n", "cp poisson2d_pref poisson2d\n", "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", - "Job <24716> is submitted to default queue <batch>.\n", + "Job <24919> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", @@ -2039,10 +1208,10 @@ " 700, 0.243173\n", " 800, 0.242228\n", " 900, 0.241291\n", - "1.93user 0.00system 0:01.93elapsed 99%CPU (0avgtext+0avgdata 24256maxresident)k\n", - "256inputs+0outputs (0major+481minor)pagefaults 0swaps\n", + "1.92user 0.00system 0:01.93elapsed 99%CPU (0avgtext+0avgdata 24256maxresident)k\n", + "256inputs+0outputs (0major+480minor)pagefaults 0swaps\n", "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,r168a4 ./poisson2d\n", - "Job <24717> is submitted to default queue <batch>.\n", + "Job <24920> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", @@ -2060,45 +1229,39 @@ "\n", " Performance counter stats for './poisson2d':\n", "\n", - " 6573061567 cycles:u \n", - " 459043186 r168a4:u \n", + " 6586609284 cycles:u \n", + " 459879452 r168a4:u \n", "\n", - " 1.920153470 seconds time elapsed\n", + " 1.925399505 seconds time elapsed\n", "\n" ] } ], "source": [ - "!make CC=gcc poisson2d\n", + "!make poisson2d_pref CC=gcc\n", "!make run\n", - "!make l3missstats\n", - "!make CC=gcc poisson2d_pref\n", - "!make run\n", - "!make l3missstats\n" + "!make l3missstats" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "**TASK**: Repeat the experiment with O3 flag. \n", - "\n", - "- First generate O3 binary and note down the performance in terms of cycles, seconds and L3 Misses. \n", - "- Modify Makefile to add software prefetching option (`-fprefetch-loop-arrays`). Compare performance of Ofast binary with and without SW prefetching and note down the performance." + "**TASK**: Repeat the experiment with the `-O3` flag. Have a look at the `Makefile` and the outlined TODO. There's a position to easily adapt `-Ofast`→`-O3`!" ] }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 100, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "gcc -std=c99 -mcpu=power9 -O3 -DUSE_DOUBLE -mvsx -maltivec poisson2d.c -o poisson2d -lm\n", + "gcc -std=c99 -DUSE_DOUBLE -O3 -mcpu=power9 -mvsx -maltivec poisson2d.c -o poisson2d -lm\n", "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", - "Job <24718> is submitted to default queue <batch>.\n", + "Job <24923> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", @@ -2116,7 +1279,7 @@ "4.73user 0.00system 0:04.73elapsed 99%CPU (0avgtext+0avgdata 24256maxresident)k\n", "256inputs+0outputs (0major+479minor)pagefaults 0swaps\n", "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,r168a4 ./poisson2d\n", - "Job <24719> is submitted to default queue <batch>.\n", + "Job <24924> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", @@ -2134,15 +1297,32 @@ "\n", " Performance counter stats for './poisson2d':\n", "\n", - " 16233775058 cycles:u \n", - " 632495173 r168a4:u \n", + " 16445764669 cycles:u \n", + " 645094089 r168a4:u \n", "\n", - " 4.729401619 seconds time elapsed\n", - "\n", - "gcc -std=c99 -mcpu=power9 -O3 -DUSE_DOUBLE -mvsx -maltivec -fprefetch-loop-arrays poisson2d.c -o poisson2d_pref -lm\n", - "cp poisson2d_pref poisson2d\n", + " 4.792567763 seconds time elapsed\n", + "\n" + ] + } + ], + "source": [ + "!make poisson2d CC=gcc -B\n", + "!make run\n", + "!make l3missstats" + ] + }, + { + "cell_type": "code", + "execution_count": 101, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc -std=c99 -DUSE_DOUBLE -O3 -mcpu=power9 -mvsx -maltivec -fprefetch-loop-arrays poisson2d.c -o poisson2d_pref -lm\n", "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", - "Job <24720> is submitted to default queue <batch>.\n", + "Job <24925> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", @@ -2157,10 +1337,10 @@ " 700, 0.243173\n", " 800, 0.242228\n", " 900, 0.241291\n", - "4.72user 0.00system 0:04.73elapsed 99%CPU (0avgtext+0avgdata 24192maxresident)k\n", - "256inputs+0outputs (0major+479minor)pagefaults 0swaps\n", + "4.74user 0.00system 0:04.74elapsed 99%CPU (0avgtext+0avgdata 24256maxresident)k\n", + "0inputs+0outputs (0major+480minor)pagefaults 0swaps\n", "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,r168a4 ./poisson2d\n", - "Job <24721> is submitted to default queue <batch>.\n", + "Job <24926> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", @@ -2178,19 +1358,16 @@ "\n", " Performance counter stats for './poisson2d':\n", "\n", - " 16244358719 cycles:u \n", - " 619811301 r168a4:u \n", + " 16239159454 cycles:u \n", + " 631061431 r168a4:u \n", "\n", - " 4.732843397 seconds time elapsed\n", + " 4.730144897 seconds time elapsed\n", "\n" ] } ], "source": [ - "!make CC=gcc -B poisson2d\n", - "!make run\n", - "!make l3missstats\n", - "!make CC=gcc -B poisson2d_pref\n", + "!make poisson2d_pref CC=gcc -B\n", "!make run\n", "!make l3missstats" ] @@ -2199,7 +1376,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Do you notice the impact difference with optimization levels? At what optimization level does software prefetching help the most" + "Do you notice the impact difference with optimization levels? At what optimization level does software prefetching help the most?" ] }, { @@ -2208,7 +1385,7 @@ "exercise": "solution" }, "source": [ - "Observing the results, we see that SW Prefetching seems to help at Ofast but not at O3. We can use the steps described in the the next section to verify that the compiler has not inserted any SW prefetch operations at O3 at all. That is because in the O3 binary the time is dominated by `__fmax` call which causes the compiler to come to the conclusion that whatever benefit we obtain by adding SW prefetch will be overshadowed by the penalty of fmax\n", + "Observing the results, we see that SW Prefetching seems to help at `-Ofast` but not at `-O3`. We can use the steps described in the the next section to verify that the compiler has not inserted any SW prefetch operations at`-O3` at all. That is because in the `-O3` binary the time is dominated by `__fmax` call which causes the compiler to come to the conclusion that whatever benefit we obtain by adding SW prefetch will be overshadowed by the penalty of `fmax()`\n", "GCC may add further loop optimizations such as unrolling upon invocation of `–fprefetch-loop-arrays`.\n" ] }, @@ -2218,7 +1395,7 @@ "source": [ "### Part B: Analysis of Instructions\n", "\n", - "Compilation of the Ofast binary with the software prefetching flag causes the compiler to generate the `dcb*' instructions that prefetch memory values to L3." + "Compilation of the `-Ofast` binary with the software prefetching flag causes the compiler to generate the `dcb*` instructions that prefetch memory values to L3." ] }, { @@ -2226,33 +1403,32 @@ "metadata": {}, "source": [ "**TASK**: \n", - "Run ` $(SC19_SUBMIT_CMD) objdump -lSd` on each binary file (O3, Ofast with prefetch/no prefetch)\n", + "Run `$(SC19_SUBMIT_CMD) objdump -lSd` on each binary file (`-O3`, `-Ofast` with prefetch/no prefetch).\n", "Look for instructions beginning with `dcb`\n", "At what optimization levels does the compiler generate software prefetching instructions?" ] }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 114, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "gcc -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec -fprefetch-loop-arrays poisson2d.c -o poisson2d_pref -lm\n", - "cp poisson2d_pref poisson2d\n" + "gcc -std=c99 -DUSE_DOUBLE -Ofast -mcpu=power9 -mvsx -maltivec -fprefetch-loop-arrays poisson2d.c -o poisson2d_pref -lm\n" ] } ], "source": [ "!make CC=gcc -B poisson2d_pref\n", - "!objdump -lSd ./poisson2d > poisson2d.dis" + "!objdump -lSd ./poisson2d_pref > poisson2d.dis" ] }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 116, "metadata": {}, "outputs": [ { @@ -2285,22 +1461,24 @@ "source": [ "### Part C: Changing Values of DSCR via compiler flags\n", "\n", - "This task requires loading IBM XL compiler from the environment. \n", + "This task requires using the IBM XL compiler. It should be already in your environment.\n", + "\n", + "\n", "We saw the impact of software prefetching in the previous subsection. \n", "In certain cases, tuning the hardware prefetcher through compiler options can also help improve performance. \n", "In this exercise we shall see some compiler options that can be used to modify the DSCR value which controls aggressiveness of prefetching. It can be also used to turn off hardware prefetching. \n", "\n", - "IBM XL compiler has an option -qprefetch=dscr=`<val>` that can be used for this purpose.\n", - "Compiling with -qprefetch=dscr=1 turns off the prefetcher.\n", - "One can give various values such as -qprefetch=dscr=4, -qprefetch=dscr=7 etc to control aggressiveness of prefetching.\n", - "For this exercise we use make CC=xlc_r to illustrate the performance impact.\n", + "IBM XL compiler has an option `-qprefetch=dscr=<val>` that can be used for this purpose.\n", + "Compiling with `-qprefetch=dscr=1` turns off the prefetcher. One can give various values such as `-qprefetch=dscr=4`, `-qprefetch=dscr=7` etc. to control aggressiveness of prefetching.\n", + "\n", + "For this exercise we use `make CC=xlc_r` to illustrate the performance impact.\n", " \n", "\n", - "**Task** Generate XL binary by compiling using the following commands. Add -qprefetch=dscr=1 and rebuild the application and note the performance; Compare the performance of the default binary with the binary compiled with -qprefetch=dscr=1. Which one is faster? \n", + "**Task** Generate a XL-compiled binary by compiling using the following cells. After you've generated a baseline, start editing the `Makefile`: Add `qprefetch=dscr=1` to the `CFLAGS` and rebuild the application and note the performance. Which one is faster? \n", "\n", - "In general, applications benefit with the default settings of hardware DSCR register. (-qprefetch=dscr=0). However, certain applications also benefit with prefetching turned off. \n", + "In general, applications benefit with the default settings of hardware DSCR register (`-qprefetch=dscr=0`). However, certain applications also benefit with prefetching turned off. \n", "\n", - "It is to be noted that DSCR values are highly sensitive to the application. One value that works well for Application A may not help Application B. \n" + "It is to be noted that DSCR values are highly sensitive to the application. One value that works well for Application A may not help Application B. " ] }, { @@ -2312,17 +1490,17 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 117, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "xlc_r -std=c99 -qarch=pwr9 -qtune=pwr9 -Ofast -DUSE_DOUBLE -DINLINE_LIBS poisson2d.c -o poisson2d -lm\n", + "xlc_r -std=c99 -DUSE_DOUBLE -Ofast -qarch=pwr9 -qtune=pwr9 -DINLINE_LIBS poisson2d.c -o poisson2d -lm\n", " 1500-036: (I) The NOSTRICT option (default at OPT(3)) has the potential to alter the semantics of a program. Please refer to documentation on the STRICT/NOSTRICT option for more information.\n", "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", - "Job <24725> is submitted to default queue <batch>.\n", + "Job <24927> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", @@ -2337,7 +1515,7 @@ " 700, 345.423208\n", " 800, 393.963155\n", " 900, 442.314962\n", - "2.27user 0.00system 0:02.27elapsed 99%CPU (0avgtext+0avgdata 24256maxresident)k\n", + "2.26user 0.00system 0:02.27elapsed 99%CPU (0avgtext+0avgdata 24256maxresident)k\n", "256inputs+0outputs (0major+477minor)pagefaults 0swaps\n" ] } @@ -2356,39 +1534,38 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "xlc_r -std=c99 -qarch=pwr9 -qtune=pwr9 -Ofast -DUSE_DOUBLE -DINLINE_LIBS -qprefetch=dscr=1 poisson2d.c -o poisson2d_dscr -lm\n", + "xlc_r -std=c99 -DUSE_DOUBLE -Ofast -qarch=pwr9 -qtune=pwr9 -DINLINE_LIBS -qprefetch=dscr=1 poisson2d.c -o poisson2d_dscr -lm\n", " 1500-036: (I) The NOSTRICT option (default at OPT(3)) has the potential to alter the semantics of a program. Please refer to documentation on the STRICT/NOSTRICT option for more information.\n", - "cp poisson2d_dscr poisson2d\n", "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", - "Job <24726> is submitted to default queue <batch>.\n", + "Job <24929> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", "Calculate current execution.\n", " 0, 0.249995\n", - " 100, 50.149062\n", - " 200, 99.849327\n", - " 300, 149.352369\n", - " 400, 198.659746\n", - " 500, 247.773000\n", - " 600, 296.693652\n", - " 700, 345.423208\n", - " 800, 393.963155\n", - " 900, 442.314962\n", - "4.63user 0.01system 0:04.64elapsed 99%CPU (0avgtext+0avgdata 24192maxresident)k\n", - "256inputs+0outputs (0major+476minor)pagefaults 0swaps\n" + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "4.58user 0.00system 0:04.59elapsed 99%CPU (0avgtext+0avgdata 24192maxresident)k\n", + "0inputs+0outputs (0major+476minor)pagefaults 0swaps\n" ] } ], "source": [ - "!make CC=xlc_r -B poisson2d_dscr\n", + "!make poisson2d_dscr CC=xlc_r -B\n", "!make run" ] }, @@ -2444,14 +1621,14 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "/autofs/nccsopen-svm1_home/archanaravindar/SC19-Tutorial/Task3/Solutions\n" + "/autofs/nccsopen-svm1_home/aherten/SC19-Tutorial/3-Optimizing_POWER/Handson/Task3\n" ] } ], @@ -2465,80 +1642,49 @@ "source": [ "### Part A: Implement OpenMP Pragmas; Compilation\n", "\n", - "**Task**: Please add the correct OpenMP pragmas to poisson2d.c and compilations flags in the Makefile to enable OpenMP with GCC and XL compilers.\n", + "**Task**: Please add the correct OpenMP directives to poisson2d.c and compilations flags in the Makefile to enable OpenMP with GCC and XL compilers.\n", "\n", - "* **pragmas**: Look at the TODOs in [`poisson2d.c`](/edit/Task3/poisson2d.c) to add OpenMP parallelism. The pragmas in question are `#pragma omp parallel for`\n", - "* **Compilation**: Please add compilation flags enabling OpenMP in GCC and XL to Makefile. For GCC, we need to add `-fopenmp` and the application needs to be linked with -lgomp. \n", - "For XL, we need to add -qsmp=omp to the list of compilation flags. \n", + "* **Directives**: Look at the TODOs in [`poisson2d.c`](poisson2d.c) to add OpenMP parallelism. The pragmas in question are `#pragma omp parallel for` (and once it's `#pragma omp parallel for reduction(max:error)` – can you guess where?)\n", + "* **Compilation**: Please add compilation flags enabling OpenMP in GCC and XL to the `Makefile`. For GCC, we need to add `-fopenmp` and the application needs to be linked with `-lgomp`. For XL, we need to add `-qsmp=omp` to the list of compilation flags. \n", "\n", "Afterwards, compile and run the application with the following commands." ] }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 39, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "gcc -c -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec -fopenmp poisson2d_reference.c -o poisson2d_reference.o -lm -lgomp \n", - "gcc -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d -lm -lgomp\n", - "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", - "Job <24666> is submitted to default queue <batch>.\n", - "<<Waiting for dispatch ...>>\n", - "<<Starting on login1>>\n", - "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", - "Calculate reference solution and time with serial CPU execution.\n", - " 0, 0.249995\n", - " 100, 0.248997\n", - " 200, 0.248007\n", - " 300, 0.247025\n", - " 400, 0.246050\n", - " 500, 0.245084\n", - " 600, 0.244124\n", - " 700, 0.243173\n", - " 800, 0.242228\n", - " 900, 0.241291\n", - "Calculate current execution.\n", - " 0, 0.249995\n", - " 100, 0.248997\n", - " 200, 0.248007\n", - " 300, 0.020992\n", - " 400, 0.021114\n", - " 500, 0.245084\n", - " 600, 0.244124\n", - " 700, 0.021475\n", - " 800, 0.242228\n", - " 900, 0.021712\n", - "1000x1000: Ref: 2.3959 s, This: 2.1408 s, speedup: 1.12\n", - "11.08user 0.00system 0:04.57elapsed 242%CPU (0avgtext+0avgdata 24896maxresident)k\n", - "256inputs+0outputs (0major+739minor)pagefaults 0swaps\n" + "gcc -c -std=c99 -DUSE_DOUBLE -O3 -mcpu=power9 -mvsx -maltivec -fopenmp -lgomp poisson2d_reference.c -o poisson2d_reference.o -lm\n", + "gcc -std=c99 -DUSE_DOUBLE -O3 -mcpu=power9 -mvsx -maltivec -fopenmp -lgomp poisson2d.c poisson2d_reference.o -o poisson2d -lm \n" ] } ], "source": [ - "!make CC=gcc -B run " + "!make poisson2d CC=gcc" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The command to submit a job to the batch system is prepared in an environment variable $SC19_SUBMIT_CMD; use it together with eval. In the following cell, it is shown how to invoke the application using the batch system. " + "The command to submit a job to the batch system is prepared in an environment variable `$SC19_SUBMIT_CMD`; use it together with `eval`. In the following cell, it is shown how to invoke the application using the batch system. " ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 40, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Job <24651> is submitted to default queue <batch>.\n", + "Job <24951> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", @@ -2554,17 +1700,17 @@ " 800, 0.242228\n", " 900, 0.241291\n", "Calculate current execution.\n", - " 0, 0.249975\n", + " 0, 0.249995\n", " 100, 0.248997\n", - " 200, 0.020870\n", - " 300, 0.020992\n", + " 200, 0.248007\n", + " 300, 0.247025\n", " 400, 0.246050\n", " 500, 0.245084\n", " 600, 0.244124\n", " 700, 0.243173\n", " 800, 0.242228\n", " 900, 0.241291\n", - "1000x1000: Ref: 2.3963 s, This: 2.1526 s, speedup: 1.11\n" + "1000x1000: Ref: 4.7430 s, This: 3.9363 s, speedup: 1.20\n" ] } ], @@ -2583,7 +1729,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 32, "metadata": { "exercise": "task" }, @@ -2592,21 +1738,23 @@ "name": "stdout", "output_type": "stream", "text": [ - "Job <24653> is submitted to default queue <batch>.\n", + "Job <24945> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", + "\n", + "libgomp: Invalid value for environment variable OMP_NUM_THREADS\n", "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", "Calculate reference solution and time with serial CPU execution.\n", " 0, 0.249995\n", - " 100, 0.248997\n", - " 200, 0.248007\n", - " 300, 0.247025\n", - " 400, 0.246050\n", - " 500, 0.245084\n", - " 600, 0.244124\n", - " 700, 0.243173\n", - " 800, 0.242228\n", - " 900, 0.241291\n", + " 100, 50.149062\n", + " 200, 99.849327\n", + " 300, 149.352369\n", + " 400, 198.659746\n", + " 500, 247.773000\n", + " 600, 296.693652\n", + " 700, 345.423208\n", + " 800, 393.963155\n", + " 900, 442.314962\n", "Calculate current execution.\n", " 0, 0.249995\n", " 100, 0.248997\n", @@ -2618,7 +1766,7 @@ " 700, 0.243173\n", " 800, 0.242228\n", " 900, 0.241291\n", - "1000x1000: Ref: 2.3823 s, This: 2.7327 s, speedup: 0.87\n" + "1000x1000: Ref: 2.1046 s, This: 2.4171 s, speedup: 0.87\n" ] } ], @@ -2628,7 +1776,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 41, "metadata": { "exercise": "solution" }, @@ -2639,7 +1787,7 @@ "text": [ "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", - "1000x1000: Ref: 2.3780 s, This: 2.7215 s, speedup: 0.87\n" + "1000x1000: Ref: 4.7288 s, This: 4.9791 s, speedup: 0.95\n" ] } ], @@ -2649,7 +1797,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 42, "metadata": { "exercise": "solution" }, @@ -2660,7 +1808,7 @@ "text": [ "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", - "1000x1000: Ref: 2.3788 s, This: 1.3734 s, speedup: 1.73\n" + "1000x1000: Ref: 4.7125 s, This: 2.4914 s, speedup: 1.89\n" ] } ], @@ -2670,7 +1818,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 35, "metadata": { "exercise": "solution" }, @@ -2681,7 +1829,7 @@ "text": [ "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", - "1000x1000: Ref: 2.3779 s, This: 0.8743 s, speedup: 2.72\n" + "1000x1000: Ref: 2.1065 s, This: 1.3836 s, speedup: 1.52\n" ] } ], @@ -2842,18 +1990,18 @@ }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 36, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Job <24463> is submitted to default queue <batch>.\n", + "Job <24949> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "0-3\n", - "Job <24464> is submitted to default queue <batch>.\n", + "Job <24950> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "4-7\n" @@ -2869,7 +2017,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "There are various environment variables available within OpenMP (some specific to GCC) that hold across compilers to specify binding of threads to cores. See, for instance, the [OMP_PLACES environment Variable](https://www.openmp.org/spec-html/5.0/openmpse53.html). Examples are `OMP_PLACES` which is an OpenMP variable. We also have a GNU specific variable which can also be used to control affinity- `GOMP_CPU_AFFINITY`. Setting GOMP_CPU_AFFINITY is specific to GCC binaries but it internally serves the same function as setting OMP_PLACES. \n", + "There are various environment variables available within OpenMP (some specific to GCC) that hold across compilers to specify binding of threads to cores. See, for instance, the [OMP_PLACES environment Variable](https://www.openmp.org/spec-html/5.0/openmpse53.html). We also have a GNU specific variable which can also be used to control affinity - `GOMP_CPU_AFFINITY`. Setting `GOMP_CPU_AFFINITY` is specific to GCC binaries but it internally serves the same function as setting `OMP_PLACES`. \n", "\n", "**Task**: Run the application enabled with OpenMP from Part A with different binding configurations. Make sure to at least run a) binding all threads to a single core and b) binding threads to different cores.\n", "\n", @@ -2880,7 +2028,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 38, "metadata": { "exercise": "task" }, @@ -2889,13 +2037,12 @@ "name": "stdout", "output_type": "stream", "text": [ - "<<Waiting for dispatch ...>>\n", - "<<Starting on login1>>\n" + "/usr/bin/sh: OMP_PLACES}={X},{Y},{Z},{A}: command not found\n" ] } ], "source": [ - "!eval OMP_DISPLAY_ENV=true OMP_PLACES}=\"{X},{Y},{Z},{A}\" OMP_NUM_THREADS=4 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 100 | grep \"OMP_PLACES\\|speedup\"" + "!eval OMP_DISPLAY_ENV=true OMP_PLACES=\"{X},{Y},{Z},{A}\" OMP_NUM_THREADS=4 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 100 | grep \"OMP_PLACES\\|speedup\"" ] }, { @@ -2924,7 +2071,7 @@ "exercise": "solution" }, "source": [ - "Running with two different configurations 1) Binding all threads to the same core 2) Binding all threads to different cores, we see a higher speedup in case of binding all threads to different cores. The binding is set using `OMP_PLACES` in this case." + "Running with two different configurations 1) Binding all threads to the same core 2) Binding all threads to different cores, we see a higher speedup in case of binding all threads to different cores." ] }, { @@ -2933,12 +2080,12 @@ "exercise": "solution" }, "source": [ - "Using `OMP_PLACES` for binding" + "Using `OMP_PLACES` for binding, and using some magical Python-Bash interplay:" ] }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 43, "metadata": { "exercise": "solution" }, @@ -2951,12 +2098,12 @@ "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", " OMP_PLACES = '{0},{1},{2},{3}'\n", - "1000x1000: Ref: 2.3966 s, This: 2.1410 s, speedup: 1.12\n", + "1000x1000: Ref: 4.7315 s, This: 3.9090 s, speedup: 1.21\n", "Affinity: {0},{5},{9},{13}\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", " OMP_PLACES = '{0},{5},{9},{13}'\n", - "1000x1000: Ref: 2.3922 s, This: 0.7029 s, speedup: 3.40\n" + "1000x1000: Ref: 4.6485 s, This: 1.2829 s, speedup: 3.62\n" ] } ], @@ -3009,27 +2156,29 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The same experiments can be repeated with the IBM XL compiler. \n", + "Great!\n", + "\n", + "If you still have time: The same experiments can be repeated with the IBM XL compiler. \n", "The corresponding compiler flag to enable OpenMP parallelism that needs to be used for XL is `-qsmp=omp`\n", "\n", - "**Task**: In the Makefile add the openMP flag and generate XL binaries with the openmp option and run the application with various number of threads and note the performance speedup." + "**Task**: In the Makefile add the OpenMP flag and generate XL binaries with OpenMP and run the application with various number of threads and note the performance speedup." ] }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 44, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "xlc_r -c -std=c99 -qarch=pwr9 -qtune=pwr9 -O3 -qhot -DUSE_DOUBLE -DINLINE_LIBS -qsmp=omp -qsmp=omp poisson2d_reference.c -o poisson2d_reference.o -lm \n", + "xlc_r -c -std=c99 -DUSE_DOUBLE -O3 -qhot -qtune=pwr9 -DINLINE_LIBS -qsmp=omp poisson2d_reference.c -o poisson2d_reference.o -lm \n", " 1500-036: (I) The NOSTRICT option (default at OPT(3)) has the potential to alter the semantics of a program. Please refer to documentation on the STRICT/NOSTRICT option for more information.\n", - "xlc_r -std=c99 -qarch=pwr9 -qtune=pwr9 -O3 -qhot -DUSE_DOUBLE -DINLINE_LIBS -qsmp=omp -qsmp=omp poisson2d.c poisson2d_reference.o -o poisson2d -lm\n", + "xlc_r -std=c99 -DUSE_DOUBLE -O3 -qhot -qtune=pwr9 -DINLINE_LIBS -qsmp=omp poisson2d.c poisson2d_reference.o -o poisson2d -lm\n", " 1500-036: (I) The NOSTRICT option (default at OPT(3)) has the potential to alter the semantics of a program. Please refer to documentation on the STRICT/NOSTRICT option for more information.\n", "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", - "Job <24677> is submitted to default queue <batch>.\n", + "Job <24956> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", @@ -3045,19 +2194,19 @@ " 800, 393.963155\n", " 900, 442.314962\n", "Calculate current execution.\n", - " 0, 0.249975\n", + " 0, 0.249995\n", " 100, 50.149062\n", " 200, 99.849327\n", " 300, 149.352369\n", " 400, 198.659746\n", " 500, 247.773000\n", " 600, 296.693652\n", - " 700, 29.493030\n", - " 800, 33.799893\n", + " 700, 345.423208\n", + " 800, 393.963155\n", " 900, 442.314962\n", - "1000x1000: Ref: 6.0360 s, This: 2.4149 s, speedup: 2.50\n", - "21.04user 6.65system 0:08.48elapsed 326%CPU (0avgtext+0avgdata 23040maxresident)k\n", - "256inputs+0outputs (0major+1099minor)pagefaults 0swaps\n" + "1000x1000: Ref: 5.6783 s, This: 2.6528 s, speedup: 2.14\n", + "21.56user 6.18system 0:08.37elapsed 331%CPU (0avgtext+0avgdata 23040maxresident)k\n", + "3200inputs+0outputs (2major+1098minor)pagefaults 0swaps\n" ] } ], @@ -3212,7 +2361,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 45, "metadata": { "exercise": "solution" }, @@ -3223,7 +2372,7 @@ "text": [ "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", - "1000x1000: Ref: 2.3086 s, This: 0.2739 s, speedup: 8.43\n" + "1000x1000: Ref: 2.1678 s, This: 0.2869 s, speedup: 7.56\n" ] } ], @@ -3304,30 +2453,30 @@ "\n", "Adapt the following command with your configuration – or follow along accordingly in the non-interactive version of the Notebook.\n", "\n", + "We are mixing Python with Bash (`!`) here, so don't get confused (because of this, if we want to use Bash environment variables, we need to use two `$$`)\n", + "\n", "What's your maximum speedup?" ] }, { "cell_type": "code", - "execution_count": 38, + "execution_count": null, "metadata": { - "exercise": "solution" + "exercise": "task" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Affinity: {0},{1},{2},{3}\n", - "<<Waiting for dispatch ...>>\n", - "<<Starting on login1>>\n", - " OMP_PLACES='{0},{1},{2},{3}' custom\n", - "1000x1000: Ref: 6.0379 s, This: 2.4154 s, speedup: 2.50\n", - "Affinity: {0},{5},{9},{13}\n", + "Affinity: {X},{Y},{Z},{A}\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", - " OMP_PLACES='{0},{5},{9},{13}' custom\n", - "1000x1000: Ref: 2.3051 s, This: 0.6816 s, speedup: 3.38\n" + "1587-117 The string for the OpenMP environment variable 'OMP_PLACES' contains unexpected or invalid text. OpenMP environment variable ignored. \n", + " OMP_PLACES='cores(44)'\n", + "1000x1000: Ref: 2.0988 s, This: 0.6556 s, speedup: 3.20\n", + "Affinity: {P},{Q},{R},{S}\n", + "<<Waiting for dispatch ...>>\n" ] } ], @@ -3398,7 +2547,7 @@ "source": [ "# Survey<a name=\"survey\"></a>\n", "\n", - "Please rememeber to take some time and fill out the [survey](http://bit.ly/sc18-eval)." + "Please rememeber to take some time and fill out the [survey](http://bit.ly/sc19-eval)." ] } ], diff --git a/3-Optimizing_POWER/Handson/HandsOnPerformanceOptimization.html b/3-Optimizing_POWER/Handson/HandsOnPerformanceOptimization.html index 42729e6..53ef1e0 100644 --- a/3-Optimizing_POWER/Handson/HandsOnPerformanceOptimization.html +++ b/3-Optimizing_POWER/Handson/HandsOnPerformanceOptimization.html @@ -13017,45 +13017,6 @@ ul.typeahead-list > li > a.pull-right { .highlight .vm { color: #19177C } /* Name.Variable.Magic */ .highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ </style> -<style type="text/css"> - -/* Temporary definitions which will become obsolete with Notebook release 5.0 */ -.ansi-black-fg { color: #3E424D; } -.ansi-black-bg { background-color: #3E424D; } -.ansi-black-intense-fg { color: #282C36; } -.ansi-black-intense-bg { background-color: #282C36; } -.ansi-red-fg { color: #E75C58; } -.ansi-red-bg { background-color: #E75C58; } -.ansi-red-intense-fg { color: #B22B31; } -.ansi-red-intense-bg { background-color: #B22B31; } -.ansi-green-fg { color: #00A250; } -.ansi-green-bg { background-color: #00A250; } -.ansi-green-intense-fg { color: #007427; } -.ansi-green-intense-bg { background-color: #007427; } -.ansi-yellow-fg { color: #DDB62B; } -.ansi-yellow-bg { background-color: #DDB62B; } -.ansi-yellow-intense-fg { color: #B27D12; } -.ansi-yellow-intense-bg { background-color: #B27D12; } -.ansi-blue-fg { color: #208FFB; } -.ansi-blue-bg { background-color: #208FFB; } -.ansi-blue-intense-fg { color: #0065CA; } -.ansi-blue-intense-bg { background-color: #0065CA; } -.ansi-magenta-fg { color: #D160C4; } -.ansi-magenta-bg { background-color: #D160C4; } -.ansi-magenta-intense-fg { color: #A03196; } -.ansi-magenta-intense-bg { background-color: #A03196; } -.ansi-cyan-fg { color: #60C6C8; } -.ansi-cyan-bg { background-color: #60C6C8; } -.ansi-cyan-intense-fg { color: #258F8F; } -.ansi-cyan-intense-bg { background-color: #258F8F; } -.ansi-white-fg { color: #C5C1B4; } -.ansi-white-bg { background-color: #C5C1B4; } -.ansi-white-intense-fg { color: #A1A6B2; } -.ansi-white-intense-bg { background-color: #A1A6B2; } - -.ansi-bold { font-weight: bold; } - - </style> <style type="text/css"> @@ -13089,7 +13050,7 @@ div#notebook { <!-- Loading mathjax macro --> <!-- Load mathjax --> - <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS_HTML"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/latest.js?config=TeX-AMS_HTML"></script> <!-- MathJax configuration --> <script type="text/x-mathjax-config"> MathJax.Hub.Config({ @@ -13116,7 +13077,7 @@ div#notebook { <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<h1 id="Hands-On-Performance-Optimization">Hands-On Performance Optimization<a class="anchor-link" href="#Hands-On-Performance-Optimization">¶</a></h1><p><em>Supercomputing 2018 Tutorial "Application Porting and Optimization on GPU-Accelerated POWER Architectures", November 12th 2018</em></p> +<h1 id="Hands-On-Performance-Optimization">Hands-On Performance Optimization<a class="anchor-link" href="#Hands-On-Performance-Optimization">¶</a></h1><p><em>Supercomputing 2019 Tutorial "Application Porting and Optimization on GPU-Accelerated POWER Architectures", November 18th 2019</em></p> <hr> </div> @@ -13128,7 +13089,7 @@ div#notebook { <p>As for the first task of this tutorial, also this task is primarily designed to be executed as an interactive Jupyter Notebook. However, everything can also be done using an SSH connection to Ascent (or any other POWER9 computer) in your terminal.</p> <h2 id="Jupyter-notebook-execution">Jupyter notebook execution<a class="anchor-link" href="#Jupyter-notebook-execution">¶</a></h2><p>When using Jupyter, this Notebook will guide you through the steps. Note that if you execute a cell multiple times while optimizng the code the output will be replaced. You can however duplicate the cell you want to execute and keep its output. Check the <em>edit</em> menu above.</p> <p>You will always find links to a file browser of the corresponding task subdirectory as well as direct links to the source files you will need to edit as well as the profiling output you need to open locally.</p> -<p>If you want you also can get a <a href="/terminals/1">terminal</a> in your browser.</p> +<p>If you want you also can get a terminal in your browser; just open it via the »New Launcher« button (<code>+</code>).</p> <h2 id="Terminal-fallback">Terminal fallback<a class="anchor-link" href="#Terminal-fallback">¶</a></h2><p>The tasks are place in directories named <code>Task[1-3]</code>.</p> <p>Makefile targets are created to cover everything, from compile, to run and profile. Please take a look at the cells containing the make calls as a guide also for the non-interactive version of this description.</p> @@ -13138,10 +13099,23 @@ div#notebook { <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<h2 id="Setup">Setup<a class="anchor-link" href="#Setup">¶</a></h2><p>This hands-on session requires of GCC 6.4.0. By loading the <code>sc18/handson2</code> module before invoking this Notebook, we took care of also loading GCC 6.4.0 into the environment.</p> +<h2 id="Setup">Setup<a class="anchor-link" href="#Setup">¶</a></h2><p>We are using some very fresh compiler features and use GCC 9.2.0 because of that. It should already be in your environment. Let's check!</p> </div> </div> +</div> +<div class="cell border-box-sizing code_cell rendered"> +<div class="input"> +<div class="prompt input_prompt">In [ ]:</div> +<div class="inner_cell"> + <div class="input_area"> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>gcc --version +</pre></div> + + </div> +</div> +</div> + </div> <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> @@ -13149,17 +13123,19 @@ div#notebook { <h2 id="Tasks">Tasks<a name="top" /><a class="anchor-link" href="#Tasks">¶</a></h2><p>This session comes with multiple tasks, each one to be found in the respective sub-directory <code>Task[1-3]</code>. In each of these directories you will also find Makefiles that are set up so that you can compile and submit all necessary tasks.</p> <p>Please choose from the task below.</p> <ul> -<li><p><a href="#task1">Task 1</a>: Compile Flags<br> -Improve performance of the CPU Jacobi solver with compiler flags such as <code>Ofast</code> and profile-directed feedback (<a href="#solution0">Solution 1</a>)</p> -</li> -<li><p><a href="#task2">Task 2</a>: Software Prefetching<br> -Improve performance of the CPU Jacobi solver with software prefetching (<a href="#solution1">Solution 2</a>)</p> -</li> -<li><p><a href="#task3">Task 3</a>: OpenMP<br> -Parallelize the CPU Jacobi solver and determine the right binding to be used for optimal performance (<a href="#solution2">Solution 3</a>)</p> -</li> -<li><p><a href="#survey">Suvery</a> Please remember to take the survey !</p> -</li> +<li><a href="#task1">Task 1</a>: <strong>Basic compiler optimization flags and compiler annotations</strong></li> +</ul> +<p>Improve performance of the CPU Jacobi solver with compiler flags such as <code>Ofast</code> and profile-directed feedback. Learn about compiler annotations.</p> +<ul> +<li><a href="#task2">Task 2</a>: <strong>Optimization via Prefetching controlled by compiler</strong></li> +</ul> +<p>Improve performance of the CPU Jacobi solver with software prefetching. Some compilers such as IBM XL define flags that can be used to modify the aggressiveness of the hardware prefetcher. Learn to modify the DSCR value through XL and study the impact on application performance.</p> +<ul> +<li><a href="#task3">Task 3</a>: <strong>Optimization via OpenMP controlled by compiler and the system</strong></li> +</ul> +<p>Parallelize the CPU Jacobi solver and determine the right binding to be used for optimal performance.</p> +<ul> +<li><a href="#survey">Suvery</a> Please remember to take the survey !</li> </ul> <h3 id="Make-Targets-">Make Targets <a name="make" /><a class="anchor-link" href="#Make-Targets-">¶</a></h3><p>For all tasks we have defined the following make targets.</p> <ul> @@ -13184,11 +13160,13 @@ build <code>poisson2d</code> binary (default)</li> <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<h2 id="Task-1:-Compile-Flags-">Task 1: Compile Flags <a name="task1" /><a class="anchor-link" href="#Task-1:-Compile-Flags-">¶</a></h2><h3 id="Overview">Overview<a class="anchor-link" href="#Overview">¶</a></h3><p>The goal of this task is to understand different options available to optimize the performance of the CPU Jacobi solver</p> +<h2 id="Task-1:-Basic-compiler-optimization-flags-and-compiler-annotations-">Task 1: Basic compiler optimization flags and compiler annotations <a name="task1" /><a class="anchor-link" href="#Task-1:-Basic-compiler-optimization-flags-and-compiler-annotations-">¶</a></h2><h3 id="Overview">Overview<a class="anchor-link" href="#Overview">¶</a></h3><p>The goal of this task is to understand different options available to optimize the performance of the CPU Jacobi solver</p> <p>Your task is to:</p> <ul> <li>Optimize performance with <code>-Ofast</code> flag</li> +<li>Verify the cause for performance improvement by viewing perf profiles of O3 and Ofast binaries </li> <li>Optimize performance with profile directed feedback </li> +<li>Generate compiler annotations/remarks to understand the optimizations done by the compiler with and without profile directed feedback </li> </ul> <p>First, change the working directory to <code>Task1</code>.</p> @@ -13211,7 +13189,8 @@ build <code>poisson2d</code> binary (default)</li> <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<h3 id="Part-A:--Ofast-vs.--O3">Part A: <code>-Ofast</code> vs. <code>-O3</code><a class="anchor-link" href="#Part-A:--Ofast-vs.--O3">¶</a></h3><p>We are to compare the performance of the binary being compiled with <code>-Ofast</code> optimization and with <code>-O3</code> optimization. Right now, the Makefile specifies <code>-O3</code> as the optimization flag. Compile the code using <code>make</code> and run it with <code>make run</code> in the next two cells.</p> +<h3 id="Part-A:--Ofast-vs.--O3">Part A: <code>-Ofast</code> vs. <code>-O3</code><a class="anchor-link" href="#Part-A:--Ofast-vs.--O3">¶</a></h3><p>We are to compare the performance of the binary being compiled with <code>-Ofast</code> optimization and with <code>-O3</code> optimization. As in the previous task, we use a <code>Makefile</code> for compilation. The <code>Makefile</code> targets <code>poisson2d_O3</code> and <code>poisson2d_Ofast</code> are already prepared.</p> +<p><strong>TASK</strong>: Add <code>-O3</code> as the optimization flag for the <code>poisson2d_O3</code> target by using the corresponding <code>CFLAGS</code> definition. There are notes relating to this Task 1 in the header of the <code>Makefile</code>. Compile the code using <code>make</code> as indicated below and run with the <code>Make</code> targets <code>run</code>, <code>run_perf</code> and <code>run_perf_recrep</code>.</p> </div> </div> @@ -13221,7 +13200,7 @@ build <code>poisson2d</code> binary (default)</li> <div class="prompt input_prompt">In [ ]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make poisson2d_O3 </pre></div> </div> @@ -13245,7 +13224,7 @@ build <code>poisson2d</code> binary (default)</li> <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<p>You can use the GNU <em>perf</em> tool to profile the application using the <code>perf</code> command (see below) and see the top time-consuming functions.</p> +<p>Let's have a look at the output of the <code>Makefile</code> target <code>run_perf</code>. It invokes the GNU <em>perf</em> tool to print out details of the number of instructions executed and the number of cycles taken by POWER9 to execute the program. Feel free to add further counter to this call to <em>perf</em>.</p> </div> </div> @@ -13255,10 +13234,7 @@ build <code>poisson2d</code> binary (default)</li> <div class="prompt input_prompt">In [ ]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="c1"># perf record creates a perf.data file </span> -<span class="o">!</span>perf record -o perf.O3.data -e cycles ./poisson2d -<span class="c1"># perf report opens the perf.data file </span> -<span class="o">!</span>perf report -i perf.O3.data <span class="p">|</span> cat +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make run_perf </pre></div> </div> @@ -13269,7 +13245,7 @@ build <code>poisson2d</code> binary (default)</li> <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<p><strong>TASK</strong>: Now change the optimization flag in the <a href="/edit/Task1/Makefile">Makefile</a> to <code>-Ofast</code> and repeat the steps in the following cell. In case you follow along non-interactive, call <code>make</code> and <code>make run</code> in your shell. (If you are in the Jupyter Notebook, you can actually click the link of the <a href="/edit/Task1/Makefile">Makefile</a>. In other cases, use <code>vim</code> which is installed on Ascent.)</p> +<p>Next we run the makefile with target <code>run_perf_recrep</code> that prints the top routines of the application in terms of hotness by using a combination of <code>perf record ./app</code> and <code>perf report</code>.</p> </div> </div> @@ -13279,36 +13255,73 @@ build <code>poisson2d</code> binary (default)</li> <div class="prompt input_prompt">In [ ]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make +<div class=" highlight hl-ipython3"><pre><span></span><span class="c1"># run_perf_recrep displays the top hot routines </span> +<span class="o">!</span>make run_perf_recrep </pre></div> </div> </div> </div> +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p><strong>TASK</strong>: Now add the optimization flag <code>Ofast</code> to the <code>CFLAGS</code> for target <code>poisson2d_Ofast</code>. Compile the program with the target <code>poisson2d_Ofast</code> and run and analyse it as before with <code>run</code>, <code>run_perf</code> and <code>run_perf_recrep</code>.</p> +<p>What difference do you see?</p> + +</div> +</div> </div> <div class="cell border-box-sizing code_cell rendered"> <div class="input"> <div class="prompt input_prompt">In [ ]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make run +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make poisson2d_Ofast +<span class="o">!</span>make run </pre></div> </div> </div> </div> +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p>Again, run a <code>perf</code>-instrumented version:</p> + +</div> +</div> </div> <div class="cell border-box-sizing code_cell rendered"> <div class="input"> <div class="prompt input_prompt">In [ ]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="c1"># perf record creates a perf.data file </span> -<span class="o">!</span>perf record -o perf.Ofast.data -e cycles ./poisson2d -<span class="c1"># perf report opens the perf.data file </span> -<span class="o">!</span>perf report -i perf.Ofast.data <span class="p">|</span> cat +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make run_perf +</pre></div> + + </div> +</div> +</div> + +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p>Generate the list of top routines in terms of hotness:</p> + +</div> +</div> +</div> +<div class="cell border-box-sizing code_cell rendered"> +<div class="input"> +<div class="prompt input_prompt">In [ ]:</div> +<div class="inner_cell"> + <div class="input_area"> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make run_perf_recrep </pre></div> </div> @@ -13327,7 +13340,7 @@ build <code>poisson2d</code> binary (default)</li> <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<h4 id="Interpretation">Interpretation<a class="anchor-link" href="#Interpretation">¶</a></h4><p>Depending on the application requirement, if a high precision of results is not mandatory, the users can compile an application with <code>-Ofast</code> which enables <code>–ffast-math</code> option that implements the same math function in a relaxed manner very similar to how general mathematical expressions are implemented and avoids the overhead of calling a function from the math library. Comparing the files, you will see that the <code>-Ofast</code> binary natively implements the <code>fmax</code> function using instructions available in the hardware. The <code>-O3</code> binary makes a library call to compute <code>fmax</code> to follow a stricter <em>IEEE</em> requirement for accuracy.</p> +<h4 id="Interpretation">Interpretation<a class="anchor-link" href="#Interpretation">¶</a></h4><p>Depending on the application requirement, if a high precision of results is not mandatory, one can compile an application with <code>-Ofast</code> which enables <code>–ffast-math</code> option that implements the same math function in a relaxed manner very similar to how general mathematical expressions are implemented and avoids the overhead of calling a function from the math library. Comparing the files, you will see that the <code>-Ofast</code> binary natively implements the <code>fmax</code> function using instructions available in the hardware. The <code>-O3</code> binary makes a library call to compute <code>fmax</code> to follow a stricter <em>IEEE</em> requirement for accuracy.</p> </div> </div> @@ -13335,17 +13348,26 @@ build <code>poisson2d</code> binary (default)</li> <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<h3 id="Part-B:-Profile-directed-Feedback">Part B: Profile-directed Feedback<a class="anchor-link" href="#Part-B:-Profile-directed-Feedback">¶</a></h3><p>For the first level of optimization we saw <code>Ofast</code> cut the execution time of the <code>O3</code> binary by almost half.</p> -<p>We can optimize the performance further by using profile directed feedback optimization.</p> -<p>To compile using profile directed feedback with the GCC compiler we need to do the following steps</p> +<h3 id="Part-B:-Profile-directed-Feedback">Part B: Profile-directed Feedback<a class="anchor-link" href="#Part-B:-Profile-directed-Feedback">¶</a></h3><p>For the first level of optimization we see that <code>Ofast</code> cut the execution time of the <code>O3</code> binary by almost half.</p> +<p>We can optimize the performance further by using profile-directed feedback optimization.</p> +<p>To compile using profile-directed feedback with the GCC compiler we need to build the appplication in three stages:</p> <ol> -<li>We need to first build a training binary using <code>-fprofile-generate</code>; this instructs the compiler to record hot path information </li> -<li>Run the training binary with a smaller input size; you should see a <code>.gcda</code> file generated which stores hot path information for further optimization by the compiler </li> -<li>build the final binary using <code>-fprofile-use</code> which uses the profile information in the <code>.gcda</code> file </li> -<li>Compare the performance of the final binary with the original <code>Ofast</code> binary </li> +<li>Instrument binary;</li> +<li>Run binary with training, gather profile information;</li> +<li>Use profile information to generate optimized binary.</li> </ol> -<p><strong>TASK</strong>: First, search for <code>TODO1</code> in the <a href="/edit/Task1/Makefile">Makefile</a>. It defines an additional compilation flag for <code>gcc</code>. Insert <code>-fprofile-generate=FOLDER</code> there with FOLDER pointing to <code>$$SC18_DIR_SCRATCH</code>, your personal write-directory (the double dollar signs are intentional as they are used to escape in the GNU Make syntax).</p> -<p>After editing, run the following two cells to train your program.</p> +<p>Step 1 is achieved by compiling the binary with the correct flag – <code>-fprofile-generate</code>. In our case, we need to specify an output location, which should be <code>$(SC19_DIR_SCRATCH)</code>.</p> +<p>Step 2 consists of a usual, albeit shorter run of the instrumented binary. The can be very short, though the parameters need to be representative of the actual run. After the binary ran, an output file (with file extension <code>.gcda</code>) is written to the directory specified during compilation.</p> +<p>For Step 3, the binary is once again compiled, but this time using the <code>gcda</code> profile just generated. The according flag is <code>-fprofile-use</code>, which we set to <code>$(SC19_DIR_SCRATCH)</code> as well.</p> +<p>In our <code>Makefile</code> at hand, we prepared the steps already for you in the form of two targets.</p> +<ul> +<li><code>poisson2d_train</code>: Will compile the binary with profile-directed feedback</li> +<li><code>poisson2d_ref</code>: Will take a generated profile and compile a new, optimized binary</li> +</ul> +<p>By using dependencies, between these two targets a profile run is launched.</p> +<p><strong>TASK</strong>: Edit the <a href="`Makefile`">Makefile</a> and add the <code>-fprofile-*</code> flags to the <code>CFLAGS</code> of <code>poisson2d_train</code> and +<code>poisson2d_ref</code> as outline in the file.</p> +<p>After that, you may launch them with the following cells (<code>gen_profile</code> is a meta-target and uses <code>poisson2d_train</code> and <code>poisson2d_ref</code>). If you need to clean the generated profile, you may use <code>make clean_profile</code>.</p> </div> </div> @@ -13355,20 +13377,28 @@ build <code>poisson2d</code> binary (default)</li> <div class="prompt input_prompt">In [ ]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make poisson2d_train +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make gen_profile </pre></div> </div> </div> </div> +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p>If the previous cell executed correctly, you now have your optimized executable. Let's see if it even fast than before!</p> + +</div> +</div> </div> <div class="cell border-box-sizing code_cell rendered"> <div class="input"> <div class="prompt input_prompt">In [ ]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make run_train +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make run </pre></div> </div> @@ -13379,9 +13409,7 @@ build <code>poisson2d</code> binary (default)</li> <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<p>Now, a <code>.gcda</code> file exists in the directory which can be used for an profile-accelerated subsequent run.</p> -<p><strong>TASK</strong>: Edit the <a href="/edit/Task1/Makefile">Makefile</a> again, this time modifying <code>TODO2</code> to be equivalent to <code>-fprofile-use</code>. A directory is not needed as we copied the gcda file into the current directory.</p> -<p>Run the following cells in order to build using the newly added flag and then run with the profile-accelerated version.</p> +<p>Let's also measure instructions and cycles</p> </div> </div> @@ -13391,20 +13419,38 @@ build <code>poisson2d</code> binary (default)</li> <div class="prompt input_prompt">In [ ]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make poisson2d_profile +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make run_perf </pre></div> </div> </div> </div> +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p>What is your speed-up? Feel free to run with larger problem sizes (mesh; iterations)</p> + +</div> +</div> +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<h3 id="Part-C:-Compiler-annotations/Remarks">Part C: Compiler annotations/Remarks<a class="anchor-link" href="#Part-C:-Compiler-annotations/Remarks">¶</a></h3><p>Usually, all compilers provide an option to emit annotations or remarks by the compiler. These remarks summarize the optimizations done in detail, the location in source where these optimizations were done. There exist options that also indicate optimizations that were missed and the reason why they could not be done.</p> +<p>To generate compiler annotations using GCC, one uses <code>-fopt-info-all</code>. If you only want to see the missed options, use the option <code>-fopt-info-missed</code> instead of <code>-fopt-info-all</code>. See also the <a href="https://gcc.gnu.org/onlinedocs/gcc/Developer-Options.html#index-fopt-info">documentation of GCC regarding the flag</a>.</p> +<p><strong>TASK</strong>: Have a looK at the <code>CFLAGS</code> of the <code>Makefile</code> target <code>poisson2d_Ofast_info</code>. Add the flag <code>-fopt-info-all</code> to the list of flags. This will print optimisation information to stdout. If you rather want to print to this information to a file, use – for example – <code>-fopt-info-all=(SC19_DIR_SCRATCH)/filename</code>.</p> + +</div> +</div> </div> <div class="cell border-box-sizing code_cell rendered"> <div class="input"> <div class="prompt input_prompt">In [ ]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make run_profile +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make poisson2d_Ofast_info </pre></div> </div> @@ -13415,7 +13461,34 @@ build <code>poisson2d</code> binary (default)</li> <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<p>What is your speed-up? Feel free to run with larger problem sizes (mesh; iterations)</p> +<p>Let's compare this with the output during compilation when using profile-directed feedback from Task 1 B.</p> +<p><strong>TASK</strong>: +Adapt the <code>CFLAGS</code> of <code>poisson2d_ref_info</code> to include <code>-fopt-info-all</code> <strong>and</strong> the profile input of <code>-fprofile-use=…</code> here. <em>(Be advised: Long output!)</em></p> + +</div> +</div> +</div> +<div class="cell border-box-sizing code_cell rendered"> +<div class="input"> +<div class="prompt input_prompt">In [ ]:</div> +<div class="inner_cell"> + <div class="input_area"> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make poisson2d_ref_info +</pre></div> + + </div> +</div> +</div> + +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p>Comparing the annotations generated of a plain <code>-Ofast</code> optimization level and the one generated at <code>-Ofast</code> and profile directed feedback, we observe that many more optimizations are possible due to profile information.</p> +<p>For instance you will see annotations such as</p> + +<pre><code>poisson2d.c:114:25: optimized: loop unrolled 3 times (header execution count 436550)</code></pre> +<p>The execution count indicates the dynamic execution count of the node at runtime. This information determines which paths are hotter and subsequently facilitate additional optimizations.</p> </div> </div> @@ -13443,8 +13516,12 @@ build <code>poisson2d</code> binary (default)</li> <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<h2 id="Task-2:-Software-Pretechting">Task 2:<a name="task2" /> Software Pretechting<a class="anchor-link" href="#Task-2:-Software-Pretechting">¶</a></h2><h3 id="Overview">Overview<a class="anchor-link" href="#Overview">¶</a></h3><p>Study the difference of program execution time of different optimization levels with and without software prefetching.</p> -<p>First, change directory to that of Task 2</p> +<h2 id="Task-2:-Impact-of-Prefetching-on-Performance">Task 2:<a name="task2" /> Impact of Prefetching on Performance<a class="anchor-link" href="#Task-2:-Impact-of-Prefetching-on-Performance">¶</a></h2><h3 id="Overview">Overview<a class="anchor-link" href="#Overview">¶</a></h3><ul> +<li>Study the difference of program execution time of different optimization levels with and without software prefetching.</li> +<li>Verify the impact by measuring cache counters with and without prefetching.</li> +<li>Learn how to modify contents of DSCR (<em>Data Stream Control Register</em>) using IBM XL compiler and study the impact with different values to DSCR. </li> +</ul> +<p>But first, lets change directory to that of Task 2</p> </div> </div> @@ -13465,15 +13542,18 @@ build <code>poisson2d</code> binary (default)</li> <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<h3 id="Part-A:-Running">Part A: Running<a class="anchor-link" href="#Part-A:-Running">¶</a></h3> +<h3 id="Part-A:-Software-Prefetching">Part A: Software Prefetching<a class="anchor-link" href="#Part-A:-Software-Prefetching">¶</a></h3> </div> </div> </div> <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<p>Look at the <a href="/edit/Task2/Makefile">Makefile</a> and work on the TODOs. Please implement compile flags as mentioned in the Makefile target name.</p> -<p>Afterwards, compile each target with the following cells and submit them to the batch system. Follow along accordingly in the non-interactive version of this Notebook.</p> +<p><strong>TASK</strong>: Look at the Makefile and work on the TODOs.</p> +<ul> +<li>First generate a <code>-Ofast</code>-optimised binary and note down the performance in terms of cycles, seconds, and L3 misses. This is our baseline!</li> +<li>Modify the <code>Makefile</code> to add the option for software prefetching (<code>-fprefetch-loop-arrays</code>). Compare performance of <code>-Ofast</code> with and without software prefetching</li> +</ul> </div> </div> @@ -13483,7 +13563,7 @@ build <code>poisson2d</code> binary (default)</li> <div class="prompt input_prompt">In [ ]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make run_o3_pref +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make clean </pre></div> </div> @@ -13496,7 +13576,9 @@ build <code>poisson2d</code> binary (default)</li> <div class="prompt input_prompt">In [ ]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make run_ofast_pref +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make poisson2d <span class="nv">CC</span><span class="o">=</span>gcc +<span class="o">!</span>make run +<span class="o">!</span>make l3missstats </pre></div> </div> @@ -13509,31 +13591,66 @@ build <code>poisson2d</code> binary (default)</li> <div class="prompt input_prompt">In [ ]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make run_o3_nopref +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make poisson2d_pref <span class="nv">CC</span><span class="o">=</span>gcc +<span class="o">!</span>make run +<span class="o">!</span>make l3missstats </pre></div> </div> </div> </div> +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p><strong>TASK</strong>: Repeat the experiment with the <code>-O3</code> flag. Have a look at the <code>Makefile</code> and the outlined TODO. There's a position to easily adapt <code>-Ofast</code>→<code>-O3</code>!</p> + +</div> +</div> </div> <div class="cell border-box-sizing code_cell rendered"> <div class="input"> <div class="prompt input_prompt">In [ ]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make run_ofast_nopref +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make poisson2d <span class="nv">CC</span><span class="o">=</span>gcc -B +<span class="o">!</span>make run +<span class="o">!</span>make l3missstats </pre></div> </div> </div> </div> +</div> +<div class="cell border-box-sizing code_cell rendered"> +<div class="input"> +<div class="prompt input_prompt">In [ ]:</div> +<div class="inner_cell"> + <div class="input_area"> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make poisson2d_pref <span class="nv">CC</span><span class="o">=</span>gcc -B +<span class="o">!</span>make run +<span class="o">!</span>make l3missstats +</pre></div> + + </div> +</div> +</div> + +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p>Do you notice the impact difference with optimization levels? At what optimization level does software prefetching help the most?</p> + +</div> +</div> </div> <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<p>Do you notice the impact difference with optimization levels? It's always important to carefully study the interplay of flags.</p> +<h3 id="Part-B:-Analysis-of-Instructions">Part B: Analysis of Instructions<a class="anchor-link" href="#Part-B:-Analysis-of-Instructions">¶</a></h3><p>Compilation of the <code>-Ofast</code> binary with the software prefetching flag causes the compiler to generate the <code>dcb*</code> instructions that prefetch memory values to L3.</p> </div> </div> @@ -13541,8 +13658,10 @@ build <code>poisson2d</code> binary (default)</li> <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<h3 id="Part-B:-Analysis-of-Instructions">Part B: Analysis of Instructions<a class="anchor-link" href="#Part-B:-Analysis-of-Instructions">¶</a></h3><p>Compilation with the software prefetching flag causes the compiler to generate the <code>__dcbt</code> and <code>__dcbtst</code> instructions that prefetch memory values to L3.</p> -<p>Verify it using <code>objdump -lSd</code> on each file (<code>poisson2d_o3_pref</code>, <code>poisson2d_ofast_pref</code>, <code>poisson2d_o3_nopref</code>, <code>poisson2d_ofast_nopref</code>). You might want to grep for <code>dcb</code>.</p> +<p><strong>TASK</strong>: +Run <code>$(SC19_SUBMIT_CMD) objdump -lSd</code> on each binary file (<code>-O3</code>, <code>-Ofast</code> with prefetch/no prefetch). +Look for instructions beginning with <code>dcb</code> +At what optimization levels does the compiler generate software prefetching instructions?</p> </div> </div> @@ -13552,7 +13671,60 @@ build <code>poisson2d</code> binary (default)</li> <div class="prompt input_prompt">In [ ]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="ch">#!objdump -l…</span> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make <span class="nv">CC</span><span class="o">=</span>gcc -B poisson2d_pref +<span class="o">!</span>objdump -lSd ./poisson2d_pref > poisson2d.dis +</pre></div> + + </div> +</div> +</div> + +</div> +<div class="cell border-box-sizing code_cell rendered"> +<div class="input"> +<div class="prompt input_prompt">In [ ]:</div> +<div class="inner_cell"> + <div class="input_area"> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>grep dcb poisson2d.dis +</pre></div> + + </div> +</div> +</div> + +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<h3 id="Part-C:-Changing-Values-of-DSCR-via-compiler-flags">Part C: Changing Values of DSCR via compiler flags<a class="anchor-link" href="#Part-C:-Changing-Values-of-DSCR-via-compiler-flags">¶</a></h3><p>This task requires using the IBM XL compiler. It should be already in your environment.</p> +<p>We saw the impact of software prefetching in the previous subsection. +In certain cases, tuning the hardware prefetcher through compiler options can also help improve performance. +In this exercise we shall see some compiler options that can be used to modify the DSCR value which controls aggressiveness of prefetching. It can be also used to turn off hardware prefetching.</p> +<p>IBM XL compiler has an option <code>-qprefetch=dscr=<val></code> that can be used for this purpose. +Compiling with <code>-qprefetch=dscr=1</code> turns off the prefetcher. One can give various values such as <code>-qprefetch=dscr=4</code>, <code>-qprefetch=dscr=7</code> etc. to control aggressiveness of prefetching.</p> +<p>For this exercise we use <code>make CC=xlc_r</code> to illustrate the performance impact.</p> +<p><strong>Task</strong> Generate a XL-compiled binary by compiling using the following cells. After you've generated a baseline, start editing the <code>Makefile</code>: Add <code>qprefetch=dscr=1</code> to the <code>CFLAGS</code> and rebuild the application and note the performance. Which one is faster?</p> +<p>In general, applications benefit with the default settings of hardware DSCR register (<code>-qprefetch=dscr=0</code>). However, certain applications also benefit with prefetching turned off.</p> +<p>It is to be noted that DSCR values are highly sensitive to the application. One value that works well for Application A may not help Application B.</p> + +</div> +</div> +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p>Measure performance of the application compiled with XL at default DSCR value</p> + +</div> +</div> +</div> +<div class="cell border-box-sizing code_cell rendered"> +<div class="input"> +<div class="prompt input_prompt">In [ ]:</div> +<div class="inner_cell"> + <div class="input_area"> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make <span class="nv">CC</span><span class="o">=</span>xlc_r -B poisson2d +<span class="o">!</span>make run </pre></div> </div> @@ -13563,7 +13735,7 @@ build <code>poisson2d</code> binary (default)</li> <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<p>If you feel up to the task, you can study the number of L3 cache misses using the corresponding performance counter, <code>PM_L3_MISS</code>. Either use your knowledge from Hands-On 1, or use the following call to <code>perf</code>, in which we already converted the named counter to a raw counter address.</p> +<p>Measure performance of the application compiled with XL with DSCR value turned off</p> </div> </div> @@ -13573,14 +13745,22 @@ build <code>poisson2d</code> binary (default)</li> <div class="prompt input_prompt">In [ ]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="p">[</span><span class="s2">"poisson2d_ofast_nopref"</span><span class="p">,</span> <span class="s2">"poisson2d_ofast_pref"</span><span class="p">]:</span> - <span class="o">!</span><span class="nb">eval</span> <span class="nv">$$</span>SC18_SUBMIT_CMD perf stat -e cycles,r168a4 ./<span class="nv">$f</span> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make poisson2d_dscr <span class="nv">CC</span><span class="o">=</span>xlc_r -B +<span class="o">!</span>make run </pre></div> </div> </div> </div> +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p>Does Hardware prefetcher help this application? How much impact do you see when you turn off the hardware prefetcher?</p> + +</div> +</div> </div> <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> @@ -13588,6 +13768,7 @@ build <code>poisson2d</code> binary (default)</li> <h4 id="References">References<a class="anchor-link" href="#References">¶</a></h4><ol> <li><a href="https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html">https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html</a></li> <li><a href="https://www.gnu.org/software/gcc/projects/prefetch.html">https://www.gnu.org/software/gcc/projects/prefetch.html</a></li> +<li><a href="https://openpowerfoundation.org/?resource_lib=power-isa-version-3-0">https://openpowerfoundation.org/?resource_lib=power-isa-version-3-0</a></li> </ol> </div> @@ -13606,8 +13787,8 @@ build <code>poisson2d</code> binary (default)</li> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> <h2 id="Task-3:-OpenMP">Task 3: OpenMP<a class="anchor-link" href="#Task-3:-OpenMP">¶</a></h2><p><a name="task3"></a></p> -<h3 id="Overview">Overview<a class="anchor-link" href="#Overview">¶</a></h3><p>We add OpenMP shared-memory parallelism to the application. Also, we study the effect of binding the multi-thread processes to certain cores.</p> -<p>First, we need to change directory to that of Task3.</p> +<h3 id="Overview">Overview<a class="anchor-link" href="#Overview">¶</a></h3><p>We add OpenMP shared-memory parallelism to the application. Also, we study the effect of binding the multi-thread processes to certain cores on the resulting application performance. We do this study for both GCC and XL compilers inorder to learn about the appropriate options that need to be used. +First, we need to change directory to that of Task3. For Task 3 we modify poisson2d.c to invoke an exact copy of the main jacobi loop which is <code>poisson2d_reference</code>. We parallelize only the main loop but not <code>poisson2d_reference</code>. The speedup is the performance gain seen in the main loop as compared to the reference loop.</p> </div> </div> @@ -13628,13 +13809,12 @@ build <code>poisson2d</code> binary (default)</li> <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<h3 id="Part-A:-Implement-OpenMP-Pragmas;-Compilation">Part A: Implement OpenMP Pragmas; Compilation<a class="anchor-link" href="#Part-A:-Implement-OpenMP-Pragmas;-Compilation">¶</a></h3><p><strong>Task</strong>: Please add the correct OpenMP pragmas to the source code and compilations flags to enable OpenMP.</p> +<h3 id="Part-A:-Implement-OpenMP-Pragmas;-Compilation">Part A: Implement OpenMP Pragmas; Compilation<a class="anchor-link" href="#Part-A:-Implement-OpenMP-Pragmas;-Compilation">¶</a></h3><p><strong>Task</strong>: Please add the correct OpenMP directives to poisson2d.c and compilations flags in the Makefile to enable OpenMP with GCC and XL compilers.</p> <ul> -<li><strong>pragmas</strong>: Look at the TODOs in <a href="/edit/Task3/poisson2d.c"><code>poisson2d.c</code></a> to add OpenMP parallelism. The pragmas in question are <code>#pragma omp parallel for</code></li> -<li><strong>Compilation</strong>: Please add compilation flags enabling OpenMP in GCC to the <a href="/edit/Task3/Makefile">Makefile</a>. The flag in question is <code>-fopenmp</code>.</li> +<li><strong>Directives</strong>: Look at the TODOs in <a href="poisson2d.c"><code>poisson2d.c</code></a> to add OpenMP parallelism. The pragmas in question are <code>#pragma omp parallel for</code> (and once it's <code>#pragma omp parallel for reduction(max:error)</code> – can you guess where?)</li> +<li><strong>Compilation</strong>: Please add compilation flags enabling OpenMP in GCC and XL to the <code>Makefile</code>. For GCC, we need to add <code>-fopenmp</code> and the application needs to be linked with <code>-lgomp</code>. For XL, we need to add <code>-qsmp=omp</code> to the list of compilation flags. </li> </ul> -<p>Edit the files with the links above if you are running the interactive version of the Notebook or navigate to <code>poisson2d.c</code> and <code>Makefile</code> yourself in case you run the non-interactive version.</p> -<p>Afterwards, compile and run the application with the following cells. Non-interactive: Follow along accordingly in the shell.</p> +<p>Afterwards, compile and run the application with the following commands.</p> </div> </div> @@ -13644,20 +13824,28 @@ build <code>poisson2d</code> binary (default)</li> <div class="prompt input_prompt">In [ ]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make poisson2d +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make poisson2d <span class="nv">CC</span><span class="o">=</span>gcc </pre></div> </div> </div> </div> +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p>The command to submit a job to the batch system is prepared in an environment variable <code>$SC19_SUBMIT_CMD</code>; use it together with <code>eval</code>. In the following cell, it is shown how to invoke the application using the batch system.</p> + +</div> +</div> </div> <div class="cell border-box-sizing code_cell rendered"> <div class="input"> <div class="prompt input_prompt">In [ ]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make run +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span><span class="nb">eval</span> <span class="nv">$SC19_SUBMIT_CMD</span> ./poisson2d <span class="m">1000</span> <span class="m">1000</span> <span class="m">1000</span> </pre></div> </div> @@ -13668,7 +13856,9 @@ build <code>poisson2d</code> binary (default)</li> <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<p>The command to submit a job to the batch system is prepared in an environment variable <code>$SC18_SUBMIT_CMD</code>; use it together with <code>eval</code>. In the following cell, it is shown how to increase the work of the application.</p> +<p>Inorder to run the parallel application, we need to set the number of threads using <code>OMP_NUM_THREADS</code> +What is the best performance you can reach by setting the number of threads via <code>OMP_NUM_THREADS=N</code> with <code>N</code> being the number of threads? Feel free to play around with the command in the following cell, using 1 thread as an example.<br> +We added <code>--bind none</code> to prevent <code>jsrun</code>, the scheduler of Ascent, from overlaying binding options. Also, we use <code>-c ALL_CPUS</code> to make all CPUs on the compute nodes available to you.</p> </div> </div> @@ -13678,7 +13868,7 @@ build <code>poisson2d</code> binary (default)</li> <div class="prompt input_prompt">In [ ]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span><span class="nb">eval</span> <span class="nv">$SC18_SUBMIT_CMD</span> ./poisson2d <span class="m">1000</span> <span class="m">1000</span> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span><span class="nb">eval</span> <span class="nv">OMP_NUM_THREADS</span><span class="o">=</span>N <span class="nv">$SC19_SUBMIT_CMD</span> -c ALL_CPUS --bind none ./poisson2d <span class="m">1000</span> <span class="m">1000</span> <span class="m">1000</span> </pre></div> </div> @@ -13689,8 +13879,13 @@ build <code>poisson2d</code> binary (default)</li> <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<p>What is the best performance you can reach by setting the number of threads via <code>OMP_NUM_THREADS=N</code> with <code>N</code> being the number of threads? Feel free to play around with the command in the following cell, using 1 thread as an example.<br> -We added <code>--bind none</code> to prevent <code>jsrun</code>, the scheduler of Ascent, from overlaying binding options. Also, we use <code>-c ALL_CPUS</code> to make all CPUs on the compute nodes available to you.</p> +<h3 id="Part-B:-Bindings">Part B: Bindings<a class="anchor-link" href="#Part-B:-Bindings">¶</a></h3><p>Different CPU architectures and models come with different configuration of cores. The configuration plays an important role in the run time of the application. We need to optimize for it!</p> +<p>There are applications which can be used to determine the configuration of the processor. Among those are:</p> +<ul> +<li><code>lscpu</code>: Can be used to determine the number of sockets, number of cores, and numb of threads. It gives a very good overview and is available on most Linux systems.</li> +<li><code>ppc64_cpu --smt</code>: Specifically for POWER, this tool can give information about the number of simulations threads running per core (<em>SMT</em>, Simulataion Multi-Threading).</li> +</ul> +<p>Run <code>ppc64_cpu --smt</code> to find out about the threading configuration of Ascent!</p> </div> </div> @@ -13700,7 +13895,7 @@ We added <code>--bind none</code> to prevent <code>jsrun</code>, the scheduler o <div class="prompt input_prompt">In [ ]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span><span class="nb">eval</span> <span class="nv">OMP_NUM_THREADS</span><span class="o">=</span><span class="m">1</span> <span class="nv">$SC18_SUBMIT_CMD</span> -c ALL_CPUS --bind none ./poisson2d <span class="m">1000</span> <span class="m">1000</span> <span class="m">1000</span> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span><span class="nb">eval</span> <span class="nv">$SC19_SUBMIT_CMD</span> ppc64_cpu --smt </pre></div> </div> @@ -13711,13 +13906,11 @@ We added <code>--bind none</code> to prevent <code>jsrun</code>, the scheduler o <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<h3 id="Part-B:-Bindings">Part B: Bindings<a class="anchor-link" href="#Part-B:-Bindings">¶</a></h3><p>Different CPU architectures and models come with different configuration of cores. The configuration plays an important role in the run time of the application. We need to optimize for it!</p> -<p>There are applications which can be used to determine the configuration of the processor. Among those are:</p> +<p>There are more sources information available</p> <ul> -<li><code>lscpu</code>: Can be used to determine the number of sockets, number of cores, and numb of threads. It gives a very good overview and is available on most Linux systems.</li> -<li><code>ppc64_cpu --smt</code>: Specifically for POWER, this tool can give information about the number of simulations threads running per core (<em>SMT</em>, Simulataion Multi-Threading).</li> +<li><code>/proc/cpuinfo</code>: Holds information about virtual cores, including model and clock speed. Available on most Linux system. Usually used together with <code>cat</code></li> +<li><code>/sys/devices/system/cpu/cpu0/topology/thread_siblings_list</code>: Holds information about thread siblings for given CPU core (<code>cpu0</code> in this case). Use it to find out which thread is mapped to which core.</li> </ul> -<p>Run <code>ppc64_cpu --smt</code> to find out about the threading configuration of Ascent!</p> </div> </div> @@ -13727,7 +13920,8 @@ We added <code>--bind none</code> to prevent <code>jsrun</code>, the scheduler o <div class="prompt input_prompt">In [ ]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span><span class="nb">eval</span> <span class="nv">$SC18_SUBMIT_CMD</span> ppc64_cpu --smt +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span><span class="nv">$$</span>SC19_SUBMIT_CMD cat /sys/devices/system/cpu/cpu0/topology/thread_siblings_list +<span class="o">!</span><span class="nv">$$</span>SC19_SUBMIT_CMD cat /sys/devices/system/cpu/cpu5/topology/thread_siblings_list </pre></div> </div> @@ -13738,11 +13932,47 @@ We added <code>--bind none</code> to prevent <code>jsrun</code>, the scheduler o <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<p>There are more sources information available</p> -<ul> -<li><code>/proc/cpuinfo</code>: Holds information about virtual cores, including model and clock speed. Available on most Linux system. Usually used together with <code>cat</code></li> -<li><code>/sys/devices/system/cpu/cpu0/topology/thread_siblings_list</code>: Holds information about thread siblings for given CPU core (<code>cpu0</code> in this case). Use it to find out which thread is mapped to which core.</li> -</ul> +<p>There are various environment variables available within OpenMP (some specific to GCC) that hold across compilers to specify binding of threads to cores. See, for instance, the <a href="https://www.openmp.org/spec-html/5.0/openmpse53.html">OMP_PLACES environment Variable</a>. We also have a GNU specific variable which can also be used to control affinity - <code>GOMP_CPU_AFFINITY</code>. Setting <code>GOMP_CPU_AFFINITY</code> is specific to GCC binaries but it internally serves the same function as setting <code>OMP_PLACES</code>.</p> +<p><strong>Task</strong>: Run the application enabled with OpenMP from Part A with different binding configurations. Make sure to at least run a) binding all threads to a single core and b) binding threads to different cores.</p> +<p>Adapt the following command with your configuration – or follow along accordingly in the non-interactive version of the Notebook.</p> +<p>What's your maximum speedup?</p> + +</div> +</div> +</div> +<div class="cell border-box-sizing code_cell rendered"> +<div class="input"> +<div class="prompt input_prompt">In [ ]:</div> +<div class="inner_cell"> + <div class="input_area"> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span><span class="nb">eval</span> <span class="nv">OMP_DISPLAY_ENV</span><span class="o">=</span><span class="nb">true</span> <span class="nv">OMP_PLACES</span><span class="o">=</span><span class="s2">"{X},{Y},{Z},{A}"</span> <span class="nv">OMP_NUM_THREADS</span><span class="o">=</span><span class="m">4</span> <span class="nv">$$</span>SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d <span class="m">1000</span> <span class="m">1000</span> <span class="m">100</span> <span class="p">|</span> grep <span class="s2">"OMP_PLACES\|speedup"</span> +</pre></div> + + </div> +</div> +</div> + +</div> +<div class="cell border-box-sizing code_cell rendered"> +<div class="input"> +<div class="prompt input_prompt">In [ ]:</div> +<div class="inner_cell"> + <div class="input_area"> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span><span class="nb">eval</span> <span class="nv">OMP_DISPLAY_ENV</span><span class="o">=</span><span class="nb">true</span> <span class="nv">GOMP_CPU_AFFINITY</span><span class="o">=</span><span class="s2">"X,Y,Z,A"</span> <span class="nv">OMP_NUM_THREADS</span><span class="o">=</span><span class="m">4</span> <span class="nv">$$</span>SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d <span class="m">1000</span> <span class="m">1000</span> <span class="m">100</span> <span class="p">|</span> grep <span class="s2">"OMP_PLACES\|speedup"</span> +</pre></div> + + </div> +</div> +</div> + +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p>Great!</p> +<p>If you still have time: The same experiments can be repeated with the IBM XL compiler. +The corresponding compiler flag to enable OpenMP parallelism that needs to be used for XL is <code>-qsmp=omp</code></p> +<p><strong>Task</strong>: In the Makefile add the OpenMP flag and generate XL binaries with OpenMP and run the application with various number of threads and note the performance speedup.</p> </div> </div> @@ -13752,8 +13982,7 @@ We added <code>--bind none</code> to prevent <code>jsrun</code>, the scheduler o <div class="prompt input_prompt">In [ ]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>cat /sys/devices/system/cpu/cpu0/topology/thread_siblings_list -<span class="o">!</span>cat /sys/devices/system/cpu/cpu5/topology/thread_siblings_list +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make <span class="nv">CC</span><span class="o">=</span>xlc_r -B run </pre></div> </div> @@ -13764,9 +13993,31 @@ We added <code>--bind none</code> to prevent <code>jsrun</code>, the scheduler o <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<p>There are various environment variables available within OpenMP (and GCC) to specify binding of threads to cores. See, for instance, the <a href="https://gcc.gnu.org/onlinedocs/libgomp/Environment-Variables.html">online documentation of GCC libgomp</a>. Examples are <code>OMP_PLACES</code> or <code>GOMP_CPU_AFFINITY</code>.</p> +<p>Run the parallel application with varying numbre of threads (<code>OMP_NUM_THREADS</code>) and note the performance improvement.</p> + +</div> +</div> +</div> +<div class="cell border-box-sizing code_cell rendered"> +<div class="input"> +<div class="prompt input_prompt">In [ ]:</div> +<div class="inner_cell"> + <div class="input_area"> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span><span class="nb">eval</span> <span class="nv">OMP_NUM_THREADS</span><span class="o">=</span>N <span class="nv">$SC19_SUBMIT_CMD</span> -c ALL_CPUS --bind none ./poisson2d <span class="m">1000</span> <span class="m">1000</span> <span class="m">1000</span> +</pre></div> + + </div> +</div> +</div> + +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p>Now we repeat the exercise of using the right binding of threads for the XL binary. <code>OMP_PLACES</code> pertains to the XL binary as well as it is an OpenMP variable. <code>GOMP_CPU_AFFINITY</code> is specific to GCC binary so that cannot be used to set the binding.</p> <p><strong>Task</strong>: Run the application enabled with OpenMP from Part A with different binding configurations. Make sure to at least run a) binding all threads to a single core and b) binding threads to different cores.</p> <p>Adapt the following command with your configuration – or follow along accordingly in the non-interactive version of the Notebook.</p> +<p>We are mixing Python with Bash (<code>!</code>) here, so don't get confused (because of this, if we want to use Bash environment variables, we need to use two <code>$$</code>)</p> <p>What's your maximum speedup?</p> </div> @@ -13777,19 +14028,30 @@ We added <code>--bind none</code> to prevent <code>jsrun</code>, the scheduler o <div class="prompt input_prompt">In [ ]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span><span class="nb">eval</span> <span class="nv">OMP_DISPLAY_ENV</span><span class="o">=</span><span class="nb">true</span> <span class="nv">GOMP_CPU_AFFINITY</span><span class="o">=</span><span class="s2">"X,Y,Z,A"</span> <span class="nv">OMP_NUM_THREADS</span><span class="o">=</span><span class="m">4</span> <span class="nv">$$</span>SC18_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d <span class="m">1000</span> <span class="m">1000</span> <span class="m">100</span> <span class="p">|</span> grep <span class="s2">"OMP_PLACES\|speedup"</span> +<div class=" highlight hl-ipython3"><pre><span></span><span class="k">for</span> <span class="n">affinity</span> <span class="ow">in</span> <span class="p">[</span><span class="s2">"</span><span class="si">{X}</span><span class="s2">,</span><span class="si">{Y}</span><span class="s2">,</span><span class="si">{Z}</span><span class="s2">,</span><span class="si">{A}</span><span class="s2">"</span><span class="p">,</span> <span class="s2">"</span><span class="si">{P}</span><span class="s2">,</span><span class="si">{Q}</span><span class="s2">,</span><span class="si">{R}</span><span class="s2">,</span><span class="si">{S}</span><span class="s2">"</span><span class="p">]:</span> + <span class="nb">print</span><span class="p">(</span><span class="s2">"Affinity: </span><span class="si">{}</span><span class="s2">"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">affinity</span><span class="p">))</span> + <span class="o">!</span><span class="nb">eval</span> <span class="nv">OMP_DISPLAY_ENV</span><span class="o">=</span><span class="nb">true</span> <span class="nv">OMP_PLACES</span><span class="o">=</span><span class="nv">$affinity</span> <span class="nv">OMP_NUM_THREADS</span><span class="o">=</span><span class="m">4</span> <span class="nv">$$</span>SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d <span class="m">1000</span> <span class="m">1000</span> <span class="m">1000</span> <span class="p">|</span> grep <span class="s2">"OMP_PLACES\|speedup"</span> </pre></div> </div> </div> </div> +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p>Likewise we see a higher speedup when we bind the threads to different cores rather than to a single core. This handson illustrates that apart from compiler level tuning, system level tuning is also equally important to obtain performance improvements</p> + +</div> +</div> </div> <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> <h4 id="References">References<a class="anchor-link" href="#References">¶</a></h4><ol> <li><a href="https://gcc.gnu.org/onlinedocs/libgomp/GOMP_005fCPU_005fAFFINITY.html">https://gcc.gnu.org/onlinedocs/libgomp/GOMP_005fCPU_005fAFFINITY.html</a></li> +<li><a href="https://www.openmp.org/spec-html/5.0/openmpse53.html">https://www.openmp.org/spec-html/5.0/openmpse53.html</a></li> </ol> </div> @@ -13807,7 +14069,7 @@ We added <code>--bind none</code> to prevent <code>jsrun</code>, the scheduler o <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<h1 id="Survey">Survey<a name="survey" /><a class="anchor-link" href="#Survey">¶</a></h1><p>Please rememeber to take some time and fill out the <a href="http://bit.ly/sc18-eval">survey</a>.</p> +<h1 id="Survey">Survey<a name="survey" /><a class="anchor-link" href="#Survey">¶</a></h1><p>Please rememeber to take some time and fill out the <a href="http://bit.ly/sc19-eval">survey</a>.</p> </div> </div> diff --git a/3-Optimizing_POWER/Handson/HandsOnPerformanceOptimization.ipynb b/3-Optimizing_POWER/Handson/HandsOnPerformanceOptimization.ipynb index a28d887..64fdf69 100644 --- a/3-Optimizing_POWER/Handson/HandsOnPerformanceOptimization.ipynb +++ b/3-Optimizing_POWER/Handson/HandsOnPerformanceOptimization.ipynb @@ -5,7 +5,7 @@ "metadata": {}, "source": [ "# Hands-On Performance Optimization\n", - "_Supercomputing 2018 Tutorial \"Application Porting and Optimization on GPU-Accelerated POWER Architectures\", November 12th 2018_\n", + "_Supercomputing 2019 Tutorial \"Application Porting and Optimization on GPU-Accelerated POWER Architectures\", November 18th 2019_\n", "\n", "---" ] @@ -22,7 +22,7 @@ "\n", "You will always find links to a file browser of the corresponding task subdirectory as well as direct links to the source files you will need to edit as well as the profiling output you need to open locally.\n", "\n", - "If you want you also can get a [terminal](/terminals/1) in your browser.\n", + "If you want you also can get a terminal in your browser; just open it via the »New Launcher« button (`+`).\n", "\n", "## Terminal fallback\n", "\n", @@ -37,7 +37,16 @@ "source": [ "## Setup\n", "\n", - "This hands-on session requires of GCC 6.4.0. By loading the `sc18/handson2` module before invoking this Notebook, we took care of also loading GCC 6.4.0 into the environment." + "We are using some very fresh compiler features and use GCC 9.2.0 because of that. It should already be in your environment. Let's check!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!gcc --version" ] }, { @@ -51,14 +60,16 @@ "Please choose from the task below.\n", "\n", "\n", - "* [Task 1](#task1): Compile Flags \n", - "Improve performance of the CPU Jacobi solver with compiler flags such as `Ofast` and profile-directed feedback ([Solution 1](#solution0))\n", + "* [Task 1](#task1): __Basic compiler optimization flags and compiler annotations__\n", "\n", - "* [Task 2](#task2): Software Prefetching \n", - "Improve performance of the CPU Jacobi solver with software prefetching ([Solution 2](#solution1))\n", + "Improve performance of the CPU Jacobi solver with compiler flags such as `Ofast` and profile-directed feedback. Learn about compiler annotations.\n", "\n", - "* [Task 3](#task3): OpenMP \n", - "Parallelize the CPU Jacobi solver and determine the right binding to be used for optimal performance ([Solution 3](#solution2))\n", + "* [Task 2](#task2): __Optimization via Prefetching controlled by compiler__\n", + "\n", + "Improve performance of the CPU Jacobi solver with software prefetching. Some compilers such as IBM XL define flags that can be used to modify the aggressiveness of the hardware prefetcher. Learn to modify the DSCR value through XL and study the impact on application performance. \n", + "* [Task 3](#task3): __Optimization via OpenMP controlled by compiler and the system__\n", + "\n", + "Parallelize the CPU Jacobi solver and determine the right binding to be used for optimal performance. \n", " \n", "* [Suvery](#survey) Please remember to take the survey !\n", " \n", @@ -85,7 +96,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Task 1: Compile Flags <a name=\"task1\"></a>\n", + "## Task 1: Basic compiler optimization flags and compiler annotations <a name=\"task1\"></a>\n", "\n", "\n", "### Overview\n", @@ -95,7 +106,10 @@ "Your task is to:\n", "\n", "* Optimize performance with `-Ofast` flag\n", + "* Verify the cause for performance improvement by viewing perf profiles of O3 and Ofast binaries \n", "* Optimize performance with profile directed feedback \n", + "* Generate compiler annotations/remarks to understand the optimizations done by the compiler with and without profile directed feedback \n", + "\n", "\n", "First, change the working directory to `Task1`." ] @@ -115,7 +129,9 @@ "source": [ "### Part A: `-Ofast` vs. `-O3`\n", "\n", - "We are to compare the performance of the binary being compiled with `-Ofast` optimization and with `-O3` optimization. Right now, the Makefile specifies `-O3` as the optimization flag. Compile the code using `make` and run it with `make run` in the next two cells." + "We are to compare the performance of the binary being compiled with `-Ofast` optimization and with `-O3` optimization. As in the previous task, we use a `Makefile` for compilation. The `Makefile` targets `poisson2d_O3` and `poisson2d_Ofast` are already prepared. \n", + "\n", + "**TASK**: Add `-O3` as the optimization flag for the `poisson2d_O3` target by using the corresponding `CFLAGS` definition. There are notes relating to this Task 1 in the header of the `Makefile`. Compile the code using `make` as indicated below and run with the `Make` targets `run`, `run_perf` and `run_perf_recrep`. " ] }, { @@ -124,7 +140,7 @@ "metadata": {}, "outputs": [], "source": [ - "!make" + "!make poisson2d_O3" ] }, { @@ -140,7 +156,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "You can use the GNU _perf_ tool to profile the application using the `perf` command (see below) and see the top time-consuming functions." + "Let's have a look at the output of the `Makefile` target `run_perf`. It invokes the GNU _perf_ tool to print out details of the number of instructions executed and the number of cycles taken by POWER9 to execute the program. Feel free to add further counter to this call to _perf_." ] }, { @@ -149,17 +165,14 @@ "metadata": {}, "outputs": [], "source": [ - "# perf record creates a perf.data file \n", - "!perf record -o perf.O3.data -e cycles ./poisson2d\n", - "# perf report opens the perf.data file \n", - "!perf report -i perf.O3.data | cat" + "!make run_perf" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "**TASK**: Now change the optimization flag in the [Makefile](/edit/Task1/Makefile) to `-Ofast` and repeat the steps in the following cell. In case you follow along non-interactive, call `make` and `make run` in your shell. (If you are in the Jupyter Notebook, you can actually click the link of the [Makefile](/edit/Task1/Makefile). In other cases, use `vim` which is installed on Ascent.)" + "Next we run the makefile with target `run_perf_recrep` that prints the top routines of the application in terms of hotness by using a combination of `perf record ./app` and `perf report`. " ] }, { @@ -168,7 +181,17 @@ "metadata": {}, "outputs": [], "source": [ - "!make" + "# run_perf_recrep displays the top hot routines \n", + "!make run_perf_recrep" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**TASK**: Now add the optimization flag `Ofast` to the `CFLAGS` for target `poisson2d_Ofast`. Compile the program with the target `poisson2d_Ofast` and run and analyse it as before with `run`, `run_perf` and `run_perf_recrep`.\n", + "\n", + "What difference do you see?" ] }, { @@ -177,19 +200,40 @@ "metadata": {}, "outputs": [], "source": [ + "!make poisson2d_Ofast \n", "!make run" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Again, run a `perf`-instrumented version:" + ] + }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "# perf record creates a perf.data file \n", - "!perf record -o perf.Ofast.data -e cycles ./poisson2d\n", - "# perf report opens the perf.data file \n", - "!perf report -i perf.Ofast.data | cat" + "!make run_perf" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Generate the list of top routines in terms of hotness:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!make run_perf_recrep" ] }, { @@ -205,7 +249,7 @@ "source": [ "#### Interpretation\n", "\n", - "Depending on the application requirement, if a high precision of results is not mandatory, the users can compile an application with `-Ofast` which enables `–ffast-math` option that implements the same math function in a relaxed manner very similar to how general mathematical expressions are implemented and avoids the overhead of calling a function from the math library. Comparing the files, you will see that the `-Ofast` binary natively implements the `fmax` function using instructions available in the hardware. The `-O3` binary makes a library call to compute `fmax` to follow a stricter _IEEE_ requirement for accuracy." + "Depending on the application requirement, if a high precision of results is not mandatory, one can compile an application with `-Ofast` which enables `–ffast-math` option that implements the same math function in a relaxed manner very similar to how general mathematical expressions are implemented and avoids the overhead of calling a function from the math library. Comparing the files, you will see that the `-Ofast` binary natively implements the `fmax` function using instructions available in the hardware. The `-O3` binary makes a library call to compute `fmax` to follow a stricter _IEEE_ requirement for accuracy." ] }, { @@ -214,20 +258,34 @@ "source": [ "### Part B: Profile-directed Feedback\n", "\n", - "For the first level of optimization we saw `Ofast` cut the execution time of the `O3` binary by almost half.\n", + "For the first level of optimization we see that `Ofast` cut the execution time of the `O3` binary by almost half.\n", + "\n", + "We can optimize the performance further by using profile-directed feedback optimization.\n", + "\n", + "To compile using profile-directed feedback with the GCC compiler we need to build the appplication in three stages:\n", + "\n", + "1. Instrument binary;\n", + "2. Run binary with training, gather profile information;\n", + "3. Use profile information to generate optimized binary.\n", + "\n", + "\n", + "Step 1 is achieved by compiling the binary with the correct flag – `-fprofile-generate`. In our case, we need to specify an output location, which should be `$(SC19_DIR_SCRATCH)`.\n", + "\n", + "Step 2 consists of a usual, albeit shorter run of the instrumented binary. The can be very short, though the parameters need to be representative of the actual run. After the binary ran, an output file (with file extension `.gcda`) is written to the directory specified during compilation.\n", "\n", - "We can optimize the performance further by using profile directed feedback optimization.\n", + "For Step 3, the binary is once again compiled, but this time using the `gcda` profile just generated. The according flag is `-fprofile-use`, which we set to `$(SC19_DIR_SCRATCH)` as well.\n", "\n", - "To compile using profile directed feedback with the GCC compiler we need to do the following steps\n", + "In our `Makefile` at hand, we prepared the steps already for you in the form of two targets.\n", "\n", - "1. We need to first build a training binary using `-fprofile-generate`; this instructs the compiler to record hot path information \n", - "2. Run the training binary with a smaller input size; you should see a `.gcda` file generated which stores hot path information for further optimization by the compiler \n", - "3. build the final binary using `-fprofile-use` which uses the profile information in the `.gcda` file \n", - "4. Compare the performance of the final binary with the original `Ofast` binary \n", + "* `poisson2d_train`: Will compile the binary with profile-directed feedback\n", + "* `poisson2d_ref`: Will take a generated profile and compile a new, optimized binary\n", "\n", - "**TASK**: First, search for `TODO1` in the [Makefile](/edit/Task1/Makefile). It defines an additional compilation flag for `gcc`. Insert `-fprofile-generate=FOLDER` there with FOLDER pointing to `$$SC18_DIR_SCRATCH`, your personal write-directory (the double dollar signs are intentional as they are used to escape in the GNU Make syntax).\n", + "By using dependencies, between these two targets a profile run is launched.\n", "\n", - "After editing, run the following two cells to train your program." + "**TASK**: Edit the [Makefile](`Makefile`) and add the `-fprofile-*` flags to the `CFLAGS` of `poisson2d_train` and\n", + "`poisson2d_ref` as outline in the file.\n", + "\n", + "After that, you may launch them with the following cells (`gen_profile` is a meta-target and uses `poisson2d_train` and `poisson2d_ref`). If you need to clean the generated profile, you may use `make clean_profile`." ] }, { @@ -236,7 +294,14 @@ "metadata": {}, "outputs": [], "source": [ - "!make poisson2d_train" + "!make gen_profile" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If the previous cell executed correctly, you now have your optimized executable. Let's see if it even fast than before!" ] }, { @@ -245,18 +310,43 @@ "metadata": {}, "outputs": [], "source": [ - "!make run_train" + "!make run" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Now, a `.gcda` file exists in the directory which can be used for an profile-accelerated subsequent run.\n", + "Let's also measure instructions and cycles" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!make run_perf" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "What is your speed-up? Feel free to run with larger problem sizes (mesh; iterations)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Part C: Compiler annotations/Remarks\n", "\n", - "**TASK**: Edit the [Makefile](/edit/Task1/Makefile) again, this time modifying `TODO2` to be equivalent to `-fprofile-use`. A directory is not needed as we copied the gcda file into the current directory.\n", + "Usually, all compilers provide an option to emit annotations or remarks by the compiler. These remarks summarize the optimizations done in detail, the location in source where these optimizations were done. There exist options that also indicate optimizations that were missed and the reason why they could not be done. \n", "\n", - "Run the following cells in order to build using the newly added flag and then run with the profile-accelerated version." + "To generate compiler annotations using GCC, one uses `-fopt-info-all`. If you only want to see the missed options, use the option `-fopt-info-missed` instead of `-fopt-info-all`. See also the [documentation of GCC regarding the flag](https://gcc.gnu.org/onlinedocs/gcc/Developer-Options.html#index-fopt-info).\n", + "\n", + "**TASK**: Have a looK at the `CFLAGS` of the `Makefile` target `poisson2d_Ofast_info`. Add the flag `-fopt-info-all` to the list of flags. This will print optimisation information to stdout. If you rather want to print to this information to a file, use – for example – `-fopt-info-all=(SC19_DIR_SCRATCH)/filename`." ] }, { @@ -265,7 +355,17 @@ "metadata": {}, "outputs": [], "source": [ - "!make poisson2d_profile" + "!make poisson2d_Ofast_info" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's compare this with the output during compilation when using profile-directed feedback from Task 1 B.\n", + "\n", + "**TASK**: \n", + "Adapt the `CFLAGS` of `poisson2d_ref_info` to include `-fopt-info-all` **and** the profile input of `-fprofile-use=…` here. *(Be advised: Long output!)*" ] }, { @@ -274,14 +374,21 @@ "metadata": {}, "outputs": [], "source": [ - "!make run_profile" + "!make poisson2d_ref_info" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "What is your speed-up? Feel free to run with larger problem sizes (mesh; iterations)" + "Comparing the annotations generated of a plain `-Ofast` optimization level and the one generated at `-Ofast` and profile directed feedback, we observe that many more optimizations are possible due to profile information.\n", + "\n", + "For instance you will see annotations such as\n", + "```\n", + "poisson2d.c:114:25: optimized: loop unrolled 3 times (header execution count 436550)\n", + "```\n", + "\n", + "The execution count indicates the dynamic execution count of the node at runtime. This information determines which paths are hotter and subsequently facilitate additional optimizations." ] }, { @@ -307,14 +414,16 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Task 2:<a name=\"task2\"></a> Software Pretechting\n", + "## Task 2:<a name=\"task2\"></a> Impact of Prefetching on Performance\n", "\n", "\n", "### Overview\n", "\n", - "Study the difference of program execution time of different optimization levels with and without software prefetching.\n", + "* Study the difference of program execution time of different optimization levels with and without software prefetching.\n", + "* Verify the impact by measuring cache counters with and without prefetching.\n", + "* Learn how to modify contents of DSCR (*Data Stream Control Register*) using IBM XL compiler and study the impact with different values to DSCR. \n", "\n", - "First, change directory to that of Task 2" + "But first, lets change directory to that of Task 2" ] }, { @@ -330,16 +439,26 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Part A: Running" + "### Part A: Software Prefetching" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Look at the [Makefile](/edit/Task2/Makefile) and work on the TODOs. Please implement compile flags as mentioned in the Makefile target name.\n", + "**TASK**: Look at the Makefile and work on the TODOs. \n", "\n", - "Afterwards, compile each target with the following cells and submit them to the batch system. Follow along accordingly in the non-interactive version of this Notebook." + "- First generate a `-Ofast`-optimised binary and note down the performance in terms of cycles, seconds, and L3 misses. This is our baseline!\n", + "- Modify the `Makefile` to add the option for software prefetching (`-fprefetch-loop-arrays`). Compare performance of `-Ofast` with and without software prefetching" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!make clean" ] }, { @@ -348,7 +467,9 @@ "metadata": {}, "outputs": [], "source": [ - "!make run_o3_pref" + "!make poisson2d CC=gcc\n", + "!make run\n", + "!make l3missstats" ] }, { @@ -357,7 +478,16 @@ "metadata": {}, "outputs": [], "source": [ - "!make run_ofast_pref" + "!make poisson2d_pref CC=gcc\n", + "!make run\n", + "!make l3missstats" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**TASK**: Repeat the experiment with the `-O3` flag. Have a look at the `Makefile` and the outlined TODO. There's a position to easily adapt `-Ofast`→`-O3`!" ] }, { @@ -366,7 +496,9 @@ "metadata": {}, "outputs": [], "source": [ - "!make run_o3_nopref" + "!make poisson2d CC=gcc -B\n", + "!make run\n", + "!make l3missstats" ] }, { @@ -375,14 +507,16 @@ "metadata": {}, "outputs": [], "source": [ - "!make run_ofast_nopref" + "!make poisson2d_pref CC=gcc -B\n", + "!make run\n", + "!make l3missstats" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Do you notice the impact difference with optimization levels? It's always important to carefully study the interplay of flags." + "Do you notice the impact difference with optimization levels? At what optimization level does software prefetching help the most?" ] }, { @@ -391,27 +525,86 @@ "source": [ "### Part B: Analysis of Instructions\n", "\n", - "Compilation with the software prefetching flag causes the compiler to generate the `__dcbt` and `__dcbtst` instructions that prefetch memory values to L3.\n", + "Compilation of the `-Ofast` binary with the software prefetching flag causes the compiler to generate the `dcb*` instructions that prefetch memory values to L3." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**TASK**: \n", + "Run `$(SC19_SUBMIT_CMD) objdump -lSd` on each binary file (`-O3`, `-Ofast` with prefetch/no prefetch).\n", + "Look for instructions beginning with `dcb`\n", + "At what optimization levels does the compiler generate software prefetching instructions?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!make CC=gcc -B poisson2d_pref\n", + "!objdump -lSd ./poisson2d_pref > poisson2d.dis" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!grep dcb poisson2d.dis" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Part C: Changing Values of DSCR via compiler flags\n", + "\n", + "This task requires using the IBM XL compiler. It should be already in your environment.\n", + "\n", + "\n", + "We saw the impact of software prefetching in the previous subsection. \n", + "In certain cases, tuning the hardware prefetcher through compiler options can also help improve performance. \n", + "In this exercise we shall see some compiler options that can be used to modify the DSCR value which controls aggressiveness of prefetching. It can be also used to turn off hardware prefetching. \n", + "\n", + "IBM XL compiler has an option `-qprefetch=dscr=<val>` that can be used for this purpose.\n", + "Compiling with `-qprefetch=dscr=1` turns off the prefetcher. One can give various values such as `-qprefetch=dscr=4`, `-qprefetch=dscr=7` etc. to control aggressiveness of prefetching.\n", + "\n", + "For this exercise we use `make CC=xlc_r` to illustrate the performance impact.\n", + " \n", + "\n", + "**Task** Generate a XL-compiled binary by compiling using the following cells. After you've generated a baseline, start editing the `Makefile`: Add `qprefetch=dscr=1` to the `CFLAGS` and rebuild the application and note the performance. Which one is faster? \n", "\n", - "Verify it using `objdump -lSd` on each file (`poisson2d_o3_pref`, `poisson2d_ofast_pref`, `poisson2d_o3_nopref`, `poisson2d_ofast_nopref`). You might want to grep for `dcb`." + "In general, applications benefit with the default settings of hardware DSCR register (`-qprefetch=dscr=0`). However, certain applications also benefit with prefetching turned off. \n", + "\n", + "It is to be noted that DSCR values are highly sensitive to the application. One value that works well for Application A may not help Application B. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Measure performance of the application compiled with XL at default DSCR value" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "sc18": "task" - }, + "metadata": {}, "outputs": [], "source": [ - "#!objdump -l…" + "!make CC=xlc_r -B poisson2d\n", + "!make run" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "If you feel up to the task, you can study the number of L3 cache misses using the corresponding performance counter, `PM_L3_MISS`. Either use your knowledge from Hands-On 1, or use the following call to `perf`, in which we already converted the named counter to a raw counter address." + "Measure performance of the application compiled with XL with DSCR value turned off" ] }, { @@ -420,8 +613,15 @@ "metadata": {}, "outputs": [], "source": [ - "for f in [\"poisson2d_ofast_nopref\", \"poisson2d_ofast_pref\"]:\n", - " !eval $$SC18_SUBMIT_CMD perf stat -e cycles,r168a4 ./$f\n" + "!make poisson2d_dscr CC=xlc_r -B\n", + "!make run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Does Hardware prefetcher help this application? How much impact do you see when you turn off the hardware prefetcher? " ] }, { @@ -431,7 +631,8 @@ "#### References\n", "\n", "1. https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html\n", - "2. https://www.gnu.org/software/gcc/projects/prefetch.html" + "2. https://www.gnu.org/software/gcc/projects/prefetch.html\n", + "3. https://openpowerfoundation.org/?resource_lib=power-isa-version-3-0\n" ] }, { @@ -453,20 +654,14 @@ "\n", "### Overview\n", "\n", - "We add OpenMP shared-memory parallelism to the application. Also, we study the effect of binding the multi-thread processes to certain cores.\n", - "\n", - "First, we need to change directory to that of Task3." + "We add OpenMP shared-memory parallelism to the application. Also, we study the effect of binding the multi-thread processes to certain cores on the resulting application performance. We do this study for both GCC and XL compilers inorder to learn about the appropriate options that need to be used.\n", + "First, we need to change directory to that of Task3. For Task 3 we modify poisson2d.c to invoke an exact copy of the main jacobi loop which is `poisson2d_reference`. We parallelize only the main loop but not `poisson2d_reference`. The speedup is the performance gain seen in the main loop as compared to the reference loop." ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "ExecuteTime": { - "end_time": "2018-11-07T13:47:57.724441Z", - "start_time": "2018-11-07T13:47:57.718745Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "%cd ../Task3" @@ -478,14 +673,12 @@ "source": [ "### Part A: Implement OpenMP Pragmas; Compilation\n", "\n", - "**Task**: Please add the correct OpenMP pragmas to the source code and compilations flags to enable OpenMP.\n", - "\n", - "* **pragmas**: Look at the TODOs in [`poisson2d.c`](/edit/Task3/poisson2d.c) to add OpenMP parallelism. The pragmas in question are `#pragma omp parallel for`\n", - "* **Compilation**: Please add compilation flags enabling OpenMP in GCC to the [Makefile](/edit/Task3/Makefile). The flag in question is `-fopenmp`.\n", + "**Task**: Please add the correct OpenMP directives to poisson2d.c and compilations flags in the Makefile to enable OpenMP with GCC and XL compilers.\n", "\n", - "Edit the files with the links above if you are running the interactive version of the Notebook or navigate to `poisson2d.c` and `Makefile` yourself in case you run the non-interactive version.\n", + "* **Directives**: Look at the TODOs in [`poisson2d.c`](poisson2d.c) to add OpenMP parallelism. The pragmas in question are `#pragma omp parallel for` (and once it's `#pragma omp parallel for reduction(max:error)` – can you guess where?)\n", + "* **Compilation**: Please add compilation flags enabling OpenMP in GCC and XL to the `Makefile`. For GCC, we need to add `-fopenmp` and the application needs to be linked with `-lgomp`. For XL, we need to add `-qsmp=omp` to the list of compilation flags. \n", "\n", - "Afterwards, compile and run the application with the following cells. Non-interactive: Follow along accordingly in the shell." + "Afterwards, compile and run the application with the following commands." ] }, { @@ -494,23 +687,14 @@ "metadata": {}, "outputs": [], "source": [ - "!make poisson2d" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!make run" + "!make poisson2d CC=gcc" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The command to submit a job to the batch system is prepared in an environment variable `$SC18_SUBMIT_CMD`; use it together with `eval`. In the following cell, it is shown how to increase the work of the application." + "The command to submit a job to the batch system is prepared in an environment variable `$SC19_SUBMIT_CMD`; use it together with `eval`. In the following cell, it is shown how to invoke the application using the batch system. " ] }, { @@ -519,13 +703,14 @@ "metadata": {}, "outputs": [], "source": [ - "!eval $SC18_SUBMIT_CMD ./poisson2d 1000 1000" + "!eval $SC19_SUBMIT_CMD ./poisson2d 1000 1000 1000" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ + "Inorder to run the parallel application, we need to set the number of threads using `OMP_NUM_THREADS`\n", "What is the best performance you can reach by setting the number of threads via `OMP_NUM_THREADS=N` with `N` being the number of threads? Feel free to play around with the command in the following cell, using 1 thread as an example. \n", "We added `--bind none` to prevent `jsrun`, the scheduler of Ascent, from overlaying binding options. Also, we use `-c ALL_CPUS` to make all CPUs on the compute nodes available to you." ] @@ -534,11 +719,11 @@ "cell_type": "code", "execution_count": null, "metadata": { - "sc18": "task" + "exercise": "task" }, "outputs": [], "source": [ - "!eval OMP_NUM_THREADS=1 $SC18_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000" + "!eval OMP_NUM_THREADS=N $SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000" ] }, { @@ -563,7 +748,7 @@ "metadata": {}, "outputs": [], "source": [ - "!eval $SC18_SUBMIT_CMD ppc64_cpu --smt" + "!eval $SC19_SUBMIT_CMD ppc64_cpu --smt" ] }, { @@ -582,15 +767,15 @@ "metadata": {}, "outputs": [], "source": [ - "!cat /sys/devices/system/cpu/cpu0/topology/thread_siblings_list\n", - "!cat /sys/devices/system/cpu/cpu5/topology/thread_siblings_list" + "!$$SC19_SUBMIT_CMD cat /sys/devices/system/cpu/cpu0/topology/thread_siblings_list\n", + "!$$SC19_SUBMIT_CMD cat /sys/devices/system/cpu/cpu5/topology/thread_siblings_list" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "There are various environment variables available within OpenMP (and GCC) to specify binding of threads to cores. See, for instance, the [online documentation of GCC libgomp](https://gcc.gnu.org/onlinedocs/libgomp/Environment-Variables.html). Examples are `OMP_PLACES` or `GOMP_CPU_AFFINITY`.\n", + "There are various environment variables available within OpenMP (some specific to GCC) that hold across compilers to specify binding of threads to cores. See, for instance, the [OMP_PLACES environment Variable](https://www.openmp.org/spec-html/5.0/openmpse53.html). We also have a GNU specific variable which can also be used to control affinity - `GOMP_CPU_AFFINITY`. Setting `GOMP_CPU_AFFINITY` is specific to GCC binaries but it internally serves the same function as setting `OMP_PLACES`. \n", "\n", "**Task**: Run the application enabled with OpenMP from Part A with different binding configurations. Make sure to at least run a) binding all threads to a single core and b) binding threads to different cores.\n", "\n", @@ -603,11 +788,96 @@ "cell_type": "code", "execution_count": null, "metadata": { - "sc18": "task" + "exercise": "task" + }, + "outputs": [], + "source": [ + "!eval OMP_DISPLAY_ENV=true OMP_PLACES=\"{X},{Y},{Z},{A}\" OMP_NUM_THREADS=4 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 100 | grep \"OMP_PLACES\\|speedup\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "exercise": "task" }, "outputs": [], "source": [ - "!eval OMP_DISPLAY_ENV=true GOMP_CPU_AFFINITY=\"X,Y,Z,A\" OMP_NUM_THREADS=4 $$SC18_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 100 | grep \"OMP_PLACES\\|speedup\"" + "!eval OMP_DISPLAY_ENV=true GOMP_CPU_AFFINITY=\"X,Y,Z,A\" OMP_NUM_THREADS=4 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 100 | grep \"OMP_PLACES\\|speedup\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Great!\n", + "\n", + "If you still have time: The same experiments can be repeated with the IBM XL compiler. \n", + "The corresponding compiler flag to enable OpenMP parallelism that needs to be used for XL is `-qsmp=omp`\n", + "\n", + "**Task**: In the Makefile add the OpenMP flag and generate XL binaries with OpenMP and run the application with various number of threads and note the performance speedup." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!make CC=xlc_r -B run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Run the parallel application with varying numbre of threads (`OMP_NUM_THREADS`) and note the performance improvement. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "exercise": "task" + }, + "outputs": [], + "source": [ + "!eval OMP_NUM_THREADS=N $SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we repeat the exercise of using the right binding of threads for the XL binary. `OMP_PLACES` pertains to the XL binary as well as it is an OpenMP variable. `GOMP_CPU_AFFINITY` is specific to GCC binary so that cannot be used to set the binding.\n", + "\n", + "**Task**: Run the application enabled with OpenMP from Part A with different binding configurations. Make sure to at least run a) binding all threads to a single core and b) binding threads to different cores.\n", + "\n", + "Adapt the following command with your configuration – or follow along accordingly in the non-interactive version of the Notebook.\n", + "\n", + "We are mixing Python with Bash (`!`) here, so don't get confused (because of this, if we want to use Bash environment variables, we need to use two `$$`)\n", + "\n", + "What's your maximum speedup?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "exercise": "task" + }, + "outputs": [], + "source": [ + "for affinity in [\"{X},{Y},{Z},{A}\", \"{P},{Q},{R},{S}\"]:\n", + " print(\"Affinity: {}\".format(affinity))\n", + " !eval OMP_DISPLAY_ENV=true OMP_PLACES=$affinity OMP_NUM_THREADS=4 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep \"OMP_PLACES\\|speedup\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Likewise we see a higher speedup when we bind the threads to different cores rather than to a single core. This handson illustrates that apart from compiler level tuning, system level tuning is also equally important to obtain performance improvements \n" ] }, { @@ -615,7 +885,8 @@ "metadata": {}, "source": [ "#### References\n", - "1. https://gcc.gnu.org/onlinedocs/libgomp/GOMP_005fCPU_005fAFFINITY.html" + "1. https://gcc.gnu.org/onlinedocs/libgomp/GOMP_005fCPU_005fAFFINITY.html\n", + "2. https://www.openmp.org/spec-html/5.0/openmpse53.html" ] }, { @@ -633,7 +904,7 @@ "source": [ "# Survey<a name=\"survey\"></a>\n", "\n", - "Please rememeber to take some time and fill out the [survey](http://bit.ly/sc18-eval)." + "Please rememeber to take some time and fill out the [survey](http://bit.ly/sc19-eval)." ] } ], @@ -653,7 +924,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.7" + "version": "3.7.0" } }, "nbformat": 4, diff --git a/3-Optimizing_POWER/Handson/HandsOnPerformanceOptimization.pdf b/3-Optimizing_POWER/Handson/HandsOnPerformanceOptimization.pdf index 00d2d949244f7bf9ddf9aca6f4e1db96b1adf874..c441f9df6b2b229cc5a4f31e855c472c5a6e884b 100644 GIT binary patch literal 85793 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 LQdM>JcjE#8G+WX3 literal 54013 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 LE>%@me>W}wHi$0n diff --git a/3-Optimizing_POWER/Handson/Solution-Notebook/HandsOnPerformanceOptimization.html b/3-Optimizing_POWER/Handson/Solution-Notebook/HandsOnPerformanceOptimization.html index 8847eff..639b1d1 100644 --- a/3-Optimizing_POWER/Handson/Solution-Notebook/HandsOnPerformanceOptimization.html +++ b/3-Optimizing_POWER/Handson/Solution-Notebook/HandsOnPerformanceOptimization.html @@ -13017,45 +13017,6 @@ ul.typeahead-list > li > a.pull-right { .highlight .vm { color: #19177C } /* Name.Variable.Magic */ .highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ </style> -<style type="text/css"> - -/* Temporary definitions which will become obsolete with Notebook release 5.0 */ -.ansi-black-fg { color: #3E424D; } -.ansi-black-bg { background-color: #3E424D; } -.ansi-black-intense-fg { color: #282C36; } -.ansi-black-intense-bg { background-color: #282C36; } -.ansi-red-fg { color: #E75C58; } -.ansi-red-bg { background-color: #E75C58; } -.ansi-red-intense-fg { color: #B22B31; } -.ansi-red-intense-bg { background-color: #B22B31; } -.ansi-green-fg { color: #00A250; } -.ansi-green-bg { background-color: #00A250; } -.ansi-green-intense-fg { color: #007427; } -.ansi-green-intense-bg { background-color: #007427; } -.ansi-yellow-fg { color: #DDB62B; } -.ansi-yellow-bg { background-color: #DDB62B; } -.ansi-yellow-intense-fg { color: #B27D12; } -.ansi-yellow-intense-bg { background-color: #B27D12; } -.ansi-blue-fg { color: #208FFB; } -.ansi-blue-bg { background-color: #208FFB; } -.ansi-blue-intense-fg { color: #0065CA; } -.ansi-blue-intense-bg { background-color: #0065CA; } -.ansi-magenta-fg { color: #D160C4; } -.ansi-magenta-bg { background-color: #D160C4; } -.ansi-magenta-intense-fg { color: #A03196; } -.ansi-magenta-intense-bg { background-color: #A03196; } -.ansi-cyan-fg { color: #60C6C8; } -.ansi-cyan-bg { background-color: #60C6C8; } -.ansi-cyan-intense-fg { color: #258F8F; } -.ansi-cyan-intense-bg { background-color: #258F8F; } -.ansi-white-fg { color: #C5C1B4; } -.ansi-white-bg { background-color: #C5C1B4; } -.ansi-white-intense-fg { color: #A1A6B2; } -.ansi-white-intense-bg { background-color: #A1A6B2; } - -.ansi-bold { font-weight: bold; } - - </style> <style type="text/css"> @@ -13089,7 +13050,7 @@ div#notebook { <!-- Loading mathjax macro --> <!-- Load mathjax --> - <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS_HTML"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/latest.js?config=TeX-AMS_HTML"></script> <!-- MathJax configuration --> <script type="text/x-mathjax-config"> MathJax.Hub.Config({ @@ -13116,7 +13077,7 @@ div#notebook { <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<h1 id="Hands-On-Performance-Optimization">Hands-On Performance Optimization<a class="anchor-link" href="#Hands-On-Performance-Optimization">¶</a></h1><p><em>Supercomputing 2018 Tutorial "Application Porting and Optimization on GPU-Accelerated POWER Architectures", November 12th 2018</em></p> +<h1 id="Hands-On-Performance-Optimization">Hands-On Performance Optimization<a class="anchor-link" href="#Hands-On-Performance-Optimization">¶</a></h1><p><em>Supercomputing 2019 Tutorial "Application Porting and Optimization on GPU-Accelerated POWER Architectures", November 18th 2019</em></p> <hr> </div> @@ -13128,7 +13089,7 @@ div#notebook { <p>As for the first task of this tutorial, also this task is primarily designed to be executed as an interactive Jupyter Notebook. However, everything can also be done using an SSH connection to Ascent (or any other POWER9 computer) in your terminal.</p> <h2 id="Jupyter-notebook-execution">Jupyter notebook execution<a class="anchor-link" href="#Jupyter-notebook-execution">¶</a></h2><p>When using Jupyter, this Notebook will guide you through the steps. Note that if you execute a cell multiple times while optimizng the code the output will be replaced. You can however duplicate the cell you want to execute and keep its output. Check the <em>edit</em> menu above.</p> <p>You will always find links to a file browser of the corresponding task subdirectory as well as direct links to the source files you will need to edit as well as the profiling output you need to open locally.</p> -<p>If you want you also can get a <a href="/terminals/1">terminal</a> in your browser.</p> +<p>If you want you also can get a terminal in your browser; just open it via the »New Launcher« button (<code>+</code>).</p> <h2 id="Terminal-fallback">Terminal fallback<a class="anchor-link" href="#Terminal-fallback">¶</a></h2><p>The tasks are place in directories named <code>Task[1-3]</code>.</p> <p>Makefile targets are created to cover everything, from compile, to run and profile. Please take a look at the cells containing the make calls as a guide also for the non-interactive version of this description.</p> @@ -13138,10 +13099,45 @@ div#notebook { <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<h2 id="Setup">Setup<a class="anchor-link" href="#Setup">¶</a></h2><p>This hands-on session requires of GCC 6.4.0. By loading the <code>sc18/handson2</code> module before invoking this Notebook, we took care of also loading GCC 6.4.0 into the environment.</p> +<h2 id="Setup">Setup<a class="anchor-link" href="#Setup">¶</a></h2><p>We are using some very fresh compiler features and use GCC 9.2.0 because of that. It should already be in your environment. Let's check!</p> + +</div> +</div> +</div> +<div class="cell border-box-sizing code_cell rendered"> +<div class="input"> +<div class="prompt input_prompt">In [1]:</div> +<div class="inner_cell"> + <div class="input_area"> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>gcc --version +</pre></div> + + </div> +</div> +</div> + +<div class="output_wrapper"> +<div class="output"> + + +<div class="output_area"> + + <div class="prompt"></div> + + +<div class="output_subarea output_stream output_stdout output_text"> +<pre>gcc (GCC) 9.2.0 +Copyright (C) 2019 Free Software Foundation, Inc. +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +</pre> +</div> +</div> </div> </div> + </div> <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> @@ -13149,17 +13145,19 @@ div#notebook { <h2 id="Tasks">Tasks<a name="top" /><a class="anchor-link" href="#Tasks">¶</a></h2><p>This session comes with multiple tasks, each one to be found in the respective sub-directory <code>Task[1-3]</code>. In each of these directories you will also find Makefiles that are set up so that you can compile and submit all necessary tasks.</p> <p>Please choose from the task below.</p> <ul> -<li><p><a href="#task1">Task 1</a>: Compile Flags<br> -Improve performance of the CPU Jacobi solver with compiler flags such as <code>Ofast</code> and profile-directed feedback (<a href="#solution0">Solution 1</a>)</p> -</li> -<li><p><a href="#task2">Task 2</a>: Software Prefetching<br> -Improve performance of the CPU Jacobi solver with software prefetching (<a href="#solution1">Solution 2</a>)</p> -</li> -<li><p><a href="#task3">Task 3</a>: OpenMP<br> -Parallelize the CPU Jacobi solver and determine the right binding to be used for optimal performance (<a href="#solution2">Solution 3</a>)</p> -</li> -<li><p><a href="#survey">Suvery</a> Please remember to take the survey !</p> -</li> +<li><a href="#task1">Task 1</a>: <strong>Basic compiler optimization flags and compiler annotations</strong></li> +</ul> +<p>Improve performance of the CPU Jacobi solver with compiler flags such as <code>Ofast</code> and profile-directed feedback. Learn about compiler annotations.</p> +<ul> +<li><a href="#task2">Task 2</a>: <strong>Optimization via Prefetching controlled by compiler</strong></li> +</ul> +<p>Improve performance of the CPU Jacobi solver with software prefetching. Some compilers such as IBM XL define flags that can be used to modify the aggressiveness of the hardware prefetcher. Learn to modify the DSCR value through XL and study the impact on application performance.</p> +<ul> +<li><a href="#task3">Task 3</a>: <strong>Optimization via OpenMP controlled by compiler and the system</strong></li> +</ul> +<p>Parallelize the CPU Jacobi solver and determine the right binding to be used for optimal performance.</p> +<ul> +<li><a href="#survey">Suvery</a> Please remember to take the survey !</li> </ul> <h3 id="Make-Targets-">Make Targets <a name="make" /><a class="anchor-link" href="#Make-Targets-">¶</a></h3><p>For all tasks we have defined the following make targets.</p> <ul> @@ -13184,11 +13182,13 @@ build <code>poisson2d</code> binary (default)</li> <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<h2 id="Task-1:-Compile-Flags-">Task 1: Compile Flags <a name="task1" /><a class="anchor-link" href="#Task-1:-Compile-Flags-">¶</a></h2><h3 id="Overview">Overview<a class="anchor-link" href="#Overview">¶</a></h3><p>The goal of this task is to understand different options available to optimize the performance of the CPU Jacobi solver</p> +<h2 id="Task-1:-Basic-compiler-optimization-flags-and-compiler-annotations-">Task 1: Basic compiler optimization flags and compiler annotations <a name="task1" /><a class="anchor-link" href="#Task-1:-Basic-compiler-optimization-flags-and-compiler-annotations-">¶</a></h2><h3 id="Overview">Overview<a class="anchor-link" href="#Overview">¶</a></h3><p>The goal of this task is to understand different options available to optimize the performance of the CPU Jacobi solver</p> <p>Your task is to:</p> <ul> <li>Optimize performance with <code>-Ofast</code> flag</li> +<li>Verify the cause for performance improvement by viewing perf profiles of O3 and Ofast binaries </li> <li>Optimize performance with profile directed feedback </li> +<li>Generate compiler annotations/remarks to understand the optimizations done by the compiler with and without profile directed feedback </li> </ul> <p>First, change the working directory to <code>Task1</code>.</p> @@ -13197,7 +13197,7 @@ build <code>poisson2d</code> binary (default)</li> </div> <div class="cell border-box-sizing code_cell rendered"> <div class="input"> -<div class="prompt input_prompt">In [1]:</div> +<div class="prompt input_prompt">In [4]:</div> <div class="inner_cell"> <div class="input_area"> <div class=" highlight hl-ipython3"><pre><span></span><span class="o">%</span><span class="k">cd</span> Task1 @@ -13217,7 +13217,7 @@ build <code>poisson2d</code> binary (default)</li> <div class="output_subarea output_stream output_stdout output_text"> -<pre>/autofs/nccsopen-svm1_home/aherten/SC18-Tutorial/3-Optimizing_POWER/Handson/Task1 +<pre>/autofs/nccsopen-svm1_home/aherten/SC19-Tutorial/3-Optimizing_POWER/Handson/Task1 </pre> </div> </div> @@ -13229,17 +13229,18 @@ build <code>poisson2d</code> binary (default)</li> <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<h3 id="Part-A:--Ofast-vs.--O3">Part A: <code>-Ofast</code> vs. <code>-O3</code><a class="anchor-link" href="#Part-A:--Ofast-vs.--O3">¶</a></h3><p>We are to compare the performance of the binary being compiled with <code>-Ofast</code> optimization and with <code>-O3</code> optimization. Right now, the Makefile specifies <code>-O3</code> as the optimization flag. Compile the code using <code>make</code> and run it with <code>make run</code> in the next two cells.</p> +<h3 id="Part-A:--Ofast-vs.--O3">Part A: <code>-Ofast</code> vs. <code>-O3</code><a class="anchor-link" href="#Part-A:--Ofast-vs.--O3">¶</a></h3><p>We are to compare the performance of the binary being compiled with <code>-Ofast</code> optimization and with <code>-O3</code> optimization. As in the previous task, we use a <code>Makefile</code> for compilation. The <code>Makefile</code> targets <code>poisson2d_O3</code> and <code>poisson2d_Ofast</code> are already prepared.</p> +<p><strong>TASK</strong>: Add <code>-O3</code> as the optimization flag for the <code>poisson2d_O3</code> target by using the corresponding <code>CFLAGS</code> definition. There are notes relating to this Task 1 in the header of the <code>Makefile</code>. Compile the code using <code>make</code> as indicated below and run with the <code>Make</code> targets <code>run</code>, <code>run_perf</code> and <code>run_perf_recrep</code>.</p> </div> </div> </div> <div class="cell border-box-sizing code_cell rendered"> <div class="input"> -<div class="prompt input_prompt">In [2]:</div> +<div class="prompt input_prompt">In [84]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make poisson2d_O3 </pre></div> </div> @@ -13256,8 +13257,8 @@ build <code>poisson2d</code> binary (default)</li> <div class="output_subarea output_stream output_stdout output_text"> -<pre>/sw/ascent/gcc/6.4.0/bin/gcc -c -std=c99 -mcpu=power9 -O3 -DUSE_DOUBLE -mvsx -maltivec -fopenmp poisson2d_reference.c -o poisson2d_reference.o -lm -/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -O3 -DUSE_DOUBLE -mvsx -maltivec -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d -lm +<pre>gcc -c -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -O3 poisson2d_reference.c -o poisson2d_reference.o -lm +gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -O3 poisson2d.c poisson2d_reference.o -o poisson2d -lm </pre> </div> </div> @@ -13268,7 +13269,7 @@ build <code>poisson2d</code> binary (default)</li> </div> <div class="cell border-box-sizing code_cell rendered"> <div class="input"> -<div class="prompt input_prompt">In [3]:</div> +<div class="prompt input_prompt">In [73]:</div> <div class="inner_cell"> <div class="input_area"> <div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make run @@ -13288,25 +13289,24 @@ build <code>poisson2d</code> binary (default)</li> <div class="output_subarea output_stream output_stdout output_text"> -<pre>bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d -Job <5033> is submitted to default queue <batch>. +<pre>bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d +Job <24897> is submitted to default queue <batch>. <<Waiting for dispatch ...>> <<Starting on login1>> -Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh -Calculate reference solution and time with serial CPU execution. - 0, 0.249980 - 100, 0.246028 - 200, 0.242198 - 300, 0.238487 - 400, 0.234887 +Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh Calculate current execution. - 0, 0.249980 - 100, 0.246028 - 200, 0.242198 - 300, 0.238487 - 400, 0.234887 -1.13user 0.00system 0:01.15elapsed 97%CPU (0avgtext+0avgdata 10944maxresident)k -2560inputs+0outputs (1major+264minor)pagefaults 0swaps + 0, 0.249995 + 100, 0.248997 + 200, 0.248007 + 300, 0.247025 + 400, 0.246050 + 500, 0.245084 + 600, 0.244124 + 700, 0.243173 + 800, 0.242228 + 900, 0.241291 +4.73user 0.00system 0:04.73elapsed 99%CPU (0avgtext+0avgdata 24256maxresident)k +256inputs+0outputs (0major+480minor)pagefaults 0swaps </pre> </div> </div> @@ -13318,20 +13318,17 @@ Calculate current execution. <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<p>You can use the GNU <em>perf</em> tool to profile the application using the <code>perf</code> command (see below) and see the top time-consuming functions.</p> +<p>Let's have a look at the output of the <code>Makefile</code> target <code>run_perf</code>. It invokes the GNU <em>perf</em> tool to print out details of the number of instructions executed and the number of cycles taken by POWER9 to execute the program. Feel free to add further counter to this call to <em>perf</em>.</p> </div> </div> </div> <div class="cell border-box-sizing code_cell rendered"> <div class="input"> -<div class="prompt input_prompt">In [4]:</div> +<div class="prompt input_prompt">In [74]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="c1"># perf record creates a perf.data file </span> -<span class="o">!</span>perf record -o perf.O3.data -e cycles ./poisson2d -<span class="c1"># perf report opens the perf.data file </span> -<span class="o">!</span>perf report -i perf.O3.data <span class="p">|</span> cat +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make run_perf </pre></div> </div> @@ -13348,49 +13345,121 @@ Calculate current execution. <div class="output_subarea output_stream output_stdout output_text"> -<pre>Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh -Calculate reference solution and time with serial CPU execution. - 0, 0.249980 - 100, 0.246028 - 200, 0.242198 - 300, 0.238487 - 400, 0.234887 +<pre>bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,instructions ./poisson2d +Job <24898> is submitted to default queue <batch>. +<<Waiting for dispatch ...>> +<<Starting on login1>> +Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh +Calculate current execution. + 0, 0.249995 + 100, 0.248997 + 200, 0.248007 + 300, 0.247025 + 400, 0.246050 + 500, 0.245084 + 600, 0.244124 + 700, 0.243173 + 800, 0.242228 + 900, 0.241291 + + Performance counter stats for './poisson2d': + + 16264721613 cycles:u + 28463907825 instructions:u # 1.75 insn per cycle + + 4.738444892 seconds time elapsed + +</pre> +</div> +</div> + +</div> +</div> + +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p>Next we run the makefile with target <code>run_perf_recrep</code> that prints the top routines of the application in terms of hotness by using a combination of <code>perf record ./app</code> and <code>perf report</code>.</p> + +</div> +</div> +</div> +<div class="cell border-box-sizing code_cell rendered"> +<div class="input"> +<div class="prompt input_prompt">In [75]:</div> +<div class="inner_cell"> + <div class="input_area"> +<div class=" highlight hl-ipython3"><pre><span></span><span class="c1"># run_perf_recrep displays the top hot routines </span> +<span class="o">!</span>make run_perf_recrep +</pre></div> + + </div> +</div> +</div> + +<div class="output_wrapper"> +<div class="output"> + + +<div class="output_area"> + + <div class="prompt"></div> + + +<div class="output_subarea output_stream output_stdout output_text"> +<pre>bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf record -e cycles --output=/gpfs/wolf/trn003/scratch/aherten//cycles.data ./poisson2d +Job <24899> is submitted to default queue <batch>. +<<Waiting for dispatch ...>> +<<Starting on login1>> +Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh Calculate current execution. - 0, 0.249980 - 100, 0.246028 - 200, 0.242198 - 300, 0.238487 - 400, 0.234887 -[ perf record: Woken up 1 times to write data ] -[ perf record: Captured and wrote 0.172 MB perf.O3.data (4125 samples) ] + 0, 0.249995 + 100, 0.248997 + 200, 0.248007 + 300, 0.247025 + 400, 0.246050 + 500, 0.245084 + 600, 0.244124 + 700, 0.243173 + 800, 0.242228 + 900, 0.241291 +[ perf record: Woken up 3 times to write data ] +[ perf record: Captured and wrote 0.739 MB /gpfs/wolf/trn003/scratch/aherten//cycles.data (19102 samples) ] +bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf report -i /gpfs/wolf/trn003/scratch/aherten//cycles.data --stdio +Job <24900> is submitted to default queue <batch>. +<<Waiting for dispatch ...>> +<<Starting on login1>> # To display the perf.data header info, please use --header/--header-only options. # # # Total Lost Samples: 0 # -# Samples: 4K of event 'cycles:u' -# Event count (approx.): 3867635297 +# Samples: 19K of event 'cycles:u' +# Event count (approx.): 16254596654 # -# Overhead Command Shared Object Symbol -# ........ ......... ................. ........................................ +# Overhead Command Shared Object Symbol +# ........ ......... ............. ........................................ # - 72.02% poisson2d poisson2d [.] 00000040.plt_call.fmax@@GLIBC_2.17 - 10.16% poisson2d poisson2d [.] poisson2d_reference - 9.99% poisson2d poisson2d [.] main - 4.69% poisson2d libc-2.17.so [.] __memcpy_power7 - 2.23% poisson2d libm-2.17.so [.] __fmaxf - 0.75% poisson2d libm-2.17.so [.] __exp_finite - 0.07% poisson2d poisson2d [.] 00000040.plt_call.memcpy@@GLIBC_2.17 - 0.02% poisson2d poisson2d [.] check_results - 0.02% poisson2d libm-2.17.so [.] __GI___exp - 0.01% poisson2d ld-2.17.so [.] _dl_relocate_object - 0.01% poisson2d [kernel.kallsyms] [k] arch_local_irq_restore - 0.00% poisson2d ld-2.17.so [.] _dl_new_object - 0.00% poisson2d ld-2.17.so [.] _start + 65.50% poisson2d poisson2d [.] 00000038.plt_call.fmax@@GLIBC_2.17 + 21.21% poisson2d poisson2d [.] main + 9.18% poisson2d libc-2.17.so [.] __memcpy_power7 + 3.28% poisson2d libm-2.17.so [.] __fmaxf + 0.74% poisson2d libm-2.17.so [.] __exp_finite + 0.04% poisson2d poisson2d [.] 00000038.plt_call.memcpy@@GLIBC_2.17 + 0.01% poisson2d libm-2.17.so [.] __GI___exp + 0.01% poisson2d ld-2.17.so [.] check_match.10253 + 0.01% poisson2d ld-2.17.so [.] do_lookup_x + 0.00% poisson2d ld-2.17.so [.] _dl_lookup_symbol_x + 0.00% poisson2d ld-2.17.so [.] _dl_relocate_object + 0.00% poisson2d ld-2.17.so [.] strcmp + 0.00% poisson2d ld-2.17.so [.] _wordcopy_fwd_aligned + 0.00% poisson2d ld-2.17.so [.] _dl_sysdep_start + 0.00% poisson2d ld-2.17.so [.] _start # -# (Tip: Show user configuration overrides: perf config --user --list) +# (Tip: Limit to show entries above 5% only: perf report --percent-limit 5) # </pre> </div> @@ -13403,17 +13472,19 @@ Calculate current execution. <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<p><strong>TASK</strong>: Now change the optimization flag in the <a href="/edit/Task1/Makefile">Makefile</a> to <code>-Ofast</code> and repeat the steps in the following cell. In case you follow along non-interactive, call <code>make</code> and <code>make run</code> in your shell. (If you are in the Jupyter Notebook, you can actually click the link of the <a href="/edit/Task1/Makefile">Makefile</a>. In other cases, use <code>vim</code> which is installed on Ascent.)</p> +<p><strong>TASK</strong>: Now add the optimization flag <code>Ofast</code> to the <code>CFLAGS</code> for target <code>poisson2d_Ofast</code>. Compile the program with the target <code>poisson2d_Ofast</code> and run and analyse it as before with <code>run</code>, <code>run_perf</code> and <code>run_perf_recrep</code>.</p> +<p>What difference do you see?</p> </div> </div> </div> <div class="cell border-box-sizing code_cell rendered"> <div class="input"> -<div class="prompt input_prompt">In [5]:</div> +<div class="prompt input_prompt">In [76]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make poisson2d_Ofast +<span class="o">!</span>make run </pre></div> </div> @@ -13430,8 +13501,25 @@ Calculate current execution. <div class="output_subarea output_stream output_stdout output_text"> -<pre>/sw/ascent/gcc/6.4.0/bin/gcc -c -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec -fopenmp poisson2d_reference.c -o poisson2d_reference.o -lm -/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d -lm +<pre>gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast poisson2d.c poisson2d_reference.o -o poisson2d -lm +bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d +Job <24901> is submitted to default queue <batch>. +<<Waiting for dispatch ...>> +<<Starting on login1>> +Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh +Calculate current execution. + 0, 0.249995 + 100, 0.248997 + 200, 0.248007 + 300, 0.247025 + 400, 0.246050 + 500, 0.245084 + 600, 0.244124 + 700, 0.243173 + 800, 0.242228 + 900, 0.241291 +2.41user 0.00system 0:02.41elapsed 99%CPU (0avgtext+0avgdata 24256maxresident)k +256inputs+0outputs (0major+480minor)pagefaults 0swaps </pre> </div> </div> @@ -13439,13 +13527,21 @@ Calculate current execution. </div> </div> +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p>Again, run a <code>perf</code>-instrumented version:</p> + +</div> +</div> </div> <div class="cell border-box-sizing code_cell rendered"> <div class="input"> -<div class="prompt input_prompt">In [6]:</div> +<div class="prompt input_prompt">In [77]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make run +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make run_perf </pre></div> </div> @@ -13462,25 +13558,30 @@ Calculate current execution. <div class="output_subarea output_stream output_stdout output_text"> -<pre>bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d -Job <5034> is submitted to default queue <batch>. +<pre>bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,instructions ./poisson2d +Job <24902> is submitted to default queue <batch>. <<Waiting for dispatch ...>> <<Starting on login1>> -Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh -Calculate reference solution and time with serial CPU execution. - 0, 0.249980 - 100, 0.246028 - 200, 0.242198 - 300, 0.238487 - 400, 0.234887 +Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh Calculate current execution. - 0, 0.249980 - 100, 0.246028 - 200, 0.242198 - 300, 0.238487 - 400, 0.234887 -0.51user 0.00system 0:00.52elapsed 99%CPU (0avgtext+0avgdata 10816maxresident)k -256inputs+0outputs (0major+264minor)pagefaults 0swaps + 0, 0.249995 + 100, 0.248997 + 200, 0.248007 + 300, 0.247025 + 400, 0.246050 + 500, 0.245084 + 600, 0.244124 + 700, 0.243173 + 800, 0.242228 + 900, 0.241291 + + Performance counter stats for './poisson2d': + + 8258991976 cycles:u + 12013091172 instructions:u # 1.45 insn per cycle + + 2.408703909 seconds time elapsed + </pre> </div> </div> @@ -13488,16 +13589,21 @@ Calculate current execution. </div> </div> +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p>Generate the list of top routines in terms of hotness:</p> + +</div> +</div> </div> <div class="cell border-box-sizing code_cell rendered"> <div class="input"> -<div class="prompt input_prompt">In [7]:</div> +<div class="prompt input_prompt">In [78]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="c1"># perf record creates a perf.data file </span> -<span class="o">!</span>perf record -o perf.Ofast.data -e cycles ./poisson2d -<span class="c1"># perf report opens the perf.data file </span> -<span class="o">!</span>perf report -i perf.Ofast.data <span class="p">|</span> cat +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make run_perf_recrep </pre></div> </div> @@ -13514,45 +13620,58 @@ Calculate current execution. <div class="output_subarea output_stream output_stdout output_text"> -<pre>Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh -Calculate reference solution and time with serial CPU execution. - 0, 0.249980 - 100, 0.246028 - 200, 0.242198 - 300, 0.238487 - 400, 0.234887 +<pre>bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf record -e cycles --output=/gpfs/wolf/trn003/scratch/aherten//cycles.data ./poisson2d +Job <24903> is submitted to default queue <batch>. +<<Waiting for dispatch ...>> +<<Starting on login1>> +Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh Calculate current execution. - 0, 0.249980 - 100, 0.246028 - 200, 0.242198 - 300, 0.238487 - 400, 0.234887 -[ perf record: Woken up 1 times to write data ] -[ perf record: Captured and wrote 0.086 MB perf.Ofast.data (1889 samples) ] + 0, 0.249995 + 100, 0.248997 + 200, 0.248007 + 300, 0.247025 + 400, 0.246050 + 500, 0.245084 + 600, 0.244124 + 700, 0.243173 + 800, 0.242228 + 900, 0.241291 +[ perf record: Woken up 2 times to write data ] +[ perf record: Captured and wrote 0.382 MB /gpfs/wolf/trn003/scratch/aherten//cycles.data (9728 samples) ] +bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf report -i /gpfs/wolf/trn003/scratch/aherten//cycles.data --stdio +Job <24904> is submitted to default queue <batch>. +<<Waiting for dispatch ...>> +<<Starting on login1>> # To display the perf.data header info, please use --header/--header-only options. # # # Total Lost Samples: 0 # -# Samples: 1K of event 'cycles:u' -# Event count (approx.): 1765737747 +# Samples: 9K of event 'cycles:u' +# Event count (approx.): 8268811890 # -# Overhead Command Shared Object Symbol -# ........ ......... ............. ....................... +# Overhead Command Shared Object Symbol +# ........ ......... ............. ........................................ # - 44.65% poisson2d poisson2d [.] main - 43.84% poisson2d poisson2d [.] poisson2d_reference - 10.28% poisson2d libc-2.17.so [.] __memcpy_power7 - 1.12% poisson2d libm-2.17.so [.] __exp_finite - 0.05% poisson2d poisson2d [.] check_results - 0.03% poisson2d ld-2.17.so [.] _dl_relocate_object - 0.02% poisson2d libc-2.17.so [.] __readdir64 - 0.01% poisson2d ld-2.17.so [.] _dl_new_object + 81.12% poisson2d poisson2d [.] main + 17.97% poisson2d libc-2.17.so [.] __memcpy_power7 + 0.79% poisson2d libm-2.17.so [.] __exp_finite + 0.04% poisson2d poisson2d [.] 00000038.plt_call.memcpy@@GLIBC_2.17 + 0.02% poisson2d ld-2.17.so [.] do_lookup_x + 0.01% poisson2d libc-2.17.so [.] vfprintf@@GLIBC_2.17 + 0.01% poisson2d libc-2.17.so [.] _dl_addr + 0.01% poisson2d ld-2.17.so [.] _dl_relocate_object + 0.01% poisson2d ld-2.17.so [.] check_match.10253 + 0.01% poisson2d ld-2.17.so [.] _dl_lookup_symbol_x + 0.01% poisson2d ld-2.17.so [.] strcmp + 0.00% poisson2d ld-2.17.so [.] open_path + 0.00% poisson2d ld-2.17.so [.] init_tls + 0.00% poisson2d ld-2.17.so [.] _dl_sysdep_start 0.00% poisson2d ld-2.17.so [.] _start # -# (Tip: System-wide collection from all CPUs: perf record -a) +# (Tip: For tracepoint events, try: perf report -s trace_fields) # </pre> </div> @@ -13573,7 +13692,7 @@ Calculate current execution. <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<h4 id="Interpretation">Interpretation<a class="anchor-link" href="#Interpretation">¶</a></h4><p>Depending on the application requirement, if a high precision of results is not mandatory, the users can compile an application with <code>-Ofast</code> which enables <code>–ffast-math</code> option that implements the same math function in a relaxed manner very similar to how general mathematical expressions are implemented and avoids the overhead of calling a function from the math library. Comparing the files, you will see that the <code>-Ofast</code> binary natively implements the <code>fmax</code> function using instructions available in the hardware. The <code>-O3</code> binary makes a library call to compute <code>fmax</code> to follow a stricter <em>IEEE</em> requirement for accuracy.</p> +<h4 id="Interpretation">Interpretation<a class="anchor-link" href="#Interpretation">¶</a></h4><p>Depending on the application requirement, if a high precision of results is not mandatory, one can compile an application with <code>-Ofast</code> which enables <code>–ffast-math</code> option that implements the same math function in a relaxed manner very similar to how general mathematical expressions are implemented and avoids the overhead of calling a function from the math library. Comparing the files, you will see that the <code>-Ofast</code> binary natively implements the <code>fmax</code> function using instructions available in the hardware. The <code>-O3</code> binary makes a library call to compute <code>fmax</code> to follow a stricter <em>IEEE</em> requirement for accuracy.</p> </div> </div> @@ -13581,27 +13700,85 @@ Calculate current execution. <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<h3 id="Part-B:-Profile-directed-Feedback">Part B: Profile-directed Feedback<a class="anchor-link" href="#Part-B:-Profile-directed-Feedback">¶</a></h3><p>For the first level of optimization we saw <code>Ofast</code> cut the execution time of the <code>O3</code> binary by almost half.</p> -<p>We can optimize the performance further by using profile directed feedback optimization.</p> -<p>To compile using profile directed feedback with the GCC compiler we need to do the following steps</p> +<h3 id="Part-B:-Profile-directed-Feedback">Part B: Profile-directed Feedback<a class="anchor-link" href="#Part-B:-Profile-directed-Feedback">¶</a></h3><p>For the first level of optimization we see that <code>Ofast</code> cut the execution time of the <code>O3</code> binary by almost half.</p> +<p>We can optimize the performance further by using profile-directed feedback optimization.</p> +<p>To compile using profile-directed feedback with the GCC compiler we need to build the appplication in three stages:</p> <ol> -<li>We need to first build a training binary using <code>-fprofile-generate</code>; this instructs the compiler to record hot path information </li> -<li>Run the training binary with a smaller input size; you should see a <code>.gcda</code> file generated which stores hot path information for further optimization by the compiler </li> -<li>build the final binary using <code>-fprofile-use</code> which uses the profile information in the <code>.gcda</code> file </li> -<li>Compare the performance of the final binary with the original <code>Ofast</code> binary </li> +<li>Instrument binary;</li> +<li>Run binary with training, gather profile information;</li> +<li>Use profile information to generate optimized binary.</li> </ol> -<p><strong>TASK</strong>: First, search for <code>TODO1</code> in the <a href="/edit/Task1/Makefile">Makefile</a>. It defines an additional compilation flag for <code>gcc</code>. Insert <code>-fprofile-generate=FOLDER</code> there with FOLDER pointing to <code>$$SC18_DIR_SCRATCH</code>, your personal write-directory (the double dollar signs are intentional as they are used to escape in the GNU Make syntax).</p> -<p>After editing, run the following two cells to train your program.</p> +<p>Step 1 is achieved by compiling the binary with the correct flag – <code>-fprofile-generate</code>. In our case, we need to specify an output location, which should be <code>$(SC19_DIR_SCRATCH)</code>.</p> +<p>Step 2 consists of a usual, albeit shorter run of the instrumented binary. The can be very short, though the parameters need to be representative of the actual run. After the binary ran, an output file (with file extension <code>.gcda</code>) is written to the directory specified during compilation.</p> +<p>For Step 3, the binary is once again compiled, but this time using the <code>gcda</code> profile just generated. The according flag is <code>-fprofile-use</code>, which we set to <code>$(SC19_DIR_SCRATCH)</code> as well.</p> +<p>In our <code>Makefile</code> at hand, we prepared the steps already for you in the form of two targets.</p> +<ul> +<li><code>poisson2d_train</code>: Will compile the binary with profile-directed feedback</li> +<li><code>poisson2d_ref</code>: Will take a generated profile and compile a new, optimized binary</li> +</ul> +<p>By using dependencies, between these two targets a profile run is launched.</p> +<p><strong>TASK</strong>: Edit the <a href="`Makefile`">Makefile</a> and add the <code>-fprofile-*</code> flags to the <code>CFLAGS</code> of <code>poisson2d_train</code> and +<code>poisson2d_ref</code> as outline in the file.</p> +<p>After that, you may launch them with the following cells (<code>gen_profile</code> is a meta-target and uses <code>poisson2d_train</code> and <code>poisson2d_ref</code>). If you need to clean the generated profile, you may use <code>make clean_profile</code>.</p> + +</div> +</div> +</div> +<div class="cell border-box-sizing code_cell rendered"> +<div class="input"> +<div class="prompt input_prompt">In [79]:</div> +<div class="inner_cell"> + <div class="input_area"> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make gen_profile +</pre></div> + + </div> +</div> +</div> + +<div class="output_wrapper"> +<div class="output"> + + +<div class="output_area"> + + <div class="prompt"></div> + + +<div class="output_subarea output_stream output_stdout output_text"> +<pre>gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast -fprofile-generate=/gpfs/wolf/trn003/scratch/aherten/ poisson2d.c -o poisson2d_train -lm +bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS ./poisson2d_train 100 100 100 +Job <24905> is submitted to default queue <batch>. +<<Waiting for dispatch ...>> +<<Starting on login1>> +Jacobi relaxation calculation: max 100 iterations on 100 x 100 mesh +Calculate current execution. + 0, 0.249490 +echo `date` > /gpfs/wolf/trn003/scratch/aherten//.profile_generated +gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast -fprofile-use=/gpfs/wolf/trn003/scratch/aherten/ poisson2d.c -o poisson2d_ref -lm +cp poisson2d_ref poisson2d +</pre> +</div> +</div> + +</div> +</div> + +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p>If the previous cell executed correctly, you now have your optimized executable. Let's see if it even fast than before!</p> </div> </div> </div> <div class="cell border-box-sizing code_cell rendered"> <div class="input"> -<div class="prompt input_prompt">In [8]:</div> +<div class="prompt input_prompt">In [80]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make poisson2d_train +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make run </pre></div> </div> @@ -13618,8 +13795,24 @@ Calculate current execution. <div class="output_subarea output_stream output_stdout output_text"> -<pre>/sw/ascent/gcc/6.4.0/bin/gcc -c -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec "-fprofile-generate=$SC18_DIR_SCRATCH" -fopenmp poisson2d_reference.c -o poisson2d_reference.o -lm -/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec "-fprofile-generate=$SC18_DIR_SCRATCH" -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d_train -lm +<pre>bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d +Job <24906> is submitted to default queue <batch>. +<<Waiting for dispatch ...>> +<<Starting on login1>> +Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh +Calculate current execution. + 0, 0.249995 + 100, 0.248997 + 200, 0.248007 + 300, 0.247025 + 400, 0.246050 + 500, 0.245084 + 600, 0.244124 + 700, 0.243173 + 800, 0.242228 + 900, 0.241291 +2.28user 0.01system 0:02.30elapsed 99%CPU (0avgtext+0avgdata 24192maxresident)k +256inputs+0outputs (0major+479minor)pagefaults 0swaps </pre> </div> </div> @@ -13627,13 +13820,29 @@ Calculate current execution. </div> </div> +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p>Great! It is! In our tests, this shaved off another 5%.</p> + +</div> +</div> +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p>Let's also measure instructions and cycles</p> + +</div> +</div> </div> <div class="cell border-box-sizing code_cell rendered"> <div class="input"> -<div class="prompt input_prompt">In [9]:</div> +<div class="prompt input_prompt">In [81]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make run_train +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make run_perf </pre></div> </div> @@ -13650,20 +13859,30 @@ Calculate current execution. <div class="output_subarea output_stream output_stdout output_text"> -<pre>bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d_train 200 64 64 -Job <5035> is submitted to default queue <batch>. +<pre>bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,instructions ./poisson2d +Job <24907> is submitted to default queue <batch>. <<Waiting for dispatch ...>> <<Starting on login1>> -Jacobi relaxation calculation: max 200 iterations on 64 x 64 mesh -Calculate reference solution and time with serial CPU execution. - 0, 0.248743 - 100, 0.124046 +Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh Calculate current execution. - 0, 0.248743 - 100, 0.124046 -0.00user 0.00system 0:00.10elapsed 5%CPU (0avgtext+0avgdata 5248maxresident)k -512inputs+0outputs (0major+115minor)pagefaults 0swaps -mv $SC18_DIR_SCRATCH/*.gcda . + 0, 0.249995 + 100, 0.248997 + 200, 0.248007 + 300, 0.247025 + 400, 0.246050 + 500, 0.245084 + 600, 0.244124 + 700, 0.243173 + 800, 0.242228 + 900, 0.241291 + + Performance counter stats for './poisson2d': + + 7925983538 cycles:u + 12253080719 instructions:u # 1.55 insn per cycle + + 2.313471365 seconds time elapsed + </pre> </div> </div> @@ -13675,19 +13894,27 @@ mv $SC18_DIR_SCRATCH/*.gcda . <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<p>Now, a <code>.gcda</code> file exists in the directory which can be used for an profile-accelerated subsequent run.</p> -<p><strong>TASK</strong>: Edit the <a href="/edit/Task1/Makefile">Makefile</a> again, this time modifying <code>TODO2</code> to be equivalent to <code>-fprofile-use</code>. A directory is not needed as we copied the gcda file into the current directory.</p> -<p>Run the following cells in order to build using the newly added flag and then run with the profile-accelerated version.</p> +<p>What is your speed-up? Feel free to run with larger problem sizes (mesh; iterations)</p> + +</div> +</div> +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<h3 id="Part-C:-Compiler-annotations/Remarks">Part C: Compiler annotations/Remarks<a class="anchor-link" href="#Part-C:-Compiler-annotations/Remarks">¶</a></h3><p>Usually, all compilers provide an option to emit annotations or remarks by the compiler. These remarks summarize the optimizations done in detail, the location in source where these optimizations were done. There exist options that also indicate optimizations that were missed and the reason why they could not be done.</p> +<p>To generate compiler annotations using GCC, one uses <code>-fopt-info-all</code>. If you only want to see the missed options, use the option <code>-fopt-info-missed</code> instead of <code>-fopt-info-all</code>. See also the <a href="https://gcc.gnu.org/onlinedocs/gcc/Developer-Options.html#index-fopt-info">documentation of GCC regarding the flag</a>.</p> +<p><strong>TASK</strong>: Have a looK at the <code>CFLAGS</code> of the <code>Makefile</code> target <code>poisson2d_Ofast_info</code>. Add the flag <code>-fopt-info-all</code> to the list of flags. This will print optimisation information to stdout. If you rather want to print to this information to a file, use – for example – <code>-fopt-info-all=(SC19_DIR_SCRATCH)/filename</code>.</p> </div> </div> </div> <div class="cell border-box-sizing code_cell rendered"> <div class="input"> -<div class="prompt input_prompt">In [10]:</div> +<div class="prompt input_prompt">In [82]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make poisson2d_profile +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make poisson2d_Ofast_info </pre></div> </div> @@ -13704,8 +13931,74 @@ mv $SC18_DIR_SCRATCH/*.gcda . <div class="output_subarea output_stream output_stdout output_text"> -<pre>/sw/ascent/gcc/6.4.0/bin/gcc -c -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec "-fprofile-use" -fopenmp poisson2d_reference.c -o poisson2d_reference.o -lm -/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec "-fprofile-use" -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d_profile -lm +<pre>gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast -fopt-info-all poisson2d.c poisson2d_reference.o -o poisson2d_Ofast_info -lm +poisson2d.c:62:14: optimized: Inlining atoi/24 into main/33 (always_inline). +poisson2d.c:61:14: optimized: Inlining atoi/24 into main/33 (always_inline). +poisson2d.c:56:14: optimized: Inlining atoi/24 into main/33 (always_inline). +poisson2d.c:52:20: optimized: Inlining atoi/24 into main/33 (always_inline). +poisson2d.c:161:5: missed: not inlinable: main/33 -> free/38, function body not available +poisson2d.c:159:5: missed: not inlinable: main/33 -> free/38, function body not available +poisson2d.c:158:5: missed: not inlinable: main/33 -> free/38, function body not available +poisson2d.c:142:31: missed: not inlinable: main/33 -> printf/36, function body not available +poisson2d.c:103:5: missed: not inlinable: main/33 -> __builtin_puts/37, function body not available +poisson2d.c:96:5: missed: not inlinable: main/33 -> printf/36, function body not available +poisson2d.c:78:29: missed: not inlinable: main/33 -> exp/35, function body not available +poisson2d.c:68:41: missed: not inlinable: main/33 -> malloc/34, function body not available +poisson2d.c:67:41: missed: not inlinable: main/33 -> malloc/34, function body not available +poisson2d.c:65:41: missed: not inlinable: main/33 -> malloc/34, function body not available +/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available +/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available +/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available +/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available +Unit growth for small function inlining: 207->207 (0%) + +Inlined 4 calls, eliminated 0 functions + +consider run-time aliasing test between *_84 and *_87 +consider run-time aliasing test between *_92 and *_97 +consider run-time aliasing test between *_104 and *_107 +consider run-time aliasing test between *_111 and *_115 +poisson2d.c:124:13: optimized: Loop 8 distributed: split to 0 loops and 1 library calls. +poisson2d.c:90:9: optimized: Loop 10 distributed: split to 0 loops and 1 library calls. +poisson2d.c:108:25: missed: couldn't vectorize loop +poisson2d.c:108:25: missed: not vectorized: loop nest containing two or more consecutive inner loops cannot be vectorized +poisson2d.c:136:9: missed: couldn't vectorize loop +poisson2d.c:136:9: missed: Loop costings may not be worthwhile. +poisson2d.c:131:9: missed: couldn't vectorize loop +poisson2d.c:131:9: missed: Loop costings may not be worthwhile. +poisson2d.c:122:9: missed: couldn't vectorize loop +poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_543, _539, _549); +poisson2d.c:112:9: missed: couldn't vectorize loop +poisson2d.c:112:9: missed: not vectorized: control flow in loop. +poisson2d.c:114:13: optimized: loop vectorized using 16 byte vectors +poisson2d.c:88:5: missed: couldn't vectorize loop +poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_528, 0, _531); +poisson2d.c:72:5: missed: couldn't vectorize loop +poisson2d.c:78:27: missed: not vectorized: complicated access pattern. +poisson2d.c:74:9: missed: couldn't vectorize loop +poisson2d.c:78:29: missed: not vectorized: relevant stmt not supported: _27 = exp (_21); +poisson2d.c:43:5: note: vectorized 1 loops in function. +poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_528, 0, _531); +poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_543, _539, _549); +poisson2d.c:114:13: optimized: loop turned into non-loop; it never loops +/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _187 = strtol (_1, 0B, 10); +/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _189 = strtol (_2, 0B, 10); +/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _193 = strtol (_3, 0B, 10); +/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _191 = strtol (_4, 0B, 10); +poisson2d.c:65:41: missed: statement clobbers memory: A_153 = malloc (_7); +poisson2d.c:67:41: missed: statement clobbers memory: Anew_155 = malloc (_7); +poisson2d.c:68:41: missed: statement clobbers memory: rhs_157 = malloc (_7); +poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_528, 0, _531); +poisson2d.c:96:5: missed: statement clobbers memory: printf ("Jacobi relaxation calculation: max %d iterations on %d x %d mesh\n", iter_max_130, ny_139, nx_195); +poisson2d.c:103:5: missed: statement clobbers memory: __builtin_puts (&"Calculate current execution."[0]); +poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_543, _539, _549); +poisson2d.c:142:31: missed: statement clobbers memory: printf ("%5d, %0.6f\n", iter_237, error_219); +poisson2d.c:158:5: missed: statement clobbers memory: free (rhs_202); +poisson2d.c:159:5: missed: statement clobbers memory: free (Anew_124); +poisson2d.c:161:5: missed: statement clobbers memory: free (A_123); +poisson2d.c:65:41: missed: statement clobbers memory: A_144 = malloc (8000000); +poisson2d.c:67:41: missed: statement clobbers memory: Anew_143 = malloc (8000000); +poisson2d.c:68:41: missed: statement clobbers memory: rhs_142 = malloc (8000000); </pre> </div> </div> @@ -13713,13 +14006,23 @@ mv $SC18_DIR_SCRATCH/*.gcda . </div> </div> +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p>Let's compare this with the output during compilation when using profile-directed feedback from Task 1 B.</p> +<p><strong>TASK</strong>: +Adapt the <code>CFLAGS</code> of <code>poisson2d_ref_info</code> to include <code>-fopt-info-all</code> <strong>and</strong> the profile input of <code>-fprofile-use=…</code> here. <em>(Be advised: Long output!)</em></p> + +</div> +</div> </div> <div class="cell border-box-sizing code_cell rendered"> <div class="input"> -<div class="prompt input_prompt">In [11]:</div> +<div class="prompt input_prompt">In [83]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make run_profile +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make poisson2d_ref_info </pre></div> </div> @@ -13736,25 +14039,220 @@ mv $SC18_DIR_SCRATCH/*.gcda . <div class="output_subarea output_stream output_stdout output_text"> -<pre>bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d_profile -Job <5036> is submitted to default queue <batch>. +<pre>gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast -fopt-info-all -fprofile-use=/gpfs/wolf/trn003/scratch/aherten/ -Ofast -fprofile-generate=/gpfs/wolf/trn003/scratch/aherten/ poisson2d.c -o poisson2d_train -lm +poisson2d.c:62:14: optimized: Inlining atoi/24 into main/33 (always_inline). +poisson2d.c:61:14: optimized: Inlining atoi/24 into main/33 (always_inline). +poisson2d.c:56:14: optimized: Inlining atoi/24 into main/33 (always_inline). +poisson2d.c:52:20: optimized: Inlining atoi/24 into main/33 (always_inline). +Increasing alignment of decl: __gcov0.main +poisson2d.c:164:1: missed: not inlinable: _GLOBAL__sub_D_00100_1_main/48 -> __gcov_exit/55, function body not available +poisson2d.c:164:1: missed: not inlinable: _GLOBAL__sub_I_00100_0_main/47 -> __gcov_init/54, function body not available +poisson2d.c:161:5: missed: not inlinable: main/33 -> free/38, function body not available +poisson2d.c:159:5: missed: not inlinable: main/33 -> free/38, function body not available +poisson2d.c:158:5: missed: not inlinable: main/33 -> free/38, function body not available +poisson2d.c:142:31: missed: not inlinable: main/33 -> printf/36, function body not available +poisson2d.c:103:5: missed: not inlinable: main/33 -> __builtin_puts/37, function body not available +poisson2d.c:96:5: missed: not inlinable: main/33 -> printf/36, function body not available +poisson2d.c:78:29: missed: not inlinable: main/33 -> exp/35, function body not available +poisson2d.c:68:41: missed: not inlinable: main/33 -> malloc/34, function body not available +poisson2d.c:67:41: missed: not inlinable: main/33 -> malloc/34, function body not available +poisson2d.c:65:41: missed: not inlinable: main/33 -> malloc/34, function body not available +/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available +/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available +/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available +/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available +Unit growth for small function inlining: 295->295 (0%) + +Inlined 4 calls, eliminated 0 functions + +consider run-time aliasing test between *_84 and *_87 +consider run-time aliasing test between *_92 and *_97 +consider run-time aliasing test between *_104 and *_107 +consider run-time aliasing test between *_111 and *_115 +poisson2d.c:124:13: optimized: Loop 8 distributed: split to 0 loops and 1 library calls. +poisson2d.c:90:9: optimized: Loop 10 distributed: split to 0 loops and 1 library calls. +poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_64, _135, _313); +poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_632, 0, _239); +poisson2d.c:108:25: missed: couldn't vectorize loop +poisson2d.c:108:25: missed: not vectorized: loop nest containing two or more consecutive inner loops cannot be vectorized +poisson2d.c:136:9: missed: couldn't vectorize loop +poisson2d.c:136:9: missed: Loop costings may not be worthwhile. +poisson2d.c:131:9: missed: couldn't vectorize loop +poisson2d.c:131:9: missed: Loop costings may not be worthwhile. +poisson2d.c:122:9: missed: couldn't vectorize loop +poisson2d.c:122:9: missed: not vectorized: control flow in loop. +poisson2d.c:112:9: missed: couldn't vectorize loop +poisson2d.c:112:9: missed: not vectorized: control flow in loop. +poisson2d.c:114:13: optimized: loop vectorized using 16 byte vectors +poisson2d.c:88:5: missed: couldn't vectorize loop +poisson2d.c:88:5: missed: not vectorized: control flow in loop. +poisson2d.c:72:5: missed: couldn't vectorize loop +poisson2d.c:72:5: missed: not vectorized: control flow in loop. +poisson2d.c:74:9: missed: couldn't vectorize loop +poisson2d.c:78:29: missed: not vectorized: relevant stmt not supported: _27 = exp (_21); +poisson2d.c:43:5: note: vectorized 1 loops in function. +poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_632, 0, _239); +poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_64, _135, _313); +poisson2d.c:114:13: optimized: loop turned into non-loop; it never loops +/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _187 = strtol (_1, 0B, 10); +/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _189 = strtol (_2, 0B, 10); +/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _193 = strtol (_3, 0B, 10); +/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _191 = strtol (_4, 0B, 10); +poisson2d.c:65:41: missed: statement clobbers memory: A_153 = malloc (_7); +poisson2d.c:67:41: missed: statement clobbers memory: Anew_155 = malloc (_7); +poisson2d.c:68:41: missed: statement clobbers memory: rhs_157 = malloc (_7); +poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_632, 0, _239); +poisson2d.c:96:5: missed: statement clobbers memory: printf ("Jacobi relaxation calculation: max %d iterations on %d x %d mesh\n", iter_max_337, ny_124, nx_286); +poisson2d.c:103:5: missed: statement clobbers memory: __builtin_puts (&"Calculate current execution."[0]); +poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_64, _135, _313); +poisson2d.c:142:31: missed: statement clobbers memory: printf ("%5d, %0.6f\n", iter_316, error_118); +poisson2d.c:158:5: missed: statement clobbers memory: free (rhs_127); +poisson2d.c:159:5: missed: statement clobbers memory: free (Anew_311); +poisson2d.c:161:5: missed: statement clobbers memory: free (A_122); +poisson2d.c:65:41: missed: statement clobbers memory: A_129 = malloc (8000000); +poisson2d.c:67:41: missed: statement clobbers memory: Anew_132 = malloc (8000000); +poisson2d.c:68:41: missed: statement clobbers memory: rhs_140 = malloc (8000000); +poisson2d.c:136:9: note: considering unrolling loop 7 at BB 53 +considering unrolling loop with constant number of iterations +considering unrolling loop with runtime-computable number of iterations +poisson2d.c:136:9: optimized: loop unrolled 7 times (header execution count 9800) +poisson2d.c:131:9: note: considering unrolling loop 6 at BB 50 +considering unrolling loop with constant number of iterations +considering unrolling loop with runtime-computable number of iterations +poisson2d.c:131:9: optimized: loop unrolled 7 times (header execution count 9800) +poisson2d.c:122:9: note: considering unrolling loop 5 at BB 47 +considering unrolling loop with constant number of iterations +considering unrolling loop with runtime-computable number of iterations +poisson2d.c:122:9: optimized: loop unrolled 3 times (header execution count 9800) +poisson2d.c:118:25: note: considering unrolling loop 13 at BB 33 +considering unrolling loop with constant number of iterations +considering unrolling loop with runtime-computable number of iterations +poisson2d.c:118:25: optimized: loop unrolled 3 times (header execution count 436550) +poisson2d.c:118:25: note: considering unrolling loop 9 at BB 30 +considering unrolling loop with constant number of iterations +considering unrolling loop with runtime-computable number of iterations +poisson2d.c:112:9: note: considering unrolling loop 14 at BB 42 +poisson2d.c:43:5: note: considering unrolling loop 4 at BB 40 +poisson2d.c:108:25: note: considering unrolling loop 3 at BB 60 +poisson2d.c:88:5: note: considering unrolling loop 2 at BB 23 +considering unrolling loop with constant number of iterations +considering unrolling loop with runtime-computable number of iterations +poisson2d.c:88:5: optimized: loop unrolled 3 times (header execution count 100) +poisson2d.c:74:9: note: considering unrolling loop 11 at BB 12 +considering unrolling loop with constant number of iterations +considering unrolling loop with runtime-computable number of iterations +poisson2d.c:74:9: optimized: loop unrolled 3 times (header execution count 9604) +poisson2d.c:72:5: note: considering unrolling loop 1 at BB 16 +poisson2d.c:164:1: missed: statement clobbers memory: __gcov_init (&*.LPBX0); +poisson2d.c:164:1: missed: statement clobbers memory: __gcov_exit (); +bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS ./poisson2d_train 100 100 100 +Job <24908> is submitted to default queue <batch>. <<Waiting for dispatch ...>> <<Starting on login1>> -Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh -Calculate reference solution and time with serial CPU execution. - 0, 0.249980 - 100, 0.246028 - 200, 0.242198 - 300, 0.238487 - 400, 0.234887 +libgcov profiling error:/gpfs/wolf/trn003/scratch/aherten//#autofs#nccsopen-svm1_home#aherten#SC19-Tutorial#3-Optimizing_POWER#Handson#Task1#poisson2d.gcda:overwriting an existing profile data with a different timestamp +Jacobi relaxation calculation: max 100 iterations on 100 x 100 mesh Calculate current execution. - 0, 0.249980 - 100, 0.246028 - 200, 0.242198 - 300, 0.238487 - 400, 0.234887 -0.47user 0.00system 0:00.48elapsed 98%CPU (0avgtext+0avgdata 10816maxresident)k -256inputs+0outputs (0major+265minor)pagefaults 0swaps + 0, 0.249490 +echo `date` > /gpfs/wolf/trn003/scratch/aherten//.profile_generated +gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast -fopt-info-all -fprofile-use=/gpfs/wolf/trn003/scratch/aherten/ poisson2d.c poisson2d_reference.o -o poisson2d_ref_info -lm +poisson2d.c:62:14: optimized: Inlining atoi/24 into main/33 (always_inline). +poisson2d.c:61:14: optimized: Inlining atoi/24 into main/33 (always_inline). +poisson2d.c:56:14: optimized: Inlining atoi/24 into main/33 (always_inline). +poisson2d.c:52:20: optimized: Inlining atoi/24 into main/33 (always_inline). +poisson2d.c:161:5: missed: not inlinable: main/33 -> free/38, function body not available +poisson2d.c:159:5: missed: not inlinable: main/33 -> free/38, function body not available +poisson2d.c:158:5: missed: not inlinable: main/33 -> free/38, function body not available +poisson2d.c:142:31: missed: not inlinable: main/33 -> printf/36, function body not available +poisson2d.c:103:5: missed: not inlinable: main/33 -> __builtin_puts/37, function body not available +poisson2d.c:96:5: missed: not inlinable: main/33 -> printf/36, function body not available +poisson2d.c:78:29: missed: not inlinable: main/33 -> exp/35, function body not available +poisson2d.c:68:41: missed: not inlinable: main/33 -> malloc/34, function body not available +poisson2d.c:67:41: missed: not inlinable: main/33 -> malloc/34, function body not available +poisson2d.c:65:41: missed: not inlinable: main/33 -> malloc/34, function body not available +/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available +/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available +/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available +/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available +Unit growth for small function inlining: 207->207 (0%) + +Inlined 4 calls, eliminated 0 functions + +consider run-time aliasing test between *_84 and *_87 +consider run-time aliasing test between *_92 and *_97 +consider run-time aliasing test between *_104 and *_107 +consider run-time aliasing test between *_111 and *_115 +poisson2d.c:124:13: optimized: Loop 8 distributed: split to 0 loops and 1 library calls. +poisson2d.c:90:9: optimized: Loop 10 distributed: split to 0 loops and 1 library calls. +poisson2d.c:108:25: missed: couldn't vectorize loop +poisson2d.c:108:25: missed: not vectorized: loop nest containing two or more consecutive inner loops cannot be vectorized +poisson2d.c:136:9: missed: couldn't vectorize loop +poisson2d.c:136:9: missed: Loop costings may not be worthwhile. +poisson2d.c:131:9: missed: couldn't vectorize loop +poisson2d.c:131:9: missed: Loop costings may not be worthwhile. +poisson2d.c:122:9: missed: couldn't vectorize loop +poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_539, _535, _544); +poisson2d.c:112:9: missed: couldn't vectorize loop +poisson2d.c:112:9: missed: not vectorized: control flow in loop. +poisson2d.c:114:13: optimized: loop vectorized using 16 byte vectors +poisson2d.c:88:5: missed: couldn't vectorize loop +poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_524, 0, _527); +poisson2d.c:72:5: missed: couldn't vectorize loop +poisson2d.c:78:27: missed: not vectorized: complicated access pattern. +poisson2d.c:74:9: missed: couldn't vectorize loop +poisson2d.c:78:29: missed: not vectorized: relevant stmt not supported: _27 = exp (_21); +poisson2d.c:43:5: note: vectorized 1 loops in function. +poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_524, 0, _527); +poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_539, _535, _544); +poisson2d.c:114:13: optimized: loop turned into non-loop; it never loops +/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _187 = strtol (_1, 0B, 10); +/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _189 = strtol (_2, 0B, 10); +/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _193 = strtol (_3, 0B, 10); +/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _191 = strtol (_4, 0B, 10); +poisson2d.c:65:41: missed: statement clobbers memory: A_153 = malloc (_7); +poisson2d.c:67:41: missed: statement clobbers memory: Anew_155 = malloc (_7); +poisson2d.c:68:41: missed: statement clobbers memory: rhs_157 = malloc (_7); +poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_524, 0, _527); +poisson2d.c:96:5: missed: statement clobbers memory: printf ("Jacobi relaxation calculation: max %d iterations on %d x %d mesh\n", iter_max_130, ny_139, nx_195); +poisson2d.c:103:5: missed: statement clobbers memory: __builtin_puts (&"Calculate current execution."[0]); +poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_539, _535, _544); +poisson2d.c:142:31: missed: statement clobbers memory: printf ("%5d, %0.6f\n", iter_237, error_219); +poisson2d.c:158:5: missed: statement clobbers memory: free (rhs_202); +poisson2d.c:159:5: missed: statement clobbers memory: free (Anew_124); +poisson2d.c:161:5: missed: statement clobbers memory: free (A_123); +poisson2d.c:65:41: missed: statement clobbers memory: A_144 = malloc (8000000); +poisson2d.c:67:41: missed: statement clobbers memory: Anew_143 = malloc (8000000); +poisson2d.c:68:41: missed: statement clobbers memory: rhs_142 = malloc (8000000); +poisson2d.c:136:9: note: considering unrolling loop 7 at BB 47 +considering unrolling loop with constant number of iterations +considering unrolling loop with runtime-computable number of iterations +poisson2d.c:136:9: optimized: loop unrolled 7 times (header execution count 9800) +poisson2d.c:131:9: note: considering unrolling loop 6 at BB 44 +considering unrolling loop with constant number of iterations +considering unrolling loop with runtime-computable number of iterations +poisson2d.c:131:9: optimized: loop unrolled 7 times (header execution count 9800) +poisson2d.c:122:9: note: considering unrolling loop 5 at BB 40 +considering unrolling loop with constant number of iterations +considering unrolling loop with runtime-computable number of iterations +poisson2d.c:122:9: optimized: loop unrolled 7 times (header execution count 9701) +poisson2d.c:118:25: note: considering unrolling loop 13 at BB 27 +considering unrolling loop with constant number of iterations +considering unrolling loop with runtime-computable number of iterations +poisson2d.c:118:25: optimized: loop unrolled 3 times (header execution count 436550) +poisson2d.c:118:25: note: considering unrolling loop 9 at BB 24 +considering unrolling loop with constant number of iterations +considering unrolling loop with runtime-computable number of iterations +poisson2d.c:112:9: note: considering unrolling loop 14 at BB 37 +poisson2d.c:43:5: note: considering unrolling loop 4 at BB 35 +poisson2d.c:108:25: note: considering unrolling loop 3 at BB 51 +poisson2d.c:88:5: note: considering unrolling loop 2 at BB 18 +considering unrolling loop with constant number of iterations +considering unrolling loop with runtime-computable number of iterations +poisson2d.c:88:5: optimized: loop unrolled 7 times (header execution count 99) +poisson2d.c:74:9: note: considering unrolling loop 11 at BB 9 +considering unrolling loop with constant number of iterations +considering unrolling loop with runtime-computable number of iterations +poisson2d.c:74:9: optimized: loop unrolled 3 times (header execution count 9604) +poisson2d.c:72:5: note: considering unrolling loop 1 at BB 14 </pre> </div> </div> @@ -13766,7 +14264,11 @@ Calculate current execution. <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<p>What is your speed-up? Feel free to run with larger problem sizes (mesh; iterations)</p> +<p>Comparing the annotations generated of a plain <code>-Ofast</code> optimization level and the one generated at <code>-Ofast</code> and profile directed feedback, we observe that many more optimizations are possible due to profile information.</p> +<p>For instance you will see annotations such as</p> + +<pre><code>poisson2d.c:114:25: optimized: loop unrolled 3 times (header execution count 436550)</code></pre> +<p>The execution count indicates the dynamic execution count of the node at runtime. This information determines which paths are hotter and subsequently facilitate additional optimizations.</p> </div> </div> @@ -13794,15 +14296,19 @@ Calculate current execution. <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<h2 id="Task-2:-Software-Pretechting">Task 2:<a name="task2" /> Software Pretechting<a class="anchor-link" href="#Task-2:-Software-Pretechting">¶</a></h2><h3 id="Overview">Overview<a class="anchor-link" href="#Overview">¶</a></h3><p>Study the difference of program execution time of different optimization levels with and without software prefetching.</p> -<p>First, change directory to that of Task 2</p> +<h2 id="Task-2:-Impact-of-Prefetching-on-Performance">Task 2:<a name="task2" /> Impact of Prefetching on Performance<a class="anchor-link" href="#Task-2:-Impact-of-Prefetching-on-Performance">¶</a></h2><h3 id="Overview">Overview<a class="anchor-link" href="#Overview">¶</a></h3><ul> +<li>Study the difference of program execution time of different optimization levels with and without software prefetching.</li> +<li>Verify the impact by measuring cache counters with and without prefetching.</li> +<li>Learn how to modify contents of DSCR (<em>Data Stream Control Register</em>) using IBM XL compiler and study the impact with different values to DSCR. </li> +</ul> +<p>But first, lets change directory to that of Task 2</p> </div> </div> </div> <div class="cell border-box-sizing code_cell rendered"> <div class="input"> -<div class="prompt input_prompt">In [12]:</div> +<div class="prompt input_prompt">In [85]:</div> <div class="inner_cell"> <div class="input_area"> <div class=" highlight hl-ipython3"><pre><span></span><span class="o">%</span><span class="k">cd</span> ../Task2 @@ -13822,7 +14328,7 @@ Calculate current execution. <div class="output_subarea output_stream output_stdout output_text"> -<pre>/autofs/nccsopen-svm1_home/aherten/SC18-Tutorial/3-Optimizing_POWER/Handson/Task2 +<pre>/autofs/nccsopen-svm1_home/aherten/SC19-Tutorial/3-Optimizing_POWER/Handson/Task2 </pre> </div> </div> @@ -13834,25 +14340,28 @@ Calculate current execution. <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<h3 id="Part-A:-Running">Part A: Running<a class="anchor-link" href="#Part-A:-Running">¶</a></h3> +<h3 id="Part-A:-Software-Prefetching">Part A: Software Prefetching<a class="anchor-link" href="#Part-A:-Software-Prefetching">¶</a></h3> </div> </div> </div> <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<p>Look at the <a href="/edit/Task2/Makefile">Makefile</a> and work on the TODOs. Please implement compile flags as mentioned in the Makefile target name.</p> -<p>Afterwards, compile each target with the following cells and submit them to the batch system. Follow along accordingly in the non-interactive version of this Notebook.</p> +<p><strong>TASK</strong>: Look at the Makefile and work on the TODOs.</p> +<ul> +<li>First generate a <code>-Ofast</code>-optimised binary and note down the performance in terms of cycles, seconds, and L3 misses. This is our baseline!</li> +<li>Modify the <code>Makefile</code> to add the option for software prefetching (<code>-fprefetch-loop-arrays</code>). Compare performance of <code>-Ofast</code> with and without software prefetching</li> +</ul> </div> </div> </div> <div class="cell border-box-sizing code_cell rendered"> <div class="input"> -<div class="prompt input_prompt">In [13]:</div> +<div class="prompt input_prompt">In [97]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make run_o3_pref +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make clean </pre></div> </div> @@ -13869,27 +14378,7 @@ Calculate current execution. <div class="output_subarea output_stream output_stdout output_text"> -<pre>/sw/ascent/gcc/6.4.0/bin/gcc -c -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -O3 -fprefetch-loop-arrays -fopenmp poisson2d_reference.c -o poisson2d_reference.o -lm -/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -O3 -fprefetch-loop-arrays -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d_o3_pref -lm -bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d_o3_pref -Job <5037> is submitted to default queue <batch>. -<<Waiting for dispatch ...>> -<<Starting on login1>> -Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh -Calculate reference solution and time with serial CPU execution. - 0, 0.249980 - 100, 0.246028 - 200, 0.242198 - 300, 0.238487 - 400, 0.234887 -Calculate current execution. - 0, 0.249980 - 100, 0.246028 - 200, 0.242198 - 300, 0.238487 - 400, 0.234887 -1.12user 0.00system 0:01.13elapsed 99%CPU (0avgtext+0avgdata 10880maxresident)k -256inputs+0outputs (0major+265minor)pagefaults 0swaps +<pre>rm -f poisson2d poisson2d*.o </pre> </div> </div> @@ -13900,10 +14389,12 @@ Calculate current execution. </div> <div class="cell border-box-sizing code_cell rendered"> <div class="input"> -<div class="prompt input_prompt">In [14]:</div> +<div class="prompt input_prompt">In [88]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make run_ofast_pref +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make poisson2d <span class="nv">CC</span><span class="o">=</span>gcc +<span class="o">!</span>make run +<span class="o">!</span>make l3missstats </pre></div> </div> @@ -13920,26 +14411,49 @@ Calculate current execution. <div class="output_subarea output_stream output_stdout output_text"> -<pre>/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast -fprefetch-loop-arrays -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d_ofast_pref -lm -bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d_ofast_pref -Job <5038> is submitted to default queue <batch>. +<pre>make: `poisson2d' is up to date. +bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d +Job <24911> is submitted to default queue <batch>. <<Waiting for dispatch ...>> <<Starting on login1>> -Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh -Calculate reference solution and time with serial CPU execution. - 0, 0.249980 - 100, 0.246028 - 200, 0.242198 - 300, 0.238487 - 400, 0.234887 +Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh +Calculate current execution. + 0, 0.249995 + 100, 0.248997 + 200, 0.248007 + 300, 0.247025 + 400, 0.246050 + 500, 0.245084 + 600, 0.244124 + 700, 0.243173 + 800, 0.242228 + 900, 0.241291 +2.39user 0.01system 0:02.40elapsed 100%CPU (0avgtext+0avgdata 24256maxresident)k +0inputs+0outputs (0major+480minor)pagefaults 0swaps +bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,r168a4 ./poisson2d +Job <24912> is submitted to default queue <batch>. +<<Waiting for dispatch ...>> +<<Starting on login1>> +Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh Calculate current execution. - 0, 0.249980 - 100, 0.246028 - 200, 0.242198 - 300, 0.238487 - 400, 0.234887 -0.77user 0.00system 0:00.77elapsed 99%CPU (0avgtext+0avgdata 10816maxresident)k -256inputs+0outputs (0major+264minor)pagefaults 0swaps + 0, 0.249995 + 100, 0.248997 + 200, 0.248007 + 300, 0.247025 + 400, 0.246050 + 500, 0.245084 + 600, 0.244124 + 700, 0.243173 + 800, 0.242228 + 900, 0.241291 + + Performance counter stats for './poisson2d': + + 8271503902 cycles:u + 481152478 r168a4:u + + 2.412224884 seconds time elapsed + </pre> </div> </div> @@ -13950,10 +14464,12 @@ Calculate current execution. </div> <div class="cell border-box-sizing code_cell rendered"> <div class="input"> -<div class="prompt input_prompt">In [15]:</div> +<div class="prompt input_prompt">In [98]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make run_o3_nopref +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make poisson2d_pref <span class="nv">CC</span><span class="o">=</span>gcc +<span class="o">!</span>make run +<span class="o">!</span>make l3missstats </pre></div> </div> @@ -13970,26 +14486,50 @@ Calculate current execution. <div class="output_subarea output_stream output_stdout output_text"> -<pre>/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -O3 -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d_o3_nopref -lm -bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d_o3_nopref -Job <5039> is submitted to default queue <batch>. +<pre>gcc -std=c99 -DUSE_DOUBLE -Ofast -mcpu=power9 -mvsx -maltivec -fprefetch-loop-arrays -fprefetch-loop-arrays poisson2d.c -o poisson2d_pref -lm +cp poisson2d_pref poisson2d +bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d +Job <24919> is submitted to default queue <batch>. <<Waiting for dispatch ...>> <<Starting on login1>> -Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh -Calculate reference solution and time with serial CPU execution. - 0, 0.249980 - 100, 0.246028 - 200, 0.242198 - 300, 0.238487 - 400, 0.234887 +Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh +Calculate current execution. + 0, 0.249995 + 100, 0.248997 + 200, 0.248007 + 300, 0.247025 + 400, 0.246050 + 500, 0.245084 + 600, 0.244124 + 700, 0.243173 + 800, 0.242228 + 900, 0.241291 +1.92user 0.00system 0:01.93elapsed 99%CPU (0avgtext+0avgdata 24256maxresident)k +256inputs+0outputs (0major+480minor)pagefaults 0swaps +bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,r168a4 ./poisson2d +Job <24920> is submitted to default queue <batch>. +<<Waiting for dispatch ...>> +<<Starting on login1>> +Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh Calculate current execution. - 0, 0.249980 - 100, 0.246028 - 200, 0.242198 - 300, 0.238487 - 400, 0.234887 -1.13user 0.00system 0:01.13elapsed 99%CPU (0avgtext+0avgdata 10944maxresident)k -256inputs+0outputs (0major+266minor)pagefaults 0swaps + 0, 0.249995 + 100, 0.248997 + 200, 0.248007 + 300, 0.247025 + 400, 0.246050 + 500, 0.245084 + 600, 0.244124 + 700, 0.243173 + 800, 0.242228 + 900, 0.241291 + + Performance counter stats for './poisson2d': + + 6586609284 cycles:u + 459879452 r168a4:u + + 1.925399505 seconds time elapsed + </pre> </div> </div> @@ -13997,13 +14537,23 @@ Calculate current execution. </div> </div> +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p><strong>TASK</strong>: Repeat the experiment with the <code>-O3</code> flag. Have a look at the <code>Makefile</code> and the outlined TODO. There's a position to easily adapt <code>-Ofast</code>→<code>-O3</code>!</p> + +</div> +</div> </div> <div class="cell border-box-sizing code_cell rendered"> <div class="input"> -<div class="prompt input_prompt">In [16]:</div> +<div class="prompt input_prompt">In [100]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make run_ofast_nopref +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make poisson2d <span class="nv">CC</span><span class="o">=</span>gcc -B +<span class="o">!</span>make run +<span class="o">!</span>make l3missstats </pre></div> </div> @@ -14020,59 +14570,65 @@ Calculate current execution. <div class="output_subarea output_stream output_stdout output_text"> -<pre>/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d_ofast_nopref -lm -bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d_ofast_nopref -Job <5040> is submitted to default queue <batch>. +<pre>gcc -std=c99 -DUSE_DOUBLE -O3 -mcpu=power9 -mvsx -maltivec poisson2d.c -o poisson2d -lm +bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d +Job <24923> is submitted to default queue <batch>. <<Waiting for dispatch ...>> <<Starting on login1>> -Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh -Calculate reference solution and time with serial CPU execution. - 0, 0.249980 - 100, 0.246028 - 200, 0.242198 - 300, 0.238487 - 400, 0.234887 +Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh Calculate current execution. - 0, 0.249980 - 100, 0.246028 - 200, 0.242198 - 300, 0.238487 - 400, 0.234887 -0.82user 0.00system 0:00.82elapsed 99%CPU (0avgtext+0avgdata 10816maxresident)k -256inputs+0outputs (0major+265minor)pagefaults 0swaps -</pre> -</div> -</div> + 0, 0.249995 + 100, 0.248997 + 200, 0.248007 + 300, 0.247025 + 400, 0.246050 + 500, 0.245084 + 600, 0.244124 + 700, 0.243173 + 800, 0.242228 + 900, 0.241291 +4.73user 0.00system 0:04.73elapsed 99%CPU (0avgtext+0avgdata 24256maxresident)k +256inputs+0outputs (0major+479minor)pagefaults 0swaps +bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,r168a4 ./poisson2d +Job <24924> is submitted to default queue <batch>. +<<Waiting for dispatch ...>> +<<Starting on login1>> +Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh +Calculate current execution. + 0, 0.249995 + 100, 0.248997 + 200, 0.248007 + 300, 0.247025 + 400, 0.246050 + 500, 0.245084 + 600, 0.244124 + 700, 0.243173 + 800, 0.242228 + 900, 0.241291 -</div> -</div> + Performance counter stats for './poisson2d': -</div> -<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> -</div><div class="inner_cell"> -<div class="text_cell_render border-box-sizing rendered_html"> -<p>Do you notice the impact difference with optimization levels? It's always important to carefully study the interplay of flags.</p> + 16445764669 cycles:u + 645094089 r168a4:u + 4.792567763 seconds time elapsed + +</pre> </div> </div> -</div> -<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> -</div><div class="inner_cell"> -<div class="text_cell_render border-box-sizing rendered_html"> -<h3 id="Part-B:-Analysis-of-Instructions">Part B: Analysis of Instructions<a class="anchor-link" href="#Part-B:-Analysis-of-Instructions">¶</a></h3><p>Compilation with the software prefetching flag causes the compiler to generate the <code>__dcbt</code> and <code>__dcbtst</code> instructions that prefetch memory values to L3.</p> -<p>Verify it using <code>objdump -lSd</code> on each file (<code>poisson2d_o3_pref</code>, <code>poisson2d_ofast_pref</code>, <code>poisson2d_o3_nopref</code>, <code>poisson2d_ofast_nopref</code>). You might want to grep for <code>dcb</code>.</p> </div> </div> + </div> <div class="cell border-box-sizing code_cell rendered"> <div class="input"> -<div class="prompt input_prompt">In [19]:</div> +<div class="prompt input_prompt">In [101]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="p">[</span><span class="s2">"poisson2d_o3_pref"</span><span class="p">,</span> <span class="s2">"poisson2d_ofast_pref"</span><span class="p">,</span> <span class="s2">"poisson2d_o3_nopref"</span><span class="p">,</span> <span class="s2">"poisson2d_ofast_nopref"</span><span class="p">]:</span> - <span class="nb">print</span><span class="p">(</span><span class="s2">"</span><span class="si">{}</span><span class="s2">:"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">f</span><span class="p">))</span> - <span class="n">objdump</span> <span class="o">-</span><span class="n">lSd</span> <span class="err">$</span><span class="n">f</span> <span class="o">|</span> <span class="n">grep</span> <span class="n">dcb</span> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make poisson2d_pref <span class="nv">CC</span><span class="o">=</span>gcc -B +<span class="o">!</span>make run +<span class="o">!</span>make l3missstats </pre></div> </div> @@ -14089,27 +14645,809 @@ Calculate current execution. <div class="output_subarea output_stream output_stdout output_text"> -<pre>poisson2d_o3_pref: -poisson2d_ofast_pref: - 10000da0: ec f1 00 7c dcbtst 0,r30 - 10000da4: 2c fa 00 7c dcbt 0,r31 - 10000da8: 2c 62 00 7c dcbt 0,r12 - 10000dac: 2c b2 00 7c dcbt 0,r22 - 10000dcc: 2c e2 00 7c dcbt 0,r28 - 10000dd0: 2c ea 00 7c dcbt 0,r29 - 100010b4: 2c 62 00 7c dcbt 0,r12 - 100010b8: 2c 5a 00 7c dcbt 0,r11 - 100010c4: ec 19 00 7c dcbtst 0,r3 - 100010cc: 2c 22 00 7c dcbt 0,r4 - 100010d0: 2c ea 00 7c dcbt 0,r29 - 100010d4: 2c f2 00 7c dcbt 0,r30 - 100010dc: 2c fa 00 7c dcbt 0,r31 -poisson2d_o3_nopref: -poisson2d_ofast_nopref: -</pre> -</div> -</div> - +<pre>gcc -std=c99 -DUSE_DOUBLE -O3 -mcpu=power9 -mvsx -maltivec -fprefetch-loop-arrays poisson2d.c -o poisson2d_pref -lm +bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d +Job <24925> is submitted to default queue <batch>. +<<Waiting for dispatch ...>> +<<Starting on login1>> +Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh +Calculate current execution. + 0, 0.249995 + 100, 0.248997 + 200, 0.248007 + 300, 0.247025 + 400, 0.246050 + 500, 0.245084 + 600, 0.244124 + 700, 0.243173 + 800, 0.242228 + 900, 0.241291 +4.74user 0.00system 0:04.74elapsed 99%CPU (0avgtext+0avgdata 24256maxresident)k +0inputs+0outputs (0major+480minor)pagefaults 0swaps +bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,r168a4 ./poisson2d +Job <24926> is submitted to default queue <batch>. +<<Waiting for dispatch ...>> +<<Starting on login1>> +Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh +Calculate current execution. + 0, 0.249995 + 100, 0.248997 + 200, 0.248007 + 300, 0.247025 + 400, 0.246050 + 500, 0.245084 + 600, 0.244124 + 700, 0.243173 + 800, 0.242228 + 900, 0.241291 + + Performance counter stats for './poisson2d': + + 16239159454 cycles:u + 631061431 r168a4:u + + 4.730144897 seconds time elapsed + +</pre> +</div> +</div> + +</div> +</div> + +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p>Do you notice the impact difference with optimization levels? At what optimization level does software prefetching help the most?</p> + +</div> +</div> +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p>Observing the results, we see that SW Prefetching seems to help at <code>-Ofast</code> but not at <code>-O3</code>. We can use the steps described in the the next section to verify that the compiler has not inserted any SW prefetch operations at<code>-O3</code> at all. That is because in the <code>-O3</code> binary the time is dominated by <code>__fmax</code> call which causes the compiler to come to the conclusion that whatever benefit we obtain by adding SW prefetch will be overshadowed by the penalty of <code>fmax()</code> +GCC may add further loop optimizations such as unrolling upon invocation of <code>–fprefetch-loop-arrays</code>.</p> + +</div> +</div> +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<h3 id="Part-B:-Analysis-of-Instructions">Part B: Analysis of Instructions<a class="anchor-link" href="#Part-B:-Analysis-of-Instructions">¶</a></h3><p>Compilation of the <code>-Ofast</code> binary with the software prefetching flag causes the compiler to generate the <code>dcb*</code> instructions that prefetch memory values to L3.</p> + +</div> +</div> +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p><strong>TASK</strong>: +Run <code>$(SC19_SUBMIT_CMD) objdump -lSd</code> on each binary file (<code>-O3</code>, <code>-Ofast</code> with prefetch/no prefetch). +Look for instructions beginning with <code>dcb</code> +At what optimization levels does the compiler generate software prefetching instructions?</p> + +</div> +</div> +</div> +<div class="cell border-box-sizing code_cell rendered"> +<div class="input"> +<div class="prompt input_prompt">In [114]:</div> +<div class="inner_cell"> + <div class="input_area"> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make <span class="nv">CC</span><span class="o">=</span>gcc -B poisson2d_pref +<span class="o">!</span>objdump -lSd ./poisson2d_pref > poisson2d.dis +</pre></div> + + </div> +</div> +</div> + +<div class="output_wrapper"> +<div class="output"> + + +<div class="output_area"> + + <div class="prompt"></div> + + +<div class="output_subarea output_stream output_stdout output_text"> +<pre>gcc -std=c99 -DUSE_DOUBLE -Ofast -mcpu=power9 -mvsx -maltivec -fprefetch-loop-arrays poisson2d.c -o poisson2d_pref -lm +</pre> +</div> +</div> + +</div> +</div> + +</div> +<div class="cell border-box-sizing code_cell rendered"> +<div class="input"> +<div class="prompt input_prompt">In [116]:</div> +<div class="inner_cell"> + <div class="input_area"> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>grep dcb poisson2d.dis +</pre></div> + + </div> +</div> +</div> + +<div class="output_wrapper"> +<div class="output"> + + +<div class="output_area"> + + <div class="prompt"></div> + + +<div class="output_subarea output_stream output_stdout output_text"> +<pre> 10000b28: 2c d2 00 7c dcbt 0,r26 + 10000b30: 2c ba 00 7c dcbt 0,r23 + 10000b38: 2c b2 00 7c dcbt 0,r22 + 10000b50: 2c d2 00 7c dcbt 0,r26 + 10000b58: ec b9 00 7c dcbtst 0,r23 + 10000b80: 2c d2 00 7c dcbt 0,r26 + 10000e64: 2c 92 00 7c dcbt 0,r18 + 10000e68: 2c 9a 00 7c dcbt 0,r19 + 10000e6c: 2c a2 00 7c dcbt 0,r20 + 10000e70: 2c aa 00 7c dcbt 0,r21 + 10000e7c: 2c b2 00 7c dcbt 0,r22 + 10000e80: 2c d2 00 7c dcbt 0,r26 + 10000e94: ec b9 00 7c dcbtst 0,r23 +</pre> +</div> +</div> + +</div> +</div> + +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<h3 id="Part-C:-Changing-Values-of-DSCR-via-compiler-flags">Part C: Changing Values of DSCR via compiler flags<a class="anchor-link" href="#Part-C:-Changing-Values-of-DSCR-via-compiler-flags">¶</a></h3><p>This task requires using the IBM XL compiler. It should be already in your environment.</p> +<p>We saw the impact of software prefetching in the previous subsection. +In certain cases, tuning the hardware prefetcher through compiler options can also help improve performance. +In this exercise we shall see some compiler options that can be used to modify the DSCR value which controls aggressiveness of prefetching. It can be also used to turn off hardware prefetching.</p> +<p>IBM XL compiler has an option <code>-qprefetch=dscr=<val></code> that can be used for this purpose. +Compiling with <code>-qprefetch=dscr=1</code> turns off the prefetcher. One can give various values such as <code>-qprefetch=dscr=4</code>, <code>-qprefetch=dscr=7</code> etc. to control aggressiveness of prefetching.</p> +<p>For this exercise we use <code>make CC=xlc_r</code> to illustrate the performance impact.</p> +<p><strong>Task</strong> Generate a XL-compiled binary by compiling using the following cells. After you've generated a baseline, start editing the <code>Makefile</code>: Add <code>qprefetch=dscr=1</code> to the <code>CFLAGS</code> and rebuild the application and note the performance. Which one is faster?</p> +<p>In general, applications benefit with the default settings of hardware DSCR register (<code>-qprefetch=dscr=0</code>). However, certain applications also benefit with prefetching turned off.</p> +<p>It is to be noted that DSCR values are highly sensitive to the application. One value that works well for Application A may not help Application B.</p> + +</div> +</div> +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p>Measure performance of the application compiled with XL at default DSCR value</p> + +</div> +</div> +</div> +<div class="cell border-box-sizing code_cell rendered"> +<div class="input"> +<div class="prompt input_prompt">In [117]:</div> +<div class="inner_cell"> + <div class="input_area"> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make <span class="nv">CC</span><span class="o">=</span>xlc_r -B poisson2d +<span class="o">!</span>make run +</pre></div> + + </div> +</div> +</div> + +<div class="output_wrapper"> +<div class="output"> + + +<div class="output_area"> + + <div class="prompt"></div> + + +<div class="output_subarea output_stream output_stdout output_text"> +<pre>xlc_r -std=c99 -DUSE_DOUBLE -Ofast -qarch=pwr9 -qtune=pwr9 -DINLINE_LIBS poisson2d.c -o poisson2d -lm + 1500-036: (I) The NOSTRICT option (default at OPT(3)) has the potential to alter the semantics of a program. Please refer to documentation on the STRICT/NOSTRICT option for more information. +bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d +Job <24927> is submitted to default queue <batch>. +<<Waiting for dispatch ...>> +<<Starting on login1>> +Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh +Calculate current execution. + 0, 0.249995 + 100, 50.149062 + 200, 99.849327 + 300, 149.352369 + 400, 198.659746 + 500, 247.773000 + 600, 296.693652 + 700, 345.423208 + 800, 393.963155 + 900, 442.314962 +2.26user 0.00system 0:02.27elapsed 99%CPU (0avgtext+0avgdata 24256maxresident)k +256inputs+0outputs (0major+477minor)pagefaults 0swaps +</pre> +</div> +</div> + +</div> +</div> + +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p>Measure performance of the application compiled with XL with DSCR value turned off</p> + +</div> +</div> +</div> +<div class="cell border-box-sizing code_cell rendered"> +<div class="input"> +<div class="prompt input_prompt">In [9]:</div> +<div class="inner_cell"> + <div class="input_area"> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make poisson2d_dscr <span class="nv">CC</span><span class="o">=</span>xlc_r -B +<span class="o">!</span>make run +</pre></div> + + </div> +</div> +</div> + +<div class="output_wrapper"> +<div class="output"> + + +<div class="output_area"> + + <div class="prompt"></div> + + +<div class="output_subarea output_stream output_stdout output_text"> +<pre>xlc_r -std=c99 -DUSE_DOUBLE -Ofast -qarch=pwr9 -qtune=pwr9 -DINLINE_LIBS -qprefetch=dscr=1 poisson2d.c -o poisson2d_dscr -lm + 1500-036: (I) The NOSTRICT option (default at OPT(3)) has the potential to alter the semantics of a program. Please refer to documentation on the STRICT/NOSTRICT option for more information. +bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d +Job <24929> is submitted to default queue <batch>. +<<Waiting for dispatch ...>> +<<Starting on login1>> +Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh +Calculate current execution. + 0, 0.249995 + 100, 0.248997 + 200, 0.248007 + 300, 0.247025 + 400, 0.246050 + 500, 0.245084 + 600, 0.244124 + 700, 0.243173 + 800, 0.242228 + 900, 0.241291 +4.58user 0.00system 0:04.59elapsed 99%CPU (0avgtext+0avgdata 24192maxresident)k +0inputs+0outputs (0major+476minor)pagefaults 0swaps +</pre> +</div> +</div> + +</div> +</div> + +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p>Does Hardware prefetcher help this application? How much impact do you see when you turn off the hardware prefetcher?</p> + +</div> +</div> +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p>The DSCR register controls the operation of the HW Prefetcher on POWER9. It can be modified in the command line by <code>ppc64_cpu --dscr=<value></code>. However this needs admin privileges. IBM XL offers a compiler flag to set the value through the compiler. <code>-qprefetch=dscr=1</code> turns off the prefetcher. Observing the results we see that the performance without the HW prefetcher is twice as bad as that with default prefetching. So we can conclude that Prefetching helps the Jacobi application.</p> + +</div> +</div> +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<h4 id="References">References<a class="anchor-link" href="#References">¶</a></h4><ol> +<li><a href="https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html">https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html</a></li> +<li><a href="https://www.gnu.org/software/gcc/projects/prefetch.html">https://www.gnu.org/software/gcc/projects/prefetch.html</a></li> +<li><a href="https://openpowerfoundation.org/?resource_lib=power-isa-version-3-0">https://openpowerfoundation.org/?resource_lib=power-isa-version-3-0</a></li> +</ol> + +</div> +</div> +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p><a href="#top">Back to Top</a></p> +<hr> + +</div> +</div> +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<h2 id="Task-3:-OpenMP">Task 3: OpenMP<a class="anchor-link" href="#Task-3:-OpenMP">¶</a></h2><p><a name="task3"></a></p> +<h3 id="Overview">Overview<a class="anchor-link" href="#Overview">¶</a></h3><p>We add OpenMP shared-memory parallelism to the application. Also, we study the effect of binding the multi-thread processes to certain cores on the resulting application performance. We do this study for both GCC and XL compilers inorder to learn about the appropriate options that need to be used. +First, we need to change directory to that of Task3. For Task 3 we modify poisson2d.c to invoke an exact copy of the main jacobi loop which is <code>poisson2d_reference</code>. We parallelize only the main loop but not <code>poisson2d_reference</code>. The speedup is the performance gain seen in the main loop as compared to the reference loop.</p> + +</div> +</div> +</div> +<div class="cell border-box-sizing code_cell rendered"> +<div class="input"> +<div class="prompt input_prompt">In [10]:</div> +<div class="inner_cell"> + <div class="input_area"> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">%</span><span class="k">cd</span> ../Task3 +</pre></div> + + </div> +</div> +</div> + +<div class="output_wrapper"> +<div class="output"> + + +<div class="output_area"> + + <div class="prompt"></div> + + +<div class="output_subarea output_stream output_stdout output_text"> +<pre>/autofs/nccsopen-svm1_home/aherten/SC19-Tutorial/3-Optimizing_POWER/Handson/Task3 +</pre> +</div> +</div> + +</div> +</div> + +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<h3 id="Part-A:-Implement-OpenMP-Pragmas;-Compilation">Part A: Implement OpenMP Pragmas; Compilation<a class="anchor-link" href="#Part-A:-Implement-OpenMP-Pragmas;-Compilation">¶</a></h3><p><strong>Task</strong>: Please add the correct OpenMP directives to poisson2d.c and compilations flags in the Makefile to enable OpenMP with GCC and XL compilers.</p> +<ul> +<li><strong>Directives</strong>: Look at the TODOs in <a href="poisson2d.c"><code>poisson2d.c</code></a> to add OpenMP parallelism. The pragmas in question are <code>#pragma omp parallel for</code> (and once it's <code>#pragma omp parallel for reduction(max:error)</code> – can you guess where?)</li> +<li><strong>Compilation</strong>: Please add compilation flags enabling OpenMP in GCC and XL to the <code>Makefile</code>. For GCC, we need to add <code>-fopenmp</code> and the application needs to be linked with <code>-lgomp</code>. For XL, we need to add <code>-qsmp=omp</code> to the list of compilation flags. </li> +</ul> +<p>Afterwards, compile and run the application with the following commands.</p> + +</div> +</div> +</div> +<div class="cell border-box-sizing code_cell rendered"> +<div class="input"> +<div class="prompt input_prompt">In [39]:</div> +<div class="inner_cell"> + <div class="input_area"> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make poisson2d <span class="nv">CC</span><span class="o">=</span>gcc +</pre></div> + + </div> +</div> +</div> + +<div class="output_wrapper"> +<div class="output"> + + +<div class="output_area"> + + <div class="prompt"></div> + + +<div class="output_subarea output_stream output_stdout output_text"> +<pre>gcc -c -std=c99 -DUSE_DOUBLE -O3 -mcpu=power9 -mvsx -maltivec -fopenmp -lgomp poisson2d_reference.c -o poisson2d_reference.o -lm +gcc -std=c99 -DUSE_DOUBLE -O3 -mcpu=power9 -mvsx -maltivec -fopenmp -lgomp poisson2d.c poisson2d_reference.o -o poisson2d -lm +</pre> +</div> +</div> + +</div> +</div> + +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p>The command to submit a job to the batch system is prepared in an environment variable <code>$SC19_SUBMIT_CMD</code>; use it together with <code>eval</code>. In the following cell, it is shown how to invoke the application using the batch system.</p> + +</div> +</div> +</div> +<div class="cell border-box-sizing code_cell rendered"> +<div class="input"> +<div class="prompt input_prompt">In [40]:</div> +<div class="inner_cell"> + <div class="input_area"> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span><span class="nb">eval</span> <span class="nv">$SC19_SUBMIT_CMD</span> ./poisson2d <span class="m">1000</span> <span class="m">1000</span> <span class="m">1000</span> +</pre></div> + + </div> +</div> +</div> + +<div class="output_wrapper"> +<div class="output"> + + +<div class="output_area"> + + <div class="prompt"></div> + + +<div class="output_subarea output_stream output_stdout output_text"> +<pre>Job <24951> is submitted to default queue <batch>. +<<Waiting for dispatch ...>> +<<Starting on login1>> +Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh +Calculate reference solution and time with serial CPU execution. + 0, 0.249995 + 100, 0.248997 + 200, 0.248007 + 300, 0.247025 + 400, 0.246050 + 500, 0.245084 + 600, 0.244124 + 700, 0.243173 + 800, 0.242228 + 900, 0.241291 +Calculate current execution. + 0, 0.249995 + 100, 0.248997 + 200, 0.248007 + 300, 0.247025 + 400, 0.246050 + 500, 0.245084 + 600, 0.244124 + 700, 0.243173 + 800, 0.242228 + 900, 0.241291 +1000x1000: Ref: 4.7430 s, This: 3.9363 s, speedup: 1.20 +</pre> +</div> +</div> + +</div> +</div> + +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p>Inorder to run the parallel application, we need to set the number of threads using <code>OMP_NUM_THREADS</code> +What is the best performance you can reach by setting the number of threads via <code>OMP_NUM_THREADS=N</code> with <code>N</code> being the number of threads? Feel free to play around with the command in the following cell, using 1 thread as an example.<br> +We added <code>--bind none</code> to prevent <code>jsrun</code>, the scheduler of Ascent, from overlaying binding options. Also, we use <code>-c ALL_CPUS</code> to make all CPUs on the compute nodes available to you.</p> + +</div> +</div> +</div> +<div class="cell border-box-sizing code_cell rendered"> +<div class="input"> +<div class="prompt input_prompt">In [41]:</div> +<div class="inner_cell"> + <div class="input_area"> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span><span class="nv">OMP_NUM_THREADS</span><span class="o">=</span><span class="m">1</span> <span class="nv">$$</span>SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d <span class="m">1000</span> <span class="m">1000</span> <span class="m">1000</span> <span class="p">|</span> grep speedup +</pre></div> + + </div> +</div> +</div> + +<div class="output_wrapper"> +<div class="output"> + + +<div class="output_area"> + + <div class="prompt"></div> + + +<div class="output_subarea output_stream output_stdout output_text"> +<pre><<Waiting for dispatch ...>> +<<Starting on login1>> +1000x1000: Ref: 4.7288 s, This: 4.9791 s, speedup: 0.95 +</pre> +</div> +</div> + +</div> +</div> + +</div> +<div class="cell border-box-sizing code_cell rendered"> +<div class="input"> +<div class="prompt input_prompt">In [42]:</div> +<div class="inner_cell"> + <div class="input_area"> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span><span class="nv">OMP_NUM_THREADS</span><span class="o">=</span><span class="m">2</span> <span class="nv">$$</span>SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d <span class="m">1000</span> <span class="m">1000</span> <span class="m">1000</span> <span class="p">|</span> grep speedup +</pre></div> + + </div> +</div> +</div> + +<div class="output_wrapper"> +<div class="output"> + + +<div class="output_area"> + + <div class="prompt"></div> + + +<div class="output_subarea output_stream output_stdout output_text"> +<pre><<Waiting for dispatch ...>> +<<Starting on login1>> +1000x1000: Ref: 4.7125 s, This: 2.4914 s, speedup: 1.89 +</pre> +</div> +</div> + +</div> +</div> + +</div> +<div class="cell border-box-sizing code_cell rendered"> +<div class="input"> +<div class="prompt input_prompt">In [35]:</div> +<div class="inner_cell"> + <div class="input_area"> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span><span class="nv">OMP_NUM_THREADS</span><span class="o">=</span><span class="m">4</span> <span class="nv">$$</span>SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d <span class="m">1000</span> <span class="m">1000</span> <span class="m">1000</span> <span class="p">|</span> grep speedup +</pre></div> + + </div> +</div> +</div> + +<div class="output_wrapper"> +<div class="output"> + + +<div class="output_area"> + + <div class="prompt"></div> + + +<div class="output_subarea output_stream output_stdout output_text"> +<pre><<Waiting for dispatch ...>> +<<Starting on login1>> +1000x1000: Ref: 2.1065 s, This: 1.3836 s, speedup: 1.52 +</pre> +</div> +</div> + +</div> +</div> + +</div> +<div class="cell border-box-sizing code_cell rendered"> +<div class="input"> +<div class="prompt input_prompt">In [21]:</div> +<div class="inner_cell"> + <div class="input_area"> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span><span class="nv">OMP_NUM_THREADS</span><span class="o">=</span><span class="m">8</span> <span class="nv">$$</span>SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d <span class="m">1000</span> <span class="m">1000</span> <span class="m">1000</span> <span class="p">|</span> grep speedup +</pre></div> + + </div> +</div> +</div> + +<div class="output_wrapper"> +<div class="output"> + + +<div class="output_area"> + + <div class="prompt"></div> + + +<div class="output_subarea output_stream output_stdout output_text"> +<pre><<Waiting for dispatch ...>> +<<Starting on login1>> +1000x1000: Ref: 2.3868 s, This: 0.5272 s, speedup: 4.53 +</pre> +</div> +</div> + +</div> +</div> + +</div> +<div class="cell border-box-sizing code_cell rendered"> +<div class="input"> +<div class="prompt input_prompt">In [22]:</div> +<div class="inner_cell"> + <div class="input_area"> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span><span class="nv">OMP_NUM_THREADS</span><span class="o">=</span><span class="m">10</span> <span class="nv">$$</span>SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d <span class="m">1000</span> <span class="m">1000</span> <span class="m">1000</span> <span class="p">|</span> grep speedup +</pre></div> + + </div> +</div> +</div> + +<div class="output_wrapper"> +<div class="output"> + + +<div class="output_area"> + + <div class="prompt"></div> + + +<div class="output_subarea output_stream output_stdout output_text"> +<pre><<Waiting for dispatch ...>> +<<Starting on login1>> +1000x1000: Ref: 2.3912 s, This: 0.4612 s, speedup: 5.18 +</pre> +</div> +</div> + +</div> +</div> + +</div> +<div class="cell border-box-sizing code_cell rendered"> +<div class="input"> +<div class="prompt input_prompt">In [23]:</div> +<div class="inner_cell"> + <div class="input_area"> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span><span class="nv">OMP_NUM_THREADS</span><span class="o">=</span><span class="m">20</span> <span class="nv">$$</span>SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d <span class="m">1000</span> <span class="m">1000</span> <span class="m">1000</span> <span class="p">|</span> grep speedup +</pre></div> + + </div> +</div> +</div> + +<div class="output_wrapper"> +<div class="output"> + + +<div class="output_area"> + + <div class="prompt"></div> + + +<div class="output_subarea output_stream output_stdout output_text"> +<pre><<Waiting for dispatch ...>> +<<Starting on login1>> +1000x1000: Ref: 2.3864 s, This: 0.4037 s, speedup: 5.91 +</pre> +</div> +</div> + +</div> +</div> + +</div> +<div class="cell border-box-sizing code_cell rendered"> +<div class="input"> +<div class="prompt input_prompt">In [24]:</div> +<div class="inner_cell"> + <div class="input_area"> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span><span class="nv">OMP_NUM_THREADS</span><span class="o">=</span><span class="m">40</span> <span class="nv">$$</span>SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d <span class="m">1000</span> <span class="m">1000</span> <span class="m">1000</span> <span class="p">|</span> grep speedup +</pre></div> + + </div> +</div> +</div> + +<div class="output_wrapper"> +<div class="output"> + + +<div class="output_area"> + + <div class="prompt"></div> + + +<div class="output_subarea output_stream output_stdout output_text"> +<pre><<Waiting for dispatch ...>> +<<Starting on login1>> +1000x1000: Ref: 2.3773 s, This: 0.3045 s, speedup: 7.81 +</pre> +</div> +</div> + +</div> +</div> + +</div> +<div class="cell border-box-sizing code_cell rendered"> +<div class="input"> +<div class="prompt input_prompt">In [25]:</div> +<div class="inner_cell"> + <div class="input_area"> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span><span class="nv">OMP_NUM_THREADS</span><span class="o">=</span><span class="m">80</span> <span class="nv">$$</span>SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d <span class="m">1000</span> <span class="m">1000</span> <span class="m">1000</span> <span class="p">|</span> grep speedup +</pre></div> + + </div> +</div> +</div> + +<div class="output_wrapper"> +<div class="output"> + + +<div class="output_area"> + + <div class="prompt"></div> + + +<div class="output_subarea output_stream output_stdout output_text"> +<pre><<Waiting for dispatch ...>> +<<Starting on login1>> +1000x1000: Ref: 2.3819 s, This: 0.3081 s, speedup: 7.73 +</pre> +</div> +</div> + +</div> +</div> + +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<h3 id="Part-B:-Bindings">Part B: Bindings<a class="anchor-link" href="#Part-B:-Bindings">¶</a></h3><p>Different CPU architectures and models come with different configuration of cores. The configuration plays an important role in the run time of the application. We need to optimize for it!</p> +<p>There are applications which can be used to determine the configuration of the processor. Among those are:</p> +<ul> +<li><code>lscpu</code>: Can be used to determine the number of sockets, number of cores, and numb of threads. It gives a very good overview and is available on most Linux systems.</li> +<li><code>ppc64_cpu --smt</code>: Specifically for POWER, this tool can give information about the number of simulations threads running per core (<em>SMT</em>, Simulataion Multi-Threading).</li> +</ul> +<p>Run <code>ppc64_cpu --smt</code> to find out about the threading configuration of Ascent!</p> + +</div> +</div> +</div> +<div class="cell border-box-sizing code_cell rendered"> +<div class="input"> +<div class="prompt input_prompt">In [55]:</div> +<div class="inner_cell"> + <div class="input_area"> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span><span class="nb">eval</span> <span class="nv">$SC19_SUBMIT_CMD</span> ppc64_cpu --smt +</pre></div> + + </div> +</div> +</div> + +<div class="output_wrapper"> +<div class="output"> + + +<div class="output_area"> + + <div class="prompt"></div> + + +<div class="output_subarea output_stream output_stdout output_text"> +<pre>Job <24465> is submitted to default queue <batch>. +<<Waiting for dispatch ...>> +<<Starting on login1>> +SMT=4 +</pre> +</div> +</div> + </div> </div> @@ -14117,18 +15455,22 @@ poisson2d_ofast_nopref: <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<p>If you feel up to the task, you can study the number of L3 cache misses using the corresponding performance counter, <code>PM_L3_MISS</code>. Either use your knowledge from Hands-On 1, or use the following call to <code>perf</code>, in which we already converted the named counter to a raw counter address.</p> +<p>There are more sources information available</p> +<ul> +<li><code>/proc/cpuinfo</code>: Holds information about virtual cores, including model and clock speed. Available on most Linux system. Usually used together with <code>cat</code></li> +<li><code>/sys/devices/system/cpu/cpu0/topology/thread_siblings_list</code>: Holds information about thread siblings for given CPU core (<code>cpu0</code> in this case). Use it to find out which thread is mapped to which core.</li> +</ul> </div> </div> </div> <div class="cell border-box-sizing code_cell rendered"> <div class="input"> -<div class="prompt input_prompt">In [35]:</div> +<div class="prompt input_prompt">In [36]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="p">[</span><span class="s2">"poisson2d_ofast_nopref"</span><span class="p">,</span> <span class="s2">"poisson2d_ofast_pref"</span><span class="p">]:</span> - <span class="o">!</span><span class="nb">eval</span> <span class="nv">$$</span>SC18_SUBMIT_CMD perf stat -e cycles,r168a4 ./<span class="nv">$f</span> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span><span class="nv">$$</span>SC19_SUBMIT_CMD cat /sys/devices/system/cpu/cpu0/topology/thread_siblings_list +<span class="o">!</span><span class="nv">$$</span>SC19_SUBMIT_CMD cat /sys/devices/system/cpu/cpu5/topology/thread_siblings_list </pre></div> </div> @@ -14145,54 +15487,14 @@ poisson2d_ofast_nopref: <div class="output_subarea output_stream output_stdout output_text"> -<pre>Job <5048> is submitted to default queue <batch>. +<pre>Job <24949> is submitted to default queue <batch>. <<Waiting for dispatch ...>> <<Starting on login1>> -Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh -Calculate reference solution and time with serial CPU execution. - 0, 0.249980 - 100, 0.246028 - 200, 0.242198 - 300, 0.238487 - 400, 0.234887 -Calculate current execution. - 0, 0.249980 - 100, 0.246028 - 200, 0.242198 - 300, 0.238487 - 400, 0.234887 - - Performance counter stats for './poisson2d_ofast_nopref': - - 2829292169 cycles:u - 136018637 r168a4:u - - 0.826136863 seconds time elapsed - -Job <5049> is submitted to default queue <batch>. +0-3 +Job <24950> is submitted to default queue <batch>. <<Waiting for dispatch ...>> <<Starting on login1>> -Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh -Calculate reference solution and time with serial CPU execution. - 0, 0.249980 - 100, 0.246028 - 200, 0.242198 - 300, 0.238487 - 400, 0.234887 -Calculate current execution. - 0, 0.249980 - 100, 0.246028 - 200, 0.242198 - 300, 0.238487 - 400, 0.234887 - - Performance counter stats for './poisson2d_ofast_pref': - - 2654990243 cycles:u - 128824827 r168a4:u - - 0.775593651 seconds time elapsed - +4-7 </pre> </div> </div> @@ -14204,10 +15506,10 @@ Calculate current execution. <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<h4 id="References">References<a class="anchor-link" href="#References">¶</a></h4><ol> -<li><a href="https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html">https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html</a></li> -<li><a href="https://www.gnu.org/software/gcc/projects/prefetch.html">https://www.gnu.org/software/gcc/projects/prefetch.html</a></li> -</ol> +<p>There are various environment variables available within OpenMP (some specific to GCC) that hold across compilers to specify binding of threads to cores. See, for instance, the <a href="https://www.openmp.org/spec-html/5.0/openmpse53.html">OMP_PLACES environment Variable</a>. We also have a GNU specific variable which can also be used to control affinity - <code>GOMP_CPU_AFFINITY</code>. Setting <code>GOMP_CPU_AFFINITY</code> is specific to GCC binaries but it internally serves the same function as setting <code>OMP_PLACES</code>.</p> +<p><strong>Task</strong>: Run the application enabled with OpenMP from Part A with different binding configurations. Make sure to at least run a) binding all threads to a single core and b) binding threads to different cores.</p> +<p>Adapt the following command with your configuration – or follow along accordingly in the non-interactive version of the Notebook.</p> +<p>What's your maximum speedup?</p> </div> </div> @@ -14215,8 +15517,7 @@ Calculate current execution. <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<p><a href="#top">Back to Top</a></p> -<hr> +<p>Running with two different configurations 1) Binding all threads to the same core 2) Binding all threads to different cores, we see a higher speedup in case of binding all threads to different cores.</p> </div> </div> @@ -14224,19 +15525,19 @@ Calculate current execution. <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<h2 id="Task-3:-OpenMP">Task 3: OpenMP<a class="anchor-link" href="#Task-3:-OpenMP">¶</a></h2><p><a name="task3"></a></p> -<h3 id="Overview">Overview<a class="anchor-link" href="#Overview">¶</a></h3><p>We add OpenMP shared-memory parallelism to the application. Also, we study the effect of binding the multi-thread processes to certain cores.</p> -<p>First, we need to change directory to that of Task3.</p> +<p>Using <code>OMP_PLACES</code> for binding, and using some magical Python-Bash interplay:</p> </div> </div> </div> <div class="cell border-box-sizing code_cell rendered"> <div class="input"> -<div class="prompt input_prompt">In [1]:</div> +<div class="prompt input_prompt">In [43]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">%</span><span class="k">cd</span> ../Task3 +<div class=" highlight hl-ipython3"><pre><span></span><span class="k">for</span> <span class="n">affinity</span> <span class="ow">in</span> <span class="p">[</span><span class="s2">"</span><span class="si">{0}</span><span class="s2">,</span><span class="si">{1}</span><span class="s2">,</span><span class="si">{2}</span><span class="s2">,</span><span class="si">{3}</span><span class="s2">"</span><span class="p">,</span> <span class="s2">"</span><span class="si">{0}</span><span class="s2">,</span><span class="si">{5}</span><span class="s2">,</span><span class="si">{9}</span><span class="s2">,</span><span class="si">{13}</span><span class="s2">"</span><span class="p">]:</span> + <span class="nb">print</span><span class="p">(</span><span class="s2">"Affinity: </span><span class="si">{}</span><span class="s2">"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">affinity</span><span class="p">))</span> + <span class="o">!</span><span class="nb">eval</span> <span class="nv">OMP_DISPLAY_ENV</span><span class="o">=</span><span class="nb">true</span> <span class="nv">OMP_PLACES</span><span class="o">=</span><span class="nv">$affinity</span> <span class="nv">OMP_NUM_THREADS</span><span class="o">=</span><span class="m">4</span> <span class="nv">$$</span>SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d <span class="m">1000</span> <span class="m">1000</span> <span class="m">1000</span> <span class="p">|</span> grep <span class="s2">"OMP_PLACES\|speedup"</span> </pre></div> </div> @@ -14253,7 +15554,16 @@ Calculate current execution. <div class="output_subarea output_stream output_stdout output_text"> -<pre>/autofs/nccsopen-svm1_home/aherten/SC18-Tutorial/3-Optimizing_POWER/Handson/Task3 +<pre>Affinity: {0},{1},{2},{3} +<<Waiting for dispatch ...>> +<<Starting on login1>> + OMP_PLACES = '{0},{1},{2},{3}' +1000x1000: Ref: 4.7315 s, This: 3.9090 s, speedup: 1.21 +Affinity: {0},{5},{9},{13} +<<Waiting for dispatch ...>> +<<Starting on login1>> + OMP_PLACES = '{0},{5},{9},{13}' +1000x1000: Ref: 4.6485 s, This: 1.2829 s, speedup: 3.62 </pre> </div> </div> @@ -14265,23 +15575,19 @@ Calculate current execution. <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<h3 id="Part-A:-Implement-OpenMP-Pragmas;-Compilation">Part A: Implement OpenMP Pragmas; Compilation<a class="anchor-link" href="#Part-A:-Implement-OpenMP-Pragmas;-Compilation">¶</a></h3><p><strong>Task</strong>: Please add the correct OpenMP pragmas to the source code and compilations flags to enable OpenMP.</p> -<ul> -<li><strong>pragmas</strong>: Look at the TODOs in <a href="/edit/Task3/poisson2d.c"><code>poisson2d.c</code></a> to add OpenMP parallelism. The pragmas in question are <code>#pragma omp parallel for</code></li> -<li><strong>Compilation</strong>: Please add compilation flags enabling OpenMP in GCC to the <a href="/edit/Task3/Makefile">Makefile</a>. The flag in question is <code>-fopenmp</code>.</li> -</ul> -<p>Edit the files with the links above if you are running the interactive version of the Notebook or navigate to <code>poisson2d.c</code> and <code>Makefile</code> yourself in case you run the non-interactive version.</p> -<p>Afterwards, compile and run the application with the following cells. Non-interactive: Follow along accordingly in the shell.</p> +<p>In this case, we carry out the same experiment using <code>GOMP_CPU_AFFINITY</code> which essentially sets the same environment variable <code>OMP_PLACES</code>. Running with two different configurations 1) Binding all threads to the same core 2) Binding all threads to different cores, we see a higher speedup in case of binding all threads to different cores.</p> </div> </div> </div> <div class="cell border-box-sizing code_cell rendered"> <div class="input"> -<div class="prompt input_prompt">In [37]:</div> +<div class="prompt input_prompt">In [44]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make poisson2d +<div class=" highlight hl-ipython3"><pre><span></span><span class="k">for</span> <span class="n">affinity</span> <span class="ow">in</span> <span class="p">[</span><span class="s2">"0,1,2,3"</span><span class="p">,</span> <span class="s2">"0,5,9,13"</span><span class="p">]:</span> + <span class="nb">print</span><span class="p">(</span><span class="s2">"Affinity: </span><span class="si">{}</span><span class="s2">"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">affinity</span><span class="p">))</span> + <span class="o">!</span><span class="nb">eval</span> <span class="nv">OMP_DISPLAY_ENV</span><span class="o">=</span><span class="nb">true</span> <span class="nv">GOMP_CPU_AFFINITY</span><span class="o">=</span><span class="nv">$affinity</span> <span class="nv">OMP_NUM_THREADS</span><span class="o">=</span><span class="m">4</span> <span class="nv">$$</span>SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d <span class="m">1000</span> <span class="m">1000</span> <span class="m">1000</span> <span class="p">|</span> grep <span class="s2">"OMP_PLACES\|speedup"</span> </pre></div> </div> @@ -14298,8 +15604,16 @@ Calculate current execution. <div class="output_subarea output_stream output_stdout output_text"> -<pre>/sw/ascent/gcc/6.4.0/bin/gcc -c -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec poisson2d_reference.c -o poisson2d_reference.o -lm -/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d -lm +<pre>Affinity: 0,1,2,3 +<<Waiting for dispatch ...>> +<<Starting on login1>> + OMP_PLACES = '{0},{1},{2},{3}' +1000x1000: Ref: 2.3964 s, This: 2.1361 s, speedup: 1.12 +Affinity: 0,5,9,13 +<<Waiting for dispatch ...>> +<<Starting on login1>> + OMP_PLACES = '{0},{5},{9},{13}' +1000x1000: Ref: 2.3925 s, This: 0.7030 s, speedup: 3.40 </pre> </div> </div> @@ -14307,13 +15621,24 @@ Calculate current execution. </div> </div> +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p>Great!</p> +<p>If you still have time: The same experiments can be repeated with the IBM XL compiler. +The corresponding compiler flag to enable OpenMP parallelism that needs to be used for XL is <code>-qsmp=omp</code></p> +<p><strong>Task</strong>: In the Makefile add the OpenMP flag and generate XL binaries with OpenMP and run the application with various number of threads and note the performance speedup.</p> + +</div> +</div> </div> <div class="cell border-box-sizing code_cell rendered"> <div class="input"> -<div class="prompt input_prompt">In [40]:</div> +<div class="prompt input_prompt">In [44]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make run +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>make <span class="nv">CC</span><span class="o">=</span>xlc_r -B run </pre></div> </div> @@ -14330,26 +15655,40 @@ Calculate current execution. <div class="output_subarea output_stream output_stdout output_text"> -<pre>bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d -Job <5052> is submitted to default queue <batch>. +<pre>xlc_r -c -std=c99 -DUSE_DOUBLE -O3 -qhot -qtune=pwr9 -DINLINE_LIBS -qsmp=omp poisson2d_reference.c -o poisson2d_reference.o -lm + 1500-036: (I) The NOSTRICT option (default at OPT(3)) has the potential to alter the semantics of a program. Please refer to documentation on the STRICT/NOSTRICT option for more information. +xlc_r -std=c99 -DUSE_DOUBLE -O3 -qhot -qtune=pwr9 -DINLINE_LIBS -qsmp=omp poisson2d.c poisson2d_reference.o -o poisson2d -lm + 1500-036: (I) The NOSTRICT option (default at OPT(3)) has the potential to alter the semantics of a program. Please refer to documentation on the STRICT/NOSTRICT option for more information. +bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d +Job <24956> is submitted to default queue <batch>. <<Waiting for dispatch ...>> <<Starting on login1>> -Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh +Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh Calculate reference solution and time with serial CPU execution. - 0, 0.249980 - 100, 0.246028 - 200, 0.242198 - 300, 0.238487 - 400, 0.234887 + 0, 0.249995 + 100, 50.149062 + 200, 99.849327 + 300, 149.352369 + 400, 198.659746 + 500, 247.773000 + 600, 296.693652 + 700, 345.423208 + 800, 393.963155 + 900, 442.314962 Calculate current execution. - 0, 0.249980 - 100, 0.246028 - 200, 0.242198 - 300, 0.238487 - 400, 0.234887 -500x500: Ref: 0.2571 s, This: 0.2946 s, speedup: 0.87 -1.48user 0.00system 0:00.56elapsed 263%CPU (0avgtext+0avgdata 9664maxresident)k -0inputs+0outputs (0major+273minor)pagefaults 0swaps + 0, 0.249995 + 100, 50.149062 + 200, 99.849327 + 300, 149.352369 + 400, 198.659746 + 500, 247.773000 + 600, 296.693652 + 700, 345.423208 + 800, 393.963155 + 900, 442.314962 +1000x1000: Ref: 5.6783 s, This: 2.6528 s, speedup: 2.14 +21.56user 6.18system 0:08.37elapsed 331%CPU (0avgtext+0avgdata 23040maxresident)k +3200inputs+0outputs (2major+1098minor)pagefaults 0swaps </pre> </div> </div> @@ -14361,17 +15700,25 @@ Calculate current execution. <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<p>The command to submit a job to the batch system is prepared in an environment variable <code>$SC18_SUBMIT_CMD</code>; use it together with <code>eval</code>. In the following cell, it is shown how to increase the work of the application.</p> +<p>Run the parallel application with varying numbre of threads (<code>OMP_NUM_THREADS</code>) and note the performance improvement.</p> + +</div> +</div> +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p>Just as in the GCC binary we see a similar speedup with higher number of threads until a certain point beyond which the benefit tapers off.</p> </div> </div> </div> <div class="cell border-box-sizing code_cell rendered"> <div class="input"> -<div class="prompt input_prompt">In [3]:</div> +<div class="prompt input_prompt">In [28]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span><span class="nb">eval</span> <span class="nv">$SC18_SUBMIT_CMD</span> ./poisson2d <span class="m">1000</span> <span class="m">1000</span> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span><span class="nv">OMP_NUM_THREADS</span><span class="o">=</span><span class="m">1</span> <span class="nv">$$</span>SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d <span class="m">1000</span> <span class="m">1000</span> <span class="m">1000</span> <span class="p">|</span> grep speedup </pre></div> </div> @@ -14388,33 +15735,9 @@ Calculate current execution. <div class="output_subarea output_stream output_stdout output_text"> -<pre>Job <5344> is submitted to default queue <batch>. -<<Waiting for dispatch ...>> +<pre><<Waiting for dispatch ...>> <<Starting on login1>> -Jacobi relaxation calculation: max 1000 iterations on 1000 x 100 mesh -Calculate reference solution and time with serial CPU execution. - 0, 0.249743 - 100, 0.210080 - 200, 0.184635 - 300, 0.166526 - 400, 0.152783 - 500, 0.141890 - 600, 0.132978 - 700, 0.125511 - 800, 0.119142 - 900, 0.113632 -Calculate current execution. - 0, 0.249743 - 100, 0.210080 - 200, 0.184635 - 300, 0.166526 - 400, 0.152783 - 500, 0.141890 - 600, 0.132978 - 700, 0.125511 - 800, 0.119142 - 900, 0.113632 -1000x100: Ref: 1.9872 s, This: 0.2385 s, speedup: 8.33 +1000x1000: Ref: 2.2561 s, This: 2.6432 s, speedup: 0.85 </pre> </div> </div> @@ -14423,23 +15746,45 @@ Calculate current execution. </div> </div> -<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> -</div><div class="inner_cell"> -<div class="text_cell_render border-box-sizing rendered_html"> -<p>What is the best performance you can reach by setting the number of threads via <code>OMP_NUM_THREADS=N</code> with <code>N</code> being the number of threads? Feel free to play around with the command in the following cell, using 1 thread as an example.<br> -We added <code>--bind none</code> to prevent <code>jsrun</code>, the scheduler of Ascent, from overlaying binding options. Also, we use <code>-c ALL_CPUS</code> to make all CPUs on the compute nodes available to you.</p> +<div class="cell border-box-sizing code_cell rendered"> +<div class="input"> +<div class="prompt input_prompt">In [29]:</div> +<div class="inner_cell"> + <div class="input_area"> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span><span class="nv">OMP_NUM_THREADS</span><span class="o">=</span><span class="m">2</span> <span class="nv">$$</span>SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d <span class="m">1000</span> <span class="m">1000</span> <span class="m">1000</span> <span class="p">|</span> grep speedup +</pre></div> + + </div> +</div> +</div> + +<div class="output_wrapper"> +<div class="output"> + + +<div class="output_area"> + + <div class="prompt"></div> + + +<div class="output_subarea output_stream output_stdout output_text"> +<pre><<Waiting for dispatch ...>> +<<Starting on login1>> +1000x1000: Ref: 2.3071 s, This: 1.5343 s, speedup: 1.50 +</pre> +</div> +</div> </div> </div> + </div> <div class="cell border-box-sizing code_cell rendered"> <div class="input"> -<div class="prompt input_prompt">In [23]:</div> +<div class="prompt input_prompt">In [30]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="k">for</span> <span class="n">omp_num</span> <span class="ow">in</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">40</span><span class="p">]:</span> - <span class="nb">print</span><span class="p">(</span><span class="s2">"Threads: </span><span class="si">{}</span><span class="s2">"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">omp_num</span><span class="p">))</span> - <span class="o">!</span><span class="nb">eval</span> <span class="nv">OMP_NUM_THREADS</span><span class="o">=</span><span class="nv">$omp_num</span> <span class="nv">$$</span>SC18_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d <span class="m">1000</span> <span class="m">1000</span> <span class="p">|</span> grep speedup +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span><span class="nv">OMP_NUM_THREADS</span><span class="o">=</span><span class="m">4</span> <span class="nv">$$</span>SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d <span class="m">1000</span> <span class="m">1000</span> <span class="m">1000</span> <span class="p">|</span> grep speedup </pre></div> </div> @@ -14456,34 +15801,42 @@ We added <code>--bind none</code> to prevent <code>jsrun</code>, the scheduler o <div class="output_subarea output_stream output_stdout output_text"> -<pre>Threads: 1 -<<Waiting for dispatch ...>> -<<Starting on login1>> -1000x1000: Ref: 2.3037 s, This: 2.8420 s, speedup: 0.81 -Threads: 2 -<<Waiting for dispatch ...>> -<<Starting on login1>> -1000x1000: Ref: 2.2998 s, This: 1.4320 s, speedup: 1.61 -Threads: 4 -<<Waiting for dispatch ...>> -<<Starting on login1>> -1000x1000: Ref: 2.3135 s, This: 0.7168 s, speedup: 3.23 -Threads: 8 -<<Waiting for dispatch ...>> -<<Starting on login1>> -1000x1000: Ref: 2.3145 s, This: 0.5278 s, speedup: 4.39 -Threads: 10 -<<Waiting for dispatch ...>> -<<Starting on login1>> -1000x1000: Ref: 2.3153 s, This: 0.4848 s, speedup: 4.78 -Threads: 20 -<<Waiting for dispatch ...>> +<pre><<Waiting for dispatch ...>> <<Starting on login1>> -1000x1000: Ref: 2.3190 s, This: 0.2016 s, speedup: 11.50 -Threads: 40 -<<Waiting for dispatch ...>> +1000x1000: Ref: 2.2617 s, This: 0.6936 s, speedup: 3.26 +</pre> +</div> +</div> + +</div> +</div> + +</div> +<div class="cell border-box-sizing code_cell rendered"> +<div class="input"> +<div class="prompt input_prompt">In [31]:</div> +<div class="inner_cell"> + <div class="input_area"> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span><span class="nv">OMP_NUM_THREADS</span><span class="o">=</span><span class="m">8</span> <span class="nv">$$</span>SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d <span class="m">1000</span> <span class="m">1000</span> <span class="m">1000</span> <span class="p">|</span> grep speedup +</pre></div> + + </div> +</div> +</div> + +<div class="output_wrapper"> +<div class="output"> + + +<div class="output_area"> + + <div class="prompt"></div> + + +<div class="output_subarea output_stream output_stdout output_text"> +<pre><<Waiting for dispatch ...>> <<Starting on login1>> -1000x1000: Ref: 2.3243 s, This: 0.3057 s, speedup: 7.60 +1000x1000: Ref: 2.2728 s, This: 0.3402 s, speedup: 6.68 </pre> </div> </div> @@ -14492,26 +15845,45 @@ Threads: 40 </div> </div> -<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> -</div><div class="inner_cell"> -<div class="text_cell_render border-box-sizing rendered_html"> -<h3 id="Part-B:-Bindings">Part B: Bindings<a class="anchor-link" href="#Part-B:-Bindings">¶</a></h3><p>Different CPU architectures and models come with different configuration of cores. The configuration plays an important role in the run time of the application. We need to optimize for it!</p> -<p>There are applications which can be used to determine the configuration of the processor. Among those are:</p> -<ul> -<li><code>lscpu</code>: Can be used to determine the number of sockets, number of cores, and numb of threads. It gives a very good overview and is available on most Linux systems.</li> -<li><code>ppc64_cpu --smt</code>: Specifically for POWER, this tool can give information about the number of simulations threads running per core (<em>SMT</em>, Simulataion Multi-Threading).</li> -</ul> -<p>Run <code>ppc64_cpu --smt</code> to find out about the threading configuration of Ascent!</p> +<div class="cell border-box-sizing code_cell rendered"> +<div class="input"> +<div class="prompt input_prompt">In [45]:</div> +<div class="inner_cell"> + <div class="input_area"> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span><span class="nv">OMP_NUM_THREADS</span><span class="o">=</span><span class="m">10</span> <span class="nv">$$</span>SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d <span class="m">1000</span> <span class="m">1000</span> <span class="m">1000</span> <span class="p">|</span> grep speedup +</pre></div> + + </div> +</div> +</div> + +<div class="output_wrapper"> +<div class="output"> + + +<div class="output_area"> + + <div class="prompt"></div> + + +<div class="output_subarea output_stream output_stdout output_text"> +<pre><<Waiting for dispatch ...>> +<<Starting on login1>> +1000x1000: Ref: 2.1678 s, This: 0.2869 s, speedup: 7.56 +</pre> +</div> +</div> </div> </div> + </div> <div class="cell border-box-sizing code_cell rendered"> <div class="input"> -<div class="prompt input_prompt">In [48]:</div> +<div class="prompt input_prompt">In [33]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span><span class="nb">eval</span> <span class="nv">$SC18_SUBMIT_CMD</span> ppc64_cpu --smt +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span><span class="nv">OMP_NUM_THREADS</span><span class="o">=</span><span class="m">20</span> <span class="nv">$$</span>SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d <span class="m">1000</span> <span class="m">1000</span> <span class="m">1000</span> <span class="p">|</span> grep speedup </pre></div> </div> @@ -14528,10 +15900,9 @@ Threads: 40 <div class="output_subarea output_stream output_stdout output_text"> -<pre>Job <5076> is submitted to default queue <batch>. -<<Waiting for dispatch ...>> +<pre><<Waiting for dispatch ...>> <<Starting on login1>> -SMT=4 +1000x1000: Ref: 2.2813 s, This: 0.1452 s, speedup: 15.71 </pre> </div> </div> @@ -14540,25 +15911,45 @@ SMT=4 </div> </div> -<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> -</div><div class="inner_cell"> -<div class="text_cell_render border-box-sizing rendered_html"> -<p>There are more sources information available</p> -<ul> -<li><code>/proc/cpuinfo</code>: Holds information about virtual cores, including model and clock speed. Available on most Linux system. Usually used together with <code>cat</code></li> -<li><code>/sys/devices/system/cpu/cpu0/topology/thread_siblings_list</code>: Holds information about thread siblings for given CPU core (<code>cpu0</code> in this case). Use it to find out which thread is mapped to which core.</li> -</ul> +<div class="cell border-box-sizing code_cell rendered"> +<div class="input"> +<div class="prompt input_prompt">In [34]:</div> +<div class="inner_cell"> + <div class="input_area"> +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span><span class="nv">OMP_NUM_THREADS</span><span class="o">=</span><span class="m">40</span> <span class="nv">$$</span>SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d <span class="m">1000</span> <span class="m">1000</span> <span class="m">1000</span> <span class="p">|</span> grep speedup +</pre></div> + + </div> +</div> +</div> +<div class="output_wrapper"> +<div class="output"> + + +<div class="output_area"> + + <div class="prompt"></div> + + +<div class="output_subarea output_stream output_stdout output_text"> +<pre><<Waiting for dispatch ...>> +<<Starting on login1>> +1000x1000: Ref: 2.3284 s, This: 0.0981 s, speedup: 23.75 +</pre> </div> </div> + +</div> +</div> + </div> <div class="cell border-box-sizing code_cell rendered"> <div class="input"> -<div class="prompt input_prompt">In [49]:</div> +<div class="prompt input_prompt">In [35]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span>cat /sys/devices/system/cpu/cpu0/topology/thread_siblings_list -<span class="o">!</span>cat /sys/devices/system/cpu/cpu5/topology/thread_siblings_list +<div class=" highlight hl-ipython3"><pre><span></span><span class="o">!</span><span class="nv">OMP_NUM_THREADS</span><span class="o">=</span><span class="m">80</span> <span class="nv">$$</span>SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d <span class="m">1000</span> <span class="m">1000</span> <span class="m">1000</span> <span class="p">|</span> grep speedup </pre></div> </div> @@ -14575,8 +15966,9 @@ SMT=4 <div class="output_subarea output_stream output_stdout output_text"> -<pre>0-3 -4-7 +<pre><<Waiting for dispatch ...>> +<<Starting on login1>> +1000x1000: Ref: 2.2918 s, This: 0.1439 s, speedup: 15.92 </pre> </div> </div> @@ -14588,9 +15980,10 @@ SMT=4 <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<p>There are various environment variables available within OpenMP (and GCC) to specify binding of threads to cores. See, for instance, the <a href="https://gcc.gnu.org/onlinedocs/libgomp/Environment-Variables.html">online documentation of GCC libgomp</a>. Examples are <code>OMP_PLACES</code> or <code>GOMP_CPU_AFFINITY</code>.</p> +<p>Now we repeat the exercise of using the right binding of threads for the XL binary. <code>OMP_PLACES</code> pertains to the XL binary as well as it is an OpenMP variable. <code>GOMP_CPU_AFFINITY</code> is specific to GCC binary so that cannot be used to set the binding.</p> <p><strong>Task</strong>: Run the application enabled with OpenMP from Part A with different binding configurations. Make sure to at least run a) binding all threads to a single core and b) binding threads to different cores.</p> <p>Adapt the following command with your configuration – or follow along accordingly in the non-interactive version of the Notebook.</p> +<p>We are mixing Python with Bash (<code>!</code>) here, so don't get confused (because of this, if we want to use Bash environment variables, we need to use two <code>$$</code>)</p> <p>What's your maximum speedup?</p> </div> @@ -14598,12 +15991,12 @@ SMT=4 </div> <div class="cell border-box-sizing code_cell rendered"> <div class="input"> -<div class="prompt input_prompt">In [24]:</div> +<div class="prompt input_prompt">In [36]:</div> <div class="inner_cell"> <div class="input_area"> -<div class=" highlight hl-ipython3"><pre><span></span><span class="k">for</span> <span class="n">affinity</span> <span class="ow">in</span> <span class="p">[</span><span class="s2">"0,1,2,3"</span><span class="p">,</span> <span class="s2">"0,5,9,13"</span><span class="p">]:</span> +<div class=" highlight hl-ipython3"><pre><span></span><span class="k">for</span> <span class="n">affinity</span> <span class="ow">in</span> <span class="p">[</span><span class="s2">"</span><span class="si">{0}</span><span class="s2">,</span><span class="si">{1}</span><span class="s2">,</span><span class="si">{2}</span><span class="s2">,</span><span class="si">{3}</span><span class="s2">"</span><span class="p">,</span> <span class="s2">"</span><span class="si">{0}</span><span class="s2">,</span><span class="si">{5}</span><span class="s2">,</span><span class="si">{9}</span><span class="s2">,</span><span class="si">{13}</span><span class="s2">"</span><span class="p">]:</span> <span class="nb">print</span><span class="p">(</span><span class="s2">"Affinity: </span><span class="si">{}</span><span class="s2">"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">affinity</span><span class="p">))</span> - <span class="o">!</span><span class="nb">eval</span> <span class="nv">OMP_DISPLAY_ENV</span><span class="o">=</span><span class="nb">true</span> <span class="nv">GOMP_CPU_AFFINITY</span><span class="o">=</span><span class="nv">$affinity</span> <span class="nv">OMP_NUM_THREADS</span><span class="o">=</span><span class="m">4</span> <span class="nv">$$</span>SC18_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d <span class="m">1000</span> <span class="m">1000</span> <span class="m">100</span> <span class="p">|</span> grep <span class="s2">"OMP_PLACES\|speedup"</span> + <span class="o">!</span><span class="nb">eval</span> <span class="nv">OMP_DISPLAY_ENV</span><span class="o">=</span><span class="nb">true</span> <span class="nv">OMP_PLACES</span><span class="o">=</span><span class="nv">$affinity</span> <span class="nv">OMP_NUM_THREADS</span><span class="o">=</span><span class="m">4</span> <span class="nv">$$</span>SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d <span class="m">1000</span> <span class="m">1000</span> <span class="m">1000</span> <span class="p">|</span> grep <span class="s2">"OMP_PLACES\|speedup"</span> </pre></div> </div> @@ -14620,16 +16013,16 @@ SMT=4 <div class="output_subarea output_stream output_stdout output_text"> -<pre>Affinity: 0,1,2,3 +<pre>Affinity: {0},{1},{2},{3} <<Waiting for dispatch ...>> <<Starting on login1>> - OMP_PLACES = '{0},{1},{2},{3}' -1000x100: Ref: 1.9854 s, This: 0.2326 s, speedup: 8.53 -Affinity: 0,5,9,13 + OMP_PLACES='{0},{1},{2},{3}' custom +1000x1000: Ref: 5.9792 s, This: 2.4122 s, speedup: 2.48 +Affinity: {0},{5},{9},{13} <<Waiting for dispatch ...>> <<Starting on login1>> - OMP_PLACES = '{0},{5},{9},{13}' -1000x100: Ref: 1.9828 s, This: 0.0833 s, speedup: 23.80 + OMP_PLACES='{0},{5},{9},{13}' custom +1000x1000: Ref: 2.3101 s, This: 0.6884 s, speedup: 3.36 </pre> </div> </div> @@ -14637,12 +16030,21 @@ Affinity: 0,5,9,13 </div> </div> +</div> +<div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> +</div><div class="inner_cell"> +<div class="text_cell_render border-box-sizing rendered_html"> +<p>Likewise we see a higher speedup when we bind the threads to different cores rather than to a single core. This handson illustrates that apart from compiler level tuning, system level tuning is also equally important to obtain performance improvements</p> + +</div> +</div> </div> <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> <h4 id="References">References<a class="anchor-link" href="#References">¶</a></h4><ol> <li><a href="https://gcc.gnu.org/onlinedocs/libgomp/GOMP_005fCPU_005fAFFINITY.html">https://gcc.gnu.org/onlinedocs/libgomp/GOMP_005fCPU_005fAFFINITY.html</a></li> +<li><a href="https://www.openmp.org/spec-html/5.0/openmpse53.html">https://www.openmp.org/spec-html/5.0/openmpse53.html</a></li> </ol> </div> @@ -14660,7 +16062,7 @@ Affinity: 0,5,9,13 <div class="cell border-box-sizing text_cell rendered"><div class="prompt input_prompt"> </div><div class="inner_cell"> <div class="text_cell_render border-box-sizing rendered_html"> -<h1 id="Survey">Survey<a name="survey" /><a class="anchor-link" href="#Survey">¶</a></h1><p>Please rememeber to take some time and fill out the <a href="http://bit.ly/sc18-eval">survey</a>.</p> +<h1 id="Survey">Survey<a name="survey" /><a class="anchor-link" href="#Survey">¶</a></h1><p>Please rememeber to take some time and fill out the <a href="http://bit.ly/sc19-eval">survey</a>.</p> </div> </div> diff --git a/3-Optimizing_POWER/Handson/Solution-Notebook/HandsOnPerformanceOptimization.ipynb b/3-Optimizing_POWER/Handson/Solution-Notebook/HandsOnPerformanceOptimization.ipynb index e6a4bae..5208f9b 100644 --- a/3-Optimizing_POWER/Handson/Solution-Notebook/HandsOnPerformanceOptimization.ipynb +++ b/3-Optimizing_POWER/Handson/Solution-Notebook/HandsOnPerformanceOptimization.ipynb @@ -1 +1,2416 @@ -{"cells": [{"cell_type": "markdown", "metadata": {}, "source": ["# Hands-On Performance Optimization\n", "_Supercomputing 2018 Tutorial \"Application Porting and Optimization on GPU-Accelerated POWER Architectures\", November 12th 2018_\n", "\n", "---"]}, {"cell_type": "markdown", "metadata": {}, "source": ["As for the first task of this tutorial, also this task is primarily designed to be executed as an interactive Jupyter Notebook. However, everything can also be done using an SSH connection to Ascent (or any other POWER9 computer) in your terminal.\n", "\n", "## Jupyter notebook execution\n", "\n", "When using Jupyter, this Notebook will guide you through the steps. Note that if you execute a cell multiple times while optimizng the code the output will be replaced. You can however duplicate the cell you want to execute and keep its output. Check the _edit_ menu above.\n", "\n", "You will always find links to a file browser of the corresponding task subdirectory as well as direct links to the source files you will need to edit as well as the profiling output you need to open locally.\n", "\n", "If you want you also can get a [terminal](/terminals/1) in your browser.\n", "\n", "## Terminal fallback\n", "\n", "The tasks are place in directories named `Task[1-3]`.\n", "\n", "Makefile targets are created to cover everything, from compile, to run and profile. Please take a look at the cells containing the make calls as a guide also for the non-interactive version of this description."]}, {"cell_type": "markdown", "metadata": {}, "source": ["## Setup\n", "\n", "This hands-on session requires of GCC 6.4.0. By loading the `sc18/handson2` module before invoking this Notebook, we took care of also loading GCC 6.4.0 into the environment."]}, {"cell_type": "markdown", "metadata": {}, "source": ["## Tasks<a name=\"top\"></a>\n", "\n", "This session comes with multiple tasks, each one to be found in the respective sub-directory `Task[1-3]`. In each of these directories you will also find Makefiles that are set up so that you can compile and submit all necessary tasks.\n", "\n", "Please choose from the task below.\n", "\n", "\n", "* [Task 1](#task1): Compile Flags \n", "Improve performance of the CPU Jacobi solver with compiler flags such as `Ofast` and profile-directed feedback ([Solution 1](#solution0))\n", "\n", "* [Task 2](#task2): Software Prefetching \n", "Improve performance of the CPU Jacobi solver with software prefetching ([Solution 2](#solution1))\n", "\n", "* [Task 3](#task3): OpenMP \n", "Parallelize the CPU Jacobi solver and determine the right binding to be used for optimal performance ([Solution 3](#solution2))\n", " \n", "* [Suvery](#survey) Please remember to take the survey !\n", " \n", "### Make Targets <a name=\"make\"></a>\n", "\n", "For all tasks we have defined the following make targets. \n", "\n", "* __poisson2d__: \n", " build `poisson2d` binary (default)\n", "* __run__: \n", " run `poisson2d` with default parameters\n"]}, {"cell_type": "markdown", "metadata": {}, "source": ["[Back to Top](#top)\n", "\n", "---"]}, {"cell_type": "markdown", "metadata": {}, "source": ["## Task 1: Compile Flags <a name=\"task1\"></a>\n", "\n", "\n", "### Overview\n", "\n", "The goal of this task is to understand different options available to optimize the performance of the CPU Jacobi solver \n", "\n", "Your task is to:\n", "\n", "* Optimize performance with `-Ofast` flag\n", "* Optimize performance with profile directed feedback \n", "\n", "First, change the working directory to `Task1`."]}, {"cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["/autofs/nccsopen-svm1_home/aherten/SC18-Tutorial/3-Optimizing_POWER/Handson/Task1\n"]}], "source": ["%cd Task1"]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Part A: `-Ofast` vs. `-O3`\n", "\n", "We are to compare the performance of the binary being compiled with `-Ofast` optimization and with `-O3` optimization. Right now, the Makefile specifies `-O3` as the optimization flag. Compile the code using `make` and run it with `make run` in the next two cells."]}, {"cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["/sw/ascent/gcc/6.4.0/bin/gcc -c -std=c99 -mcpu=power9 -O3 -DUSE_DOUBLE -mvsx -maltivec -fopenmp poisson2d_reference.c -o poisson2d_reference.o -lm\n", "/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -O3 -DUSE_DOUBLE -mvsx -maltivec -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d -lm\n"]}], "source": ["!make"]}, {"cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", "Job <5033> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", "Calculate reference solution and time with serial CPU execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "Calculate current execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "1.13user 0.00system 0:01.15elapsed 97%CPU (0avgtext+0avgdata 10944maxresident)k\n", "2560inputs+0outputs (1major+264minor)pagefaults 0swaps\n"]}], "source": ["!make run"]}, {"cell_type": "markdown", "metadata": {}, "source": ["You can use the GNU _perf_ tool to profile the application using the `perf` command (see below) and see the top time-consuming functions."]}, {"cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", "Calculate reference solution and time with serial CPU execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "Calculate current execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "[ perf record: Woken up 1 times to write data ]\n", "[ perf record: Captured and wrote 0.172 MB perf.O3.data (4125 samples) ]\n", "# To display the perf.data header info, please use --header/--header-only options.\n", "#\n", "#\n", "# Total Lost Samples: 0\n", "#\n", "# Samples: 4K of event 'cycles:u'\n", "# Event count (approx.): 3867635297\n", "#\n", "# Overhead Command Shared Object Symbol \n", "# ........ ......... ................. ........................................\n", "#\n", " 72.02% poisson2d poisson2d [.] 00000040.plt_call.fmax@@GLIBC_2.17\n", " 10.16% poisson2d poisson2d [.] poisson2d_reference\n", " 9.99% poisson2d poisson2d [.] main\n", " 4.69% poisson2d libc-2.17.so [.] __memcpy_power7\n", " 2.23% poisson2d libm-2.17.so [.] __fmaxf\n", " 0.75% poisson2d libm-2.17.so [.] __exp_finite\n", " 0.07% poisson2d poisson2d [.] 00000040.plt_call.memcpy@@GLIBC_2.17\n", " 0.02% poisson2d poisson2d [.] check_results\n", " 0.02% poisson2d libm-2.17.so [.] __GI___exp\n", " 0.01% poisson2d ld-2.17.so [.] _dl_relocate_object\n", " 0.01% poisson2d [kernel.kallsyms] [k] arch_local_irq_restore\n", " 0.00% poisson2d ld-2.17.so [.] _dl_new_object\n", " 0.00% poisson2d ld-2.17.so [.] _start\n", "\n", "\n", "#\n", "# (Tip: Show user configuration overrides: perf config --user --list)\n", "#\n"]}], "source": ["# perf record creates a perf.data file \n", "!perf record -o perf.O3.data -e cycles ./poisson2d\n", "# perf report opens the perf.data file \n", "!perf report -i perf.O3.data | cat"]}, {"cell_type": "markdown", "metadata": {}, "source": ["**TASK**: Now change the optimization flag in the [Makefile](/edit/Task1/Makefile) to `-Ofast` and repeat the steps in the following cell. In case you follow along non-interactive, call `make` and `make run` in your shell. (If you are in the Jupyter Notebook, you can actually click the link of the [Makefile](/edit/Task1/Makefile). In other cases, use `vim` which is installed on Ascent.)"]}, {"cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["/sw/ascent/gcc/6.4.0/bin/gcc -c -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec -fopenmp poisson2d_reference.c -o poisson2d_reference.o -lm\n", "/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d -lm\n"]}], "source": ["!make"]}, {"cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", "Job <5034> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", "Calculate reference solution and time with serial CPU execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "Calculate current execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "0.51user 0.00system 0:00.52elapsed 99%CPU (0avgtext+0avgdata 10816maxresident)k\n", "256inputs+0outputs (0major+264minor)pagefaults 0swaps\n"]}], "source": ["!make run"]}, {"cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", "Calculate reference solution and time with serial CPU execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "Calculate current execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "[ perf record: Woken up 1 times to write data ]\n", "[ perf record: Captured and wrote 0.086 MB perf.Ofast.data (1889 samples) ]\n", "# To display the perf.data header info, please use --header/--header-only options.\n", "#\n", "#\n", "# Total Lost Samples: 0\n", "#\n", "# Samples: 1K of event 'cycles:u'\n", "# Event count (approx.): 1765737747\n", "#\n", "# Overhead Command Shared Object Symbol \n", "# ........ ......... ............. .......................\n", "#\n", " 44.65% poisson2d poisson2d [.] main\n", " 43.84% poisson2d poisson2d [.] poisson2d_reference\n", " 10.28% poisson2d libc-2.17.so [.] __memcpy_power7\n", " 1.12% poisson2d libm-2.17.so [.] __exp_finite\n", " 0.05% poisson2d poisson2d [.] check_results\n", " 0.03% poisson2d ld-2.17.so [.] _dl_relocate_object\n", " 0.02% poisson2d libc-2.17.so [.] __readdir64\n", " 0.01% poisson2d ld-2.17.so [.] _dl_new_object\n", " 0.00% poisson2d ld-2.17.so [.] _start\n", "\n", "\n", "#\n", "# (Tip: System-wide collection from all CPUs: perf record -a)\n", "#\n"]}], "source": ["# perf record creates a perf.data file \n", "!perf record -o perf.Ofast.data -e cycles ./poisson2d\n", "# perf report opens the perf.data file \n", "!perf report -i perf.Ofast.data | cat"]}, {"cell_type": "markdown", "metadata": {}, "source": ["If `perf` is unavailable to you on other machines, you can also study the disassembly with `objdump`: `objdump -lSd ./poisson2d > poisson2d.dis` (feel free to experiment with this in the Notebook as well, just prefix the command with a `!` to execute it.)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["#### Interpretation\n", "\n", "Depending on the application requirement, if a high precision of results is not mandatory, the users can compile an application with `-Ofast` which enables `\u2013ffast-math` option that implements the same math function in a relaxed manner very similar to how general mathematical expressions are implemented and avoids the overhead of calling a function from the math library. Comparing the files, you will see that the `-Ofast` binary natively implements the `fmax` function using instructions available in the hardware. The `-O3` binary makes a library call to compute `fmax` to follow a stricter _IEEE_ requirement for accuracy."]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Part B: Profile-directed Feedback\n", "\n", "For the first level of optimization we saw `Ofast` cut the execution time of the `O3` binary by almost half.\n", "\n", "We can optimize the performance further by using profile directed feedback optimization.\n", "\n", "To compile using profile directed feedback with the GCC compiler we need to do the following steps\n", "\n", "1. We need to first build a training binary using `-fprofile-generate`; this instructs the compiler to record hot path information \n", "2. Run the training binary with a smaller input size; you should see a `.gcda` file generated which stores hot path information for further optimization by the compiler \n", "3. build the final binary using `-fprofile-use` which uses the profile information in the `.gcda` file \n", "4. Compare the performance of the final binary with the original `Ofast` binary \n", "\n", "**TASK**: First, search for `TODO1` in the [Makefile](/edit/Task1/Makefile). It defines an additional compilation flag for `gcc`. Insert `-fprofile-generate=FOLDER` there with FOLDER pointing to `$$SC18_DIR_SCRATCH`, your personal write-directory (the double dollar signs are intentional as they are used to escape in the GNU Make syntax).\n", "\n", "After editing, run the following two cells to train your program."]}, {"cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["/sw/ascent/gcc/6.4.0/bin/gcc -c -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec \"-fprofile-generate=$SC18_DIR_SCRATCH\" -fopenmp poisson2d_reference.c -o poisson2d_reference.o -lm\n", "/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec \"-fprofile-generate=$SC18_DIR_SCRATCH\" -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d_train -lm\n"]}], "source": ["!make poisson2d_train"]}, {"cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d_train 200 64 64\n", "Job <5035> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 200 iterations on 64 x 64 mesh\n", "Calculate reference solution and time with serial CPU execution.\n", " 0, 0.248743\n", " 100, 0.124046\n", "Calculate current execution.\n", " 0, 0.248743\n", " 100, 0.124046\n", "0.00user 0.00system 0:00.10elapsed 5%CPU (0avgtext+0avgdata 5248maxresident)k\n", "512inputs+0outputs (0major+115minor)pagefaults 0swaps\n", "mv $SC18_DIR_SCRATCH/*.gcda .\n"]}], "source": ["!make run_train"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Now, a `.gcda` file exists in the directory which can be used for an profile-accelerated subsequent run.\n", "\n", "**TASK**: Edit the [Makefile](/edit/Task1/Makefile) again, this time modifying `TODO2` to be equivalent to `-fprofile-use`. A directory is not needed as we copied the gcda file into the current directory.\n", "\n", "Run the following cells in order to build using the newly added flag and then run with the profile-accelerated version."]}, {"cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["/sw/ascent/gcc/6.4.0/bin/gcc -c -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec \"-fprofile-use\" -fopenmp poisson2d_reference.c -o poisson2d_reference.o -lm\n", "/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec \"-fprofile-use\" -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d_profile -lm\n"]}], "source": ["!make poisson2d_profile"]}, {"cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d_profile\n", "Job <5036> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", "Calculate reference solution and time with serial CPU execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "Calculate current execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "0.47user 0.00system 0:00.48elapsed 98%CPU (0avgtext+0avgdata 10816maxresident)k\n", "256inputs+0outputs (0major+265minor)pagefaults 0swaps\n"]}], "source": ["!make run_profile"]}, {"cell_type": "markdown", "metadata": {}, "source": ["What is your speed-up? Feel free to run with larger problem sizes (mesh; iterations)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["#### References\n", "\n", "1. https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html\n", "2. https://perf.wiki.kernel.org/index.php/Tutorial"]}, {"cell_type": "markdown", "metadata": {}, "source": ["[Back to Top](#top)\n", "\n", "---"]}, {"cell_type": "markdown", "metadata": {}, "source": ["## Task 2:<a name=\"task2\"></a> Software Pretechting\n", "\n", "\n", "### Overview\n", "\n", "Study the difference of program execution time of different optimization levels with and without software prefetching.\n", "\n", "First, change directory to that of Task 2"]}, {"cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["/autofs/nccsopen-svm1_home/aherten/SC18-Tutorial/3-Optimizing_POWER/Handson/Task2\n"]}], "source": ["%cd ../Task2"]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Part A: Running"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Look at the [Makefile](/edit/Task2/Makefile) and work on the TODOs. Please implement compile flags as mentioned in the Makefile target name.\n", "\n", "Afterwards, compile each target with the following cells and submit them to the batch system. Follow along accordingly in the non-interactive version of this Notebook."]}, {"cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["/sw/ascent/gcc/6.4.0/bin/gcc -c -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -O3 -fprefetch-loop-arrays -fopenmp poisson2d_reference.c -o poisson2d_reference.o -lm\n", "/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -O3 -fprefetch-loop-arrays -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d_o3_pref -lm\n", "bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d_o3_pref\n", "Job <5037> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", "Calculate reference solution and time with serial CPU execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "Calculate current execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "1.12user 0.00system 0:01.13elapsed 99%CPU (0avgtext+0avgdata 10880maxresident)k\n", "256inputs+0outputs (0major+265minor)pagefaults 0swaps\n"]}], "source": ["!make run_o3_pref"]}, {"cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast -fprefetch-loop-arrays -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d_ofast_pref -lm\n", "bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d_ofast_pref\n", "Job <5038> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", "Calculate reference solution and time with serial CPU execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "Calculate current execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "0.77user 0.00system 0:00.77elapsed 99%CPU (0avgtext+0avgdata 10816maxresident)k\n", "256inputs+0outputs (0major+264minor)pagefaults 0swaps\n"]}], "source": ["!make run_ofast_pref"]}, {"cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -O3 -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d_o3_nopref -lm\n", "bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d_o3_nopref\n", "Job <5039> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", "Calculate reference solution and time with serial CPU execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "Calculate current execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "1.13user 0.00system 0:01.13elapsed 99%CPU (0avgtext+0avgdata 10944maxresident)k\n", "256inputs+0outputs (0major+266minor)pagefaults 0swaps\n"]}], "source": ["!make run_o3_nopref"]}, {"cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d_ofast_nopref -lm\n", "bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d_ofast_nopref\n", "Job <5040> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", "Calculate reference solution and time with serial CPU execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "Calculate current execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "0.82user 0.00system 0:00.82elapsed 99%CPU (0avgtext+0avgdata 10816maxresident)k\n", "256inputs+0outputs (0major+265minor)pagefaults 0swaps\n"]}], "source": ["!make run_ofast_nopref"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Do you notice the impact difference with optimization levels? It's always important to carefully study the interplay of flags."]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Part B: Analysis of Instructions\n", "\n", "Compilation with the software prefetching flag causes the compiler to generate the `__dcbt` and `__dcbtst` instructions that prefetch memory values to L3.\n", "\n", "Verify it using `objdump -lSd` on each file (`poisson2d_o3_pref`, `poisson2d_ofast_pref`, `poisson2d_o3_nopref`, `poisson2d_ofast_nopref`). You might want to grep for `dcb`."]}, {"cell_type": "code", "execution_count": 19, "metadata": {"sc18": "solution"}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["poisson2d_o3_pref:\n", "poisson2d_ofast_pref:\n", " 10000da0:\tec f1 00 7c \tdcbtst 0,r30\n", " 10000da4:\t2c fa 00 7c \tdcbt 0,r31\n", " 10000da8:\t2c 62 00 7c \tdcbt 0,r12\n", " 10000dac:\t2c b2 00 7c \tdcbt 0,r22\n", " 10000dcc:\t2c e2 00 7c \tdcbt 0,r28\n", " 10000dd0:\t2c ea 00 7c \tdcbt 0,r29\n", " 100010b4:\t2c 62 00 7c \tdcbt 0,r12\n", " 100010b8:\t2c 5a 00 7c \tdcbt 0,r11\n", " 100010c4:\tec 19 00 7c \tdcbtst 0,r3\n", " 100010cc:\t2c 22 00 7c \tdcbt 0,r4\n", " 100010d0:\t2c ea 00 7c \tdcbt 0,r29\n", " 100010d4:\t2c f2 00 7c \tdcbt 0,r30\n", " 100010dc:\t2c fa 00 7c \tdcbt 0,r31\n", "poisson2d_o3_nopref:\n", "poisson2d_ofast_nopref:\n"]}], "source": ["for f in [\"poisson2d_o3_pref\", \"poisson2d_ofast_pref\", \"poisson2d_o3_nopref\", \"poisson2d_ofast_nopref\"]:\n", " print(\"{}:\".format(f))\n", " objdump -lSd $f |\u00a0grep dcb"]}, {"cell_type": "markdown", "metadata": {}, "source": ["If you feel up to the task, you can study the number of L3 cache misses using the corresponding performance counter, `PM_L3_MISS`. Either use your knowledge from Hands-On 1, or use the following call to `perf`, in which we already converted the named counter to a raw counter address."]}, {"cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Job <5048> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", "Calculate reference solution and time with serial CPU execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "Calculate current execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "\n", " Performance counter stats for './poisson2d_ofast_nopref':\n", "\n", " 2829292169 cycles:u \n", " 136018637 r168a4:u \n", "\n", " 0.826136863 seconds time elapsed\n", "\n", "Job <5049> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", "Calculate reference solution and time with serial CPU execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "Calculate current execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "\n", " Performance counter stats for './poisson2d_ofast_pref':\n", "\n", " 2654990243 cycles:u \n", " 128824827 r168a4:u \n", "\n", " 0.775593651 seconds time elapsed\n", "\n"]}], "source": ["for f in [\"poisson2d_ofast_nopref\", \"poisson2d_ofast_pref\"]:\n", " !eval $$SC18_SUBMIT_CMD perf stat -e cycles,r168a4 ./$f\n"]}, {"cell_type": "markdown", "metadata": {}, "source": ["#### References\n", "\n", "1. https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html\n", "2. https://www.gnu.org/software/gcc/projects/prefetch.html"]}, {"cell_type": "markdown", "metadata": {}, "source": ["[Back to Top](#top)\n", "\n", "---"]}, {"cell_type": "markdown", "metadata": {}, "source": ["## Task 3: OpenMP\n", "<a name=\"task3\"></a>\n", "\n", "\n", "### Overview\n", "\n", "We add OpenMP shared-memory parallelism to the application. Also, we study the effect of binding the multi-thread processes to certain cores.\n", "\n", "First, we need to change directory to that of Task3."]}, {"cell_type": "code", "execution_count": 1, "metadata": {"ExecuteTime": {"end_time": "2018-11-07T13:47:57.724441Z", "start_time": "2018-11-07T13:47:57.718745Z"}}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["/autofs/nccsopen-svm1_home/aherten/SC18-Tutorial/3-Optimizing_POWER/Handson/Task3\n"]}], "source": ["%cd ../Task3"]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Part A: Implement OpenMP Pragmas; Compilation\n", "\n", "**Task**: Please add the correct OpenMP pragmas to the source code and compilations flags to enable OpenMP.\n", "\n", "* **pragmas**: Look at the TODOs in [`poisson2d.c`](/edit/Task3/poisson2d.c) to add OpenMP parallelism. The pragmas in question are `#pragma omp parallel for`\n", "* **Compilation**: Please add compilation flags enabling OpenMP in GCC to the [Makefile](/edit/Task3/Makefile). The flag in question is `-fopenmp`.\n", "\n", "Edit the files with the links above if you are running the interactive version of the Notebook or navigate to `poisson2d.c` and `Makefile` yourself in case you run the non-interactive version.\n", "\n", "Afterwards, compile and run the application with the following cells. Non-interactive: Follow along accordingly in the shell."]}, {"cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["/sw/ascent/gcc/6.4.0/bin/gcc -c -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec poisson2d_reference.c -o poisson2d_reference.o -lm\n", "/sw/ascent/gcc/6.4.0/bin/gcc -std=c99 -mcpu=power9 -Ofast -DUSE_DOUBLE -mvsx -maltivec -fopenmp poisson2d.c poisson2d_reference.o -o poisson2d -lm\n"]}], "source": ["!make poisson2d"]}, {"cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["bsub -W 60 -nnodes 1 -Is -P GEN111 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", "Job <5052> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 500 iterations on 500 x 500 mesh\n", "Calculate reference solution and time with serial CPU execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "Calculate current execution.\n", " 0, 0.249980\n", " 100, 0.246028\n", " 200, 0.242198\n", " 300, 0.238487\n", " 400, 0.234887\n", "500x500: Ref: 0.2571 s, This: 0.2946 s, speedup: 0.87\n", "1.48user 0.00system 0:00.56elapsed 263%CPU (0avgtext+0avgdata 9664maxresident)k\n", "0inputs+0outputs (0major+273minor)pagefaults 0swaps\n"]}], "source": ["!make run"]}, {"cell_type": "markdown", "metadata": {}, "source": ["The command to submit a job to the batch system is prepared in an environment variable `$SC18_SUBMIT_CMD`; use it together with `eval`. In the following cell, it is shown how to increase the work of the application."]}, {"cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Job <5344> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "Jacobi relaxation calculation: max 1000 iterations on 1000 x 100 mesh\n", "Calculate reference solution and time with serial CPU execution.\n", " 0, 0.249743\n", " 100, 0.210080\n", " 200, 0.184635\n", " 300, 0.166526\n", " 400, 0.152783\n", " 500, 0.141890\n", " 600, 0.132978\n", " 700, 0.125511\n", " 800, 0.119142\n", " 900, 0.113632\n", "Calculate current execution.\n", " 0, 0.249743\n", " 100, 0.210080\n", " 200, 0.184635\n", " 300, 0.166526\n", " 400, 0.152783\n", " 500, 0.141890\n", " 600, 0.132978\n", " 700, 0.125511\n", " 800, 0.119142\n", " 900, 0.113632\n", "1000x100: Ref: 1.9872 s, This: 0.2385 s, speedup: 8.33\n"]}], "source": ["!eval $SC18_SUBMIT_CMD ./poisson2d 1000 1000"]}, {"cell_type": "markdown", "metadata": {}, "source": ["What is the best performance you can reach by setting the number of threads via `OMP_NUM_THREADS=N` with `N` being the number of threads? Feel free to play around with the command in the following cell, using 1 thread as an example. \n", "We added `--bind none` to prevent `jsrun`, the scheduler of Ascent, from overlaying binding options. Also, we use `-c ALL_CPUS` to make all CPUs on the compute nodes available to you."]}, {"cell_type": "code", "execution_count": 23, "metadata": {"sc18": "solution"}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Threads: 1\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "1000x1000: Ref: 2.3037 s, This: 2.8420 s, speedup: 0.81\n", "Threads: 2\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "1000x1000: Ref: 2.2998 s, This: 1.4320 s, speedup: 1.61\n", "Threads: 4\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "1000x1000: Ref: 2.3135 s, This: 0.7168 s, speedup: 3.23\n", "Threads: 8\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "1000x1000: Ref: 2.3145 s, This: 0.5278 s, speedup: 4.39\n", "Threads: 10\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "1000x1000: Ref: 2.3153 s, This: 0.4848 s, speedup: 4.78\n", "Threads: 20\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "1000x1000: Ref: 2.3190 s, This: 0.2016 s, speedup: 11.50\n", "Threads: 40\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "1000x1000: Ref: 2.3243 s, This: 0.3057 s, speedup: 7.60\n"]}], "source": ["for omp_num in [1, 2, 4, 8, 10, 20, 40]:\n", " print(\"Threads: {}\".format(omp_num))\n", " !eval OMP_NUM_THREADS=$omp_num $$SC18_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 | grep speedup"]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Part B: Bindings\n", "\n", "Different CPU architectures and models come with different configuration of cores. The configuration plays an important role in the run time of the application. We need to optimize for it!\n", "\n", "There are applications which can be used to determine the configuration of the processor. Among those are:\n", "\n", "* `lscpu`: Can be used to determine the number of sockets, number of cores, and numb of threads. It gives a very good overview and is available on most Linux systems.\n", "* `ppc64_cpu --smt`: Specifically for POWER, this tool can give information about the number of simulations threads running per core (*SMT*, Simulataion Multi-Threading).\n", "\n", "Run `ppc64_cpu --smt` to find out about the threading configuration of Ascent!"]}, {"cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Job <5076> is submitted to default queue <batch>.\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", "SMT=4\n"]}], "source": ["!eval $SC18_SUBMIT_CMD ppc64_cpu --smt"]}, {"cell_type": "markdown", "metadata": {}, "source": ["There are more sources information available\n", "\n", "* `/proc/cpuinfo`: Holds information about virtual cores, including model and clock speed. Available on most Linux system. Usually used together with `cat`\n", "* `/sys/devices/system/cpu/cpu0/topology/thread_siblings_list`: Holds information about thread siblings for given CPU core (`cpu0` in this case). Use it to find out which thread is mapped to which core."]}, {"cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["0-3\n", "4-7\n"]}], "source": ["!cat /sys/devices/system/cpu/cpu0/topology/thread_siblings_list\n", "!cat /sys/devices/system/cpu/cpu5/topology/thread_siblings_list"]}, {"cell_type": "markdown", "metadata": {}, "source": ["There are various environment variables available within OpenMP (and GCC) to specify binding of threads to cores. See, for instance, the [online documentation of GCC libgomp](https://gcc.gnu.org/onlinedocs/libgomp/Environment-Variables.html). Examples are `OMP_PLACES` or `GOMP_CPU_AFFINITY`.\n", "\n", "**Task**: Run the application enabled with OpenMP from Part A with different binding configurations. Make sure to at least run a) binding all threads to a single core and b) binding threads to different cores.\n", "\n", "Adapt the following command with your configuration \u2013 or follow along accordingly in the non-interactive version of the Notebook.\n", "\n", "What's your maximum speedup?"]}, {"cell_type": "code", "execution_count": 24, "metadata": {"sc18": "solution"}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Affinity: 0,1,2,3\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", " OMP_PLACES = '{0},{1},{2},{3}'\n", "1000x100: Ref: 1.9854 s, This: 0.2326 s, speedup: 8.53\n", "Affinity: 0,5,9,13\n", "<<Waiting for dispatch ...>>\n", "<<Starting on login1>>\n", " OMP_PLACES = '{0},{5},{9},{13}'\n", "1000x100: Ref: 1.9828 s, This: 0.0833 s, speedup: 23.80\n"]}], "source": ["for affinity in [\"0,1,2,3\", \"0,5,9,13\"]:\n", " print(\"Affinity: {}\".format(affinity))\n", " !eval OMP_DISPLAY_ENV=true GOMP_CPU_AFFINITY=$affinity OMP_NUM_THREADS=4 $$SC18_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 100 | grep \"OMP_PLACES\\|speedup\""]}, {"cell_type": "markdown", "metadata": {}, "source": ["#### References\n", "1. https://gcc.gnu.org/onlinedocs/libgomp/GOMP_005fCPU_005fAFFINITY.html"]}, {"cell_type": "markdown", "metadata": {}, "source": ["[Back to Top](#top)\n", "\n", "---"]}, {"cell_type": "markdown", "metadata": {}, "source": ["# Survey<a name=\"survey\"></a>\n", "\n", "Please rememeber to take some time and fill out the [survey](http://bit.ly/sc18-eval)."]}], "metadata": {"kernelspec": {"display_name": "Python 3", "language": "python", "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.6.7"}}, "nbformat": 4, "nbformat_minor": 2} \ No newline at end of file +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Hands-On Performance Optimization\n", + "_Supercomputing 2019 Tutorial \"Application Porting and Optimization on GPU-Accelerated POWER Architectures\", November 18th 2019_\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As for the first task of this tutorial, also this task is primarily designed to be executed as an interactive Jupyter Notebook. However, everything can also be done using an SSH connection to Ascent (or any other POWER9 computer) in your terminal.\n", + "\n", + "## Jupyter notebook execution\n", + "\n", + "When using Jupyter, this Notebook will guide you through the steps. Note that if you execute a cell multiple times while optimizng the code the output will be replaced. You can however duplicate the cell you want to execute and keep its output. Check the _edit_ menu above.\n", + "\n", + "You will always find links to a file browser of the corresponding task subdirectory as well as direct links to the source files you will need to edit as well as the profiling output you need to open locally.\n", + "\n", + "If you want you also can get a terminal in your browser; just open it via the \u00bbNew Launcher\u00ab button (`+`).\n", + "\n", + "## Terminal fallback\n", + "\n", + "The tasks are place in directories named `Task[1-3]`.\n", + "\n", + "Makefile targets are created to cover everything, from compile, to run and profile. Please take a look at the cells containing the make calls as a guide also for the non-interactive version of this description." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "We are using some very fresh compiler features and use GCC 9.2.0 because of that. It should already be in your environment. Let's check!" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc (GCC) 9.2.0\n", + "Copyright (C) 2019 Free Software Foundation, Inc.\n", + "This is free software; see the source for copying conditions. There is NO\n", + "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n", + "\n" + ] + } + ], + "source": [ + "!gcc --version" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Tasks<a name=\"top\"></a>\n", + "\n", + "This session comes with multiple tasks, each one to be found in the respective sub-directory `Task[1-3]`. In each of these directories you will also find Makefiles that are set up so that you can compile and submit all necessary tasks.\n", + "\n", + "Please choose from the task below.\n", + "\n", + "\n", + "* [Task 1](#task1): __Basic compiler optimization flags and compiler annotations__\n", + "\n", + "Improve performance of the CPU Jacobi solver with compiler flags such as `Ofast` and profile-directed feedback. Learn about compiler annotations.\n", + "\n", + "* [Task 2](#task2): __Optimization via Prefetching controlled by compiler__\n", + "\n", + "Improve performance of the CPU Jacobi solver with software prefetching. Some compilers such as IBM XL define flags that can be used to modify the aggressiveness of the hardware prefetcher. Learn to modify the DSCR value through XL and study the impact on application performance. \n", + "* [Task 3](#task3): __Optimization via OpenMP controlled by compiler and the system__\n", + "\n", + "Parallelize the CPU Jacobi solver and determine the right binding to be used for optimal performance. \n", + " \n", + "* [Suvery](#survey) Please remember to take the survey !\n", + " \n", + "### Make Targets <a name=\"make\"></a>\n", + "\n", + "For all tasks we have defined the following make targets. \n", + "\n", + "* __poisson2d__: \n", + " build `poisson2d` binary (default)\n", + "* __run__: \n", + " run `poisson2d` with default parameters\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[Back to Top](#top)\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Task 1: Basic compiler optimization flags and compiler annotations <a name=\"task1\"></a>\n", + "\n", + "\n", + "### Overview\n", + "\n", + "The goal of this task is to understand different options available to optimize the performance of the CPU Jacobi solver \n", + "\n", + "Your task is to:\n", + "\n", + "* Optimize performance with `-Ofast` flag\n", + "* Verify the cause for performance improvement by viewing perf profiles of O3 and Ofast binaries \n", + "* Optimize performance with profile directed feedback \n", + "* Generate compiler annotations/remarks to understand the optimizations done by the compiler with and without profile directed feedback \n", + "\n", + "\n", + "First, change the working directory to `Task1`." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/autofs/nccsopen-svm1_home/aherten/SC19-Tutorial/3-Optimizing_POWER/Handson/Task1\n" + ] + } + ], + "source": [ + "%cd Task1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Part A: `-Ofast` vs. `-O3`\n", + "\n", + "We are to compare the performance of the binary being compiled with `-Ofast` optimization and with `-O3` optimization. As in the previous task, we use a `Makefile` for compilation. The `Makefile` targets `poisson2d_O3` and `poisson2d_Ofast` are already prepared. \n", + "\n", + "**TASK**: Add `-O3` as the optimization flag for the `poisson2d_O3` target by using the corresponding `CFLAGS` definition. There are notes relating to this Task 1 in the header of the `Makefile`. Compile the code using `make` as indicated below and run with the `Make` targets `run`, `run_perf` and `run_perf_recrep`. " + ] + }, + { + "cell_type": "code", + "execution_count": 84, + "metadata": { + "collapsed": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc -c -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -O3 poisson2d_reference.c -o poisson2d_reference.o -lm\n", + "gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -O3 poisson2d.c poisson2d_reference.o -o poisson2d -lm\n" + ] + } + ], + "source": [ + "!make poisson2d_O3" + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", + "Job <24897> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "4.73user 0.00system 0:04.73elapsed 99%CPU (0avgtext+0avgdata 24256maxresident)k\n", + "256inputs+0outputs (0major+480minor)pagefaults 0swaps\n" + ] + } + ], + "source": [ + "!make run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's have a look at the output of the `Makefile` target `run_perf`. It invokes the GNU _perf_ tool to print out details of the number of instructions executed and the number of cycles taken by POWER9 to execute the program. Feel free to add further counter to this call to _perf_." + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,instructions ./poisson2d\n", + "Job <24898> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "\n", + " Performance counter stats for './poisson2d':\n", + "\n", + " 16264721613 cycles:u \n", + " 28463907825 instructions:u # 1.75 insn per cycle \n", + "\n", + " 4.738444892 seconds time elapsed\n", + "\n" + ] + } + ], + "source": [ + "!make run_perf" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next we run the makefile with target `run_perf_recrep` that prints the top routines of the application in terms of hotness by using a combination of `perf record ./app` and `perf report`. " + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf record -e cycles --output=/gpfs/wolf/trn003/scratch/aherten//cycles.data ./poisson2d\n", + "Job <24899> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "[ perf record: Woken up 3 times to write data ]\n", + "[ perf record: Captured and wrote 0.739 MB /gpfs/wolf/trn003/scratch/aherten//cycles.data (19102 samples) ]\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf report -i /gpfs/wolf/trn003/scratch/aherten//cycles.data --stdio\n", + "Job <24900> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "# To display the perf.data header info, please use --header/--header-only options.\n", + "#\n", + "#\n", + "# Total Lost Samples: 0\n", + "#\n", + "# Samples: 19K of event 'cycles:u'\n", + "# Event count (approx.): 16254596654\n", + "#\n", + "# Overhead Command Shared Object Symbol \n", + "# ........ ......... ............. ........................................\n", + "#\n", + " 65.50% poisson2d poisson2d [.] 00000038.plt_call.fmax@@GLIBC_2.17\n", + " 21.21% poisson2d poisson2d [.] main\n", + " 9.18% poisson2d libc-2.17.so [.] __memcpy_power7\n", + " 3.28% poisson2d libm-2.17.so [.] __fmaxf\n", + " 0.74% poisson2d libm-2.17.so [.] __exp_finite\n", + " 0.04% poisson2d poisson2d [.] 00000038.plt_call.memcpy@@GLIBC_2.17\n", + " 0.01% poisson2d libm-2.17.so [.] __GI___exp\n", + " 0.01% poisson2d ld-2.17.so [.] check_match.10253\n", + " 0.01% poisson2d ld-2.17.so [.] do_lookup_x\n", + " 0.00% poisson2d ld-2.17.so [.] _dl_lookup_symbol_x\n", + " 0.00% poisson2d ld-2.17.so [.] _dl_relocate_object\n", + " 0.00% poisson2d ld-2.17.so [.] strcmp\n", + " 0.00% poisson2d ld-2.17.so [.] _wordcopy_fwd_aligned\n", + " 0.00% poisson2d ld-2.17.so [.] _dl_sysdep_start\n", + " 0.00% poisson2d ld-2.17.so [.] _start\n", + "\n", + "\n", + "#\n", + "# (Tip: Limit to show entries above 5% only: perf report --percent-limit 5)\n", + "#\n" + ] + } + ], + "source": [ + "# run_perf_recrep displays the top hot routines \n", + "!make run_perf_recrep" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**TASK**: Now add the optimization flag `Ofast` to the `CFLAGS` for target `poisson2d_Ofast`. Compile the program with the target `poisson2d_Ofast` and run and analyse it as before with `run`, `run_perf` and `run_perf_recrep`.\n", + "\n", + "What difference do you see?" + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast poisson2d.c poisson2d_reference.o -o poisson2d -lm\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", + "Job <24901> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "2.41user 0.00system 0:02.41elapsed 99%CPU (0avgtext+0avgdata 24256maxresident)k\n", + "256inputs+0outputs (0major+480minor)pagefaults 0swaps\n" + ] + } + ], + "source": [ + "!make poisson2d_Ofast \n", + "!make run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Again, run a `perf`-instrumented version:" + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,instructions ./poisson2d\n", + "Job <24902> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "\n", + " Performance counter stats for './poisson2d':\n", + "\n", + " 8258991976 cycles:u \n", + " 12013091172 instructions:u # 1.45 insn per cycle \n", + "\n", + " 2.408703909 seconds time elapsed\n", + "\n" + ] + } + ], + "source": [ + "!make run_perf" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Generate the list of top routines in terms of hotness:" + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf record -e cycles --output=/gpfs/wolf/trn003/scratch/aherten//cycles.data ./poisson2d\n", + "Job <24903> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "[ perf record: Woken up 2 times to write data ]\n", + "[ perf record: Captured and wrote 0.382 MB /gpfs/wolf/trn003/scratch/aherten//cycles.data (9728 samples) ]\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf report -i /gpfs/wolf/trn003/scratch/aherten//cycles.data --stdio\n", + "Job <24904> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "# To display the perf.data header info, please use --header/--header-only options.\n", + "#\n", + "#\n", + "# Total Lost Samples: 0\n", + "#\n", + "# Samples: 9K of event 'cycles:u'\n", + "# Event count (approx.): 8268811890\n", + "#\n", + "# Overhead Command Shared Object Symbol \n", + "# ........ ......... ............. ........................................\n", + "#\n", + " 81.12% poisson2d poisson2d [.] main\n", + " 17.97% poisson2d libc-2.17.so [.] __memcpy_power7\n", + " 0.79% poisson2d libm-2.17.so [.] __exp_finite\n", + " 0.04% poisson2d poisson2d [.] 00000038.plt_call.memcpy@@GLIBC_2.17\n", + " 0.02% poisson2d ld-2.17.so [.] do_lookup_x\n", + " 0.01% poisson2d libc-2.17.so [.] vfprintf@@GLIBC_2.17\n", + " 0.01% poisson2d libc-2.17.so [.] _dl_addr\n", + " 0.01% poisson2d ld-2.17.so [.] _dl_relocate_object\n", + " 0.01% poisson2d ld-2.17.so [.] check_match.10253\n", + " 0.01% poisson2d ld-2.17.so [.] _dl_lookup_symbol_x\n", + " 0.01% poisson2d ld-2.17.so [.] strcmp\n", + " 0.00% poisson2d ld-2.17.so [.] open_path\n", + " 0.00% poisson2d ld-2.17.so [.] init_tls\n", + " 0.00% poisson2d ld-2.17.so [.] _dl_sysdep_start\n", + " 0.00% poisson2d ld-2.17.so [.] _start\n", + "\n", + "\n", + "#\n", + "# (Tip: For tracepoint events, try: perf report -s trace_fields)\n", + "#\n" + ] + } + ], + "source": [ + "!make run_perf_recrep" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If `perf` is unavailable to you on other machines, you can also study the disassembly with `objdump`: `objdump -lSd ./poisson2d > poisson2d.dis` (feel free to experiment with this in the Notebook as well, just prefix the command with a `!` to execute it.)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Interpretation\n", + "\n", + "Depending on the application requirement, if a high precision of results is not mandatory, one can compile an application with `-Ofast` which enables `\u2013ffast-math` option that implements the same math function in a relaxed manner very similar to how general mathematical expressions are implemented and avoids the overhead of calling a function from the math library. Comparing the files, you will see that the `-Ofast` binary natively implements the `fmax` function using instructions available in the hardware. The `-O3` binary makes a library call to compute `fmax` to follow a stricter _IEEE_ requirement for accuracy." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Part B: Profile-directed Feedback\n", + "\n", + "For the first level of optimization we see that `Ofast` cut the execution time of the `O3` binary by almost half.\n", + "\n", + "We can optimize the performance further by using profile-directed feedback optimization.\n", + "\n", + "To compile using profile-directed feedback with the GCC compiler we need to build the appplication in three stages:\n", + "\n", + "1. Instrument binary;\n", + "2. Run binary with training, gather profile information;\n", + "3. Use profile information to generate optimized binary.\n", + "\n", + "\n", + "Step 1 is achieved by compiling the binary with the correct flag \u2013\u00a0`-fprofile-generate`. In our case, we need to specify an output location, which should be `$(SC19_DIR_SCRATCH)`.\n", + "\n", + "Step 2 consists of a usual, albeit shorter run of the instrumented binary. The can be very short, though the parameters need to be representative of the actual run. After the binary ran, an output file (with file extension `.gcda`) is written to the directory specified during compilation.\n", + "\n", + "For Step 3, the binary is once again compiled, but this time using the `gcda` profile just generated. The according flag is `-fprofile-use`, which we set to `$(SC19_DIR_SCRATCH)` as well.\n", + "\n", + "In our `Makefile` at hand, we prepared the steps already for you in the form of two targets.\n", + "\n", + "* `poisson2d_train`: Will compile the binary with profile-directed feedback\n", + "* `poisson2d_ref`: Will take a generated profile and compile a new, optimized binary\n", + "\n", + "By using dependencies, between these two targets a profile run is launched.\n", + "\n", + "**TASK**: Edit the [Makefile](`Makefile`) and add the `-fprofile-*` flags to the `CFLAGS` of `poisson2d_train` and\n", + "`poisson2d_ref` as outline in the file.\n", + "\n", + "After that, you may launch them with the following cells (`gen_profile` is a meta-target and uses `poisson2d_train` and `poisson2d_ref`). If you need to clean the generated profile, you may use `make clean_profile`." + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast -fprofile-generate=/gpfs/wolf/trn003/scratch/aherten/ poisson2d.c -o poisson2d_train -lm \n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS ./poisson2d_train 100 100 100\n", + "Job <24905> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 100 iterations on 100 x 100 mesh\n", + "Calculate current execution.\n", + " 0, 0.249490\n", + "echo `date` > /gpfs/wolf/trn003/scratch/aherten//.profile_generated\n", + "gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast -fprofile-use=/gpfs/wolf/trn003/scratch/aherten/ poisson2d.c -o poisson2d_ref -lm \n", + "cp poisson2d_ref poisson2d\n" + ] + } + ], + "source": [ + "!make gen_profile" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If the previous cell executed correctly, you now have your optimized executable. Let's see if it even fast than before!" + ] + }, + { + "cell_type": "code", + "execution_count": 80, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", + "Job <24906> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "2.28user 0.01system 0:02.30elapsed 99%CPU (0avgtext+0avgdata 24192maxresident)k\n", + "256inputs+0outputs (0major+479minor)pagefaults 0swaps\n" + ] + } + ], + "source": [ + "!make run" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "exercise": "solution" + }, + "source": [ + "Great! It is! In our tests, this shaved off another 5%." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's also measure instructions and cycles" + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,instructions ./poisson2d\n", + "Job <24907> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "\n", + " Performance counter stats for './poisson2d':\n", + "\n", + " 7925983538 cycles:u \n", + " 12253080719 instructions:u # 1.55 insn per cycle \n", + "\n", + " 2.313471365 seconds time elapsed\n", + "\n" + ] + } + ], + "source": [ + "!make run_perf" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "What is your speed-up? Feel free to run with larger problem sizes (mesh; iterations)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Part C: Compiler annotations/Remarks\n", + "\n", + "Usually, all compilers provide an option to emit annotations or remarks by the compiler. These remarks summarize the optimizations done in detail, the location in source where these optimizations were done. There exist options that also indicate optimizations that were missed and the reason why they could not be done. \n", + "\n", + "To generate compiler annotations using GCC, one uses `-fopt-info-all`. If you only want to see the missed options, use the option `-fopt-info-missed` instead of `-fopt-info-all`. See also the [documentation of GCC regarding the flag](https://gcc.gnu.org/onlinedocs/gcc/Developer-Options.html#index-fopt-info).\n", + "\n", + "**TASK**: Have a looK at the `CFLAGS` of the `Makefile` target `poisson2d_Ofast_info`. Add the flag `-fopt-info-all` to the list of flags. This will print optimisation information to stdout. If you rather want to print to this information to a file, use \u2013\u00a0for example \u2013\u00a0`-fopt-info-all=(SC19_DIR_SCRATCH)/filename`." + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast -fopt-info-all poisson2d.c poisson2d_reference.o -o poisson2d_Ofast_info -lm\n", + "poisson2d.c:62:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "poisson2d.c:61:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "poisson2d.c:56:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "poisson2d.c:52:20: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "poisson2d.c:161:5: missed: not inlinable: main/33 -> free/38, function body not available\n", + "poisson2d.c:159:5: missed: not inlinable: main/33 -> free/38, function body not available\n", + "poisson2d.c:158:5: missed: not inlinable: main/33 -> free/38, function body not available\n", + "poisson2d.c:142:31: missed: not inlinable: main/33 -> printf/36, function body not available\n", + "poisson2d.c:103:5: missed: not inlinable: main/33 -> __builtin_puts/37, function body not available\n", + "poisson2d.c:96:5: missed: not inlinable: main/33 -> printf/36, function body not available\n", + "poisson2d.c:78:29: missed: not inlinable: main/33 -> exp/35, function body not available\n", + "poisson2d.c:68:41: missed: not inlinable: main/33 -> malloc/34, function body not available\n", + "poisson2d.c:67:41: missed: not inlinable: main/33 -> malloc/34, function body not available\n", + "poisson2d.c:65:41: missed: not inlinable: main/33 -> malloc/34, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "Unit growth for small function inlining: 207->207 (0%)\n", + "\n", + "Inlined 4 calls, eliminated 0 functions\n", + "\n", + "consider run-time aliasing test between *_84 and *_87\n", + "consider run-time aliasing test between *_92 and *_97\n", + "consider run-time aliasing test between *_104 and *_107\n", + "consider run-time aliasing test between *_111 and *_115\n", + "poisson2d.c:124:13: optimized: Loop 8 distributed: split to 0 loops and 1 library calls.\n", + "poisson2d.c:90:9: optimized: Loop 10 distributed: split to 0 loops and 1 library calls.\n", + "poisson2d.c:108:25: missed: couldn't vectorize loop\n", + "poisson2d.c:108:25: missed: not vectorized: loop nest containing two or more consecutive inner loops cannot be vectorized\n", + "poisson2d.c:136:9: missed: couldn't vectorize loop\n", + "poisson2d.c:136:9: missed: Loop costings may not be worthwhile.\n", + "poisson2d.c:131:9: missed: couldn't vectorize loop\n", + "poisson2d.c:131:9: missed: Loop costings may not be worthwhile.\n", + "poisson2d.c:122:9: missed: couldn't vectorize loop\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_543, _539, _549);\n", + "poisson2d.c:112:9: missed: couldn't vectorize loop\n", + "poisson2d.c:112:9: missed: not vectorized: control flow in loop.\n", + "poisson2d.c:114:13: optimized: loop vectorized using 16 byte vectors\n", + "poisson2d.c:88:5: missed: couldn't vectorize loop\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_528, 0, _531);\n", + "poisson2d.c:72:5: missed: couldn't vectorize loop\n", + "poisson2d.c:78:27: missed: not vectorized: complicated access pattern.\n", + "poisson2d.c:74:9: missed: couldn't vectorize loop\n", + "poisson2d.c:78:29: missed: not vectorized: relevant stmt not supported: _27 = exp (_21);\n", + "poisson2d.c:43:5: note: vectorized 1 loops in function.\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_528, 0, _531);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_543, _539, _549);\n", + "poisson2d.c:114:13: optimized: loop turned into non-loop; it never loops\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _187 = strtol (_1, 0B, 10);\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _189 = strtol (_2, 0B, 10);\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _193 = strtol (_3, 0B, 10);\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _191 = strtol (_4, 0B, 10);\n", + "poisson2d.c:65:41: missed: statement clobbers memory: A_153 = malloc (_7);\n", + "poisson2d.c:67:41: missed: statement clobbers memory: Anew_155 = malloc (_7);\n", + "poisson2d.c:68:41: missed: statement clobbers memory: rhs_157 = malloc (_7);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_528, 0, _531);\n", + "poisson2d.c:96:5: missed: statement clobbers memory: printf (\"Jacobi relaxation calculation: max %d iterations on %d x %d mesh\\n\", iter_max_130, ny_139, nx_195);\n", + "poisson2d.c:103:5: missed: statement clobbers memory: __builtin_puts (&\"Calculate current execution.\"[0]);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_543, _539, _549);\n", + "poisson2d.c:142:31: missed: statement clobbers memory: printf (\"%5d, %0.6f\\n\", iter_237, error_219);\n", + "poisson2d.c:158:5: missed: statement clobbers memory: free (rhs_202);\n", + "poisson2d.c:159:5: missed: statement clobbers memory: free (Anew_124);\n", + "poisson2d.c:161:5: missed: statement clobbers memory: free (A_123);\n", + "poisson2d.c:65:41: missed: statement clobbers memory: A_144 = malloc (8000000);\n", + "poisson2d.c:67:41: missed: statement clobbers memory: Anew_143 = malloc (8000000);\n", + "poisson2d.c:68:41: missed: statement clobbers memory: rhs_142 = malloc (8000000);\n" + ] + } + ], + "source": [ + "!make poisson2d_Ofast_info" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's compare this with the output during compilation when using profile-directed feedback from Task 1 B.\n", + "\n", + "**TASK**: \n", + "Adapt the `CFLAGS` of `poisson2d_ref_info` to include `-fopt-info-all` **and** the profile input of `-fprofile-use=\u2026` here. *(Be advised: Long output!)*" + ] + }, + { + "cell_type": "code", + "execution_count": 83, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast -fopt-info-all -fprofile-use=/gpfs/wolf/trn003/scratch/aherten/ -Ofast -fprofile-generate=/gpfs/wolf/trn003/scratch/aherten/ poisson2d.c -o poisson2d_train -lm \n", + "poisson2d.c:62:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "poisson2d.c:61:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "poisson2d.c:56:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "poisson2d.c:52:20: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "Increasing alignment of decl: __gcov0.main\n", + "poisson2d.c:164:1: missed: not inlinable: _GLOBAL__sub_D_00100_1_main/48 -> __gcov_exit/55, function body not available\n", + "poisson2d.c:164:1: missed: not inlinable: _GLOBAL__sub_I_00100_0_main/47 -> __gcov_init/54, function body not available\n", + "poisson2d.c:161:5: missed: not inlinable: main/33 -> free/38, function body not available\n", + "poisson2d.c:159:5: missed: not inlinable: main/33 -> free/38, function body not available\n", + "poisson2d.c:158:5: missed: not inlinable: main/33 -> free/38, function body not available\n", + "poisson2d.c:142:31: missed: not inlinable: main/33 -> printf/36, function body not available\n", + "poisson2d.c:103:5: missed: not inlinable: main/33 -> __builtin_puts/37, function body not available\n", + "poisson2d.c:96:5: missed: not inlinable: main/33 -> printf/36, function body not available\n", + "poisson2d.c:78:29: missed: not inlinable: main/33 -> exp/35, function body not available\n", + "poisson2d.c:68:41: missed: not inlinable: main/33 -> malloc/34, function body not available\n", + "poisson2d.c:67:41: missed: not inlinable: main/33 -> malloc/34, function body not available\n", + "poisson2d.c:65:41: missed: not inlinable: main/33 -> malloc/34, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "Unit growth for small function inlining: 295->295 (0%)\n", + "\n", + "Inlined 4 calls, eliminated 0 functions\n", + "\n", + "consider run-time aliasing test between *_84 and *_87\n", + "consider run-time aliasing test between *_92 and *_97\n", + "consider run-time aliasing test between *_104 and *_107\n", + "consider run-time aliasing test between *_111 and *_115\n", + "poisson2d.c:124:13: optimized: Loop 8 distributed: split to 0 loops and 1 library calls.\n", + "poisson2d.c:90:9: optimized: Loop 10 distributed: split to 0 loops and 1 library calls.\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_64, _135, _313);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_632, 0, _239);\n", + "poisson2d.c:108:25: missed: couldn't vectorize loop\n", + "poisson2d.c:108:25: missed: not vectorized: loop nest containing two or more consecutive inner loops cannot be vectorized\n", + "poisson2d.c:136:9: missed: couldn't vectorize loop\n", + "poisson2d.c:136:9: missed: Loop costings may not be worthwhile.\n", + "poisson2d.c:131:9: missed: couldn't vectorize loop\n", + "poisson2d.c:131:9: missed: Loop costings may not be worthwhile.\n", + "poisson2d.c:122:9: missed: couldn't vectorize loop\n", + "poisson2d.c:122:9: missed: not vectorized: control flow in loop.\n", + "poisson2d.c:112:9: missed: couldn't vectorize loop\n", + "poisson2d.c:112:9: missed: not vectorized: control flow in loop.\n", + "poisson2d.c:114:13: optimized: loop vectorized using 16 byte vectors\n", + "poisson2d.c:88:5: missed: couldn't vectorize loop\n", + "poisson2d.c:88:5: missed: not vectorized: control flow in loop.\n", + "poisson2d.c:72:5: missed: couldn't vectorize loop\n", + "poisson2d.c:72:5: missed: not vectorized: control flow in loop.\n", + "poisson2d.c:74:9: missed: couldn't vectorize loop\n", + "poisson2d.c:78:29: missed: not vectorized: relevant stmt not supported: _27 = exp (_21);\n", + "poisson2d.c:43:5: note: vectorized 1 loops in function.\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_632, 0, _239);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_64, _135, _313);\n", + "poisson2d.c:114:13: optimized: loop turned into non-loop; it never loops\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _187 = strtol (_1, 0B, 10);\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _189 = strtol (_2, 0B, 10);\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _193 = strtol (_3, 0B, 10);\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _191 = strtol (_4, 0B, 10);\n", + "poisson2d.c:65:41: missed: statement clobbers memory: A_153 = malloc (_7);\n", + "poisson2d.c:67:41: missed: statement clobbers memory: Anew_155 = malloc (_7);\n", + "poisson2d.c:68:41: missed: statement clobbers memory: rhs_157 = malloc (_7);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_632, 0, _239);\n", + "poisson2d.c:96:5: missed: statement clobbers memory: printf (\"Jacobi relaxation calculation: max %d iterations on %d x %d mesh\\n\", iter_max_337, ny_124, nx_286);\n", + "poisson2d.c:103:5: missed: statement clobbers memory: __builtin_puts (&\"Calculate current execution.\"[0]);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_64, _135, _313);\n", + "poisson2d.c:142:31: missed: statement clobbers memory: printf (\"%5d, %0.6f\\n\", iter_316, error_118);\n", + "poisson2d.c:158:5: missed: statement clobbers memory: free (rhs_127);\n", + "poisson2d.c:159:5: missed: statement clobbers memory: free (Anew_311);\n", + "poisson2d.c:161:5: missed: statement clobbers memory: free (A_122);\n", + "poisson2d.c:65:41: missed: statement clobbers memory: A_129 = malloc (8000000);\n", + "poisson2d.c:67:41: missed: statement clobbers memory: Anew_132 = malloc (8000000);\n", + "poisson2d.c:68:41: missed: statement clobbers memory: rhs_140 = malloc (8000000);\n", + "poisson2d.c:136:9: note: considering unrolling loop 7 at BB 53\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:136:9: optimized: loop unrolled 7 times (header execution count 9800)\n", + "poisson2d.c:131:9: note: considering unrolling loop 6 at BB 50\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:131:9: optimized: loop unrolled 7 times (header execution count 9800)\n", + "poisson2d.c:122:9: note: considering unrolling loop 5 at BB 47\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:122:9: optimized: loop unrolled 3 times (header execution count 9800)\n", + "poisson2d.c:118:25: note: considering unrolling loop 13 at BB 33\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:118:25: optimized: loop unrolled 3 times (header execution count 436550)\n", + "poisson2d.c:118:25: note: considering unrolling loop 9 at BB 30\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:112:9: note: considering unrolling loop 14 at BB 42\n", + "poisson2d.c:43:5: note: considering unrolling loop 4 at BB 40\n", + "poisson2d.c:108:25: note: considering unrolling loop 3 at BB 60\n", + "poisson2d.c:88:5: note: considering unrolling loop 2 at BB 23\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:88:5: optimized: loop unrolled 3 times (header execution count 100)\n", + "poisson2d.c:74:9: note: considering unrolling loop 11 at BB 12\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:74:9: optimized: loop unrolled 3 times (header execution count 9604)\n", + "poisson2d.c:72:5: note: considering unrolling loop 1 at BB 16\n", + "poisson2d.c:164:1: missed: statement clobbers memory: __gcov_init (&*.LPBX0);\n", + "poisson2d.c:164:1: missed: statement clobbers memory: __gcov_exit ();\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS ./poisson2d_train 100 100 100\n", + "Job <24908> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "libgcov profiling error:/gpfs/wolf/trn003/scratch/aherten//#autofs#nccsopen-svm1_home#aherten#SC19-Tutorial#3-Optimizing_POWER#Handson#Task1#poisson2d.gcda:overwriting an existing profile data with a different timestamp\n", + "Jacobi relaxation calculation: max 100 iterations on 100 x 100 mesh\n", + "Calculate current execution.\n", + " 0, 0.249490\n", + "echo `date` > /gpfs/wolf/trn003/scratch/aherten//.profile_generated\n", + "gcc -std=c99 -mcpu=power9 -DUSE_DOUBLE -mvsx -maltivec -Ofast -fopt-info-all -fprofile-use=/gpfs/wolf/trn003/scratch/aherten/ poisson2d.c poisson2d_reference.o -o poisson2d_ref_info -lm\n", + "poisson2d.c:62:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "poisson2d.c:61:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "poisson2d.c:56:14: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "poisson2d.c:52:20: optimized: Inlining atoi/24 into main/33 (always_inline).\n", + "poisson2d.c:161:5: missed: not inlinable: main/33 -> free/38, function body not available\n", + "poisson2d.c:159:5: missed: not inlinable: main/33 -> free/38, function body not available\n", + "poisson2d.c:158:5: missed: not inlinable: main/33 -> free/38, function body not available\n", + "poisson2d.c:142:31: missed: not inlinable: main/33 -> printf/36, function body not available\n", + "poisson2d.c:103:5: missed: not inlinable: main/33 -> __builtin_puts/37, function body not available\n", + "poisson2d.c:96:5: missed: not inlinable: main/33 -> printf/36, function body not available\n", + "poisson2d.c:78:29: missed: not inlinable: main/33 -> exp/35, function body not available\n", + "poisson2d.c:68:41: missed: not inlinable: main/33 -> malloc/34, function body not available\n", + "poisson2d.c:67:41: missed: not inlinable: main/33 -> malloc/34, function body not available\n", + "poisson2d.c:65:41: missed: not inlinable: main/33 -> malloc/34, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "/usr/include/stdlib.h:280:16: missed: not inlinable: main/33 -> strtol/39, function body not available\n", + "Unit growth for small function inlining: 207->207 (0%)\n", + "\n", + "Inlined 4 calls, eliminated 0 functions\n", + "\n", + "consider run-time aliasing test between *_84 and *_87\n", + "consider run-time aliasing test between *_92 and *_97\n", + "consider run-time aliasing test between *_104 and *_107\n", + "consider run-time aliasing test between *_111 and *_115\n", + "poisson2d.c:124:13: optimized: Loop 8 distributed: split to 0 loops and 1 library calls.\n", + "poisson2d.c:90:9: optimized: Loop 10 distributed: split to 0 loops and 1 library calls.\n", + "poisson2d.c:108:25: missed: couldn't vectorize loop\n", + "poisson2d.c:108:25: missed: not vectorized: loop nest containing two or more consecutive inner loops cannot be vectorized\n", + "poisson2d.c:136:9: missed: couldn't vectorize loop\n", + "poisson2d.c:136:9: missed: Loop costings may not be worthwhile.\n", + "poisson2d.c:131:9: missed: couldn't vectorize loop\n", + "poisson2d.c:131:9: missed: Loop costings may not be worthwhile.\n", + "poisson2d.c:122:9: missed: couldn't vectorize loop\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_539, _535, _544);\n", + "poisson2d.c:112:9: missed: couldn't vectorize loop\n", + "poisson2d.c:112:9: missed: not vectorized: control flow in loop.\n", + "poisson2d.c:114:13: optimized: loop vectorized using 16 byte vectors\n", + "poisson2d.c:88:5: missed: couldn't vectorize loop\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_524, 0, _527);\n", + "poisson2d.c:72:5: missed: couldn't vectorize loop\n", + "poisson2d.c:78:27: missed: not vectorized: complicated access pattern.\n", + "poisson2d.c:74:9: missed: couldn't vectorize loop\n", + "poisson2d.c:78:29: missed: not vectorized: relevant stmt not supported: _27 = exp (_21);\n", + "poisson2d.c:43:5: note: vectorized 1 loops in function.\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_524, 0, _527);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_539, _535, _544);\n", + "poisson2d.c:114:13: optimized: loop turned into non-loop; it never loops\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _187 = strtol (_1, 0B, 10);\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _189 = strtol (_2, 0B, 10);\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _193 = strtol (_3, 0B, 10);\n", + "/usr/include/stdlib.h:280:16: missed: statement clobbers memory: _191 = strtol (_4, 0B, 10);\n", + "poisson2d.c:65:41: missed: statement clobbers memory: A_153 = malloc (_7);\n", + "poisson2d.c:67:41: missed: statement clobbers memory: Anew_155 = malloc (_7);\n", + "poisson2d.c:68:41: missed: statement clobbers memory: rhs_157 = malloc (_7);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memset (_524, 0, _527);\n", + "poisson2d.c:96:5: missed: statement clobbers memory: printf (\"Jacobi relaxation calculation: max %d iterations on %d x %d mesh\\n\", iter_max_130, ny_139, nx_195);\n", + "poisson2d.c:103:5: missed: statement clobbers memory: __builtin_puts (&\"Calculate current execution.\"[0]);\n", + "poisson2d.c:43:5: missed: statement clobbers memory: __builtin_memcpy (_539, _535, _544);\n", + "poisson2d.c:142:31: missed: statement clobbers memory: printf (\"%5d, %0.6f\\n\", iter_237, error_219);\n", + "poisson2d.c:158:5: missed: statement clobbers memory: free (rhs_202);\n", + "poisson2d.c:159:5: missed: statement clobbers memory: free (Anew_124);\n", + "poisson2d.c:161:5: missed: statement clobbers memory: free (A_123);\n", + "poisson2d.c:65:41: missed: statement clobbers memory: A_144 = malloc (8000000);\n", + "poisson2d.c:67:41: missed: statement clobbers memory: Anew_143 = malloc (8000000);\n", + "poisson2d.c:68:41: missed: statement clobbers memory: rhs_142 = malloc (8000000);\n", + "poisson2d.c:136:9: note: considering unrolling loop 7 at BB 47\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:136:9: optimized: loop unrolled 7 times (header execution count 9800)\n", + "poisson2d.c:131:9: note: considering unrolling loop 6 at BB 44\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:131:9: optimized: loop unrolled 7 times (header execution count 9800)\n", + "poisson2d.c:122:9: note: considering unrolling loop 5 at BB 40\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:122:9: optimized: loop unrolled 7 times (header execution count 9701)\n", + "poisson2d.c:118:25: note: considering unrolling loop 13 at BB 27\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:118:25: optimized: loop unrolled 3 times (header execution count 436550)\n", + "poisson2d.c:118:25: note: considering unrolling loop 9 at BB 24\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:112:9: note: considering unrolling loop 14 at BB 37\n", + "poisson2d.c:43:5: note: considering unrolling loop 4 at BB 35\n", + "poisson2d.c:108:25: note: considering unrolling loop 3 at BB 51\n", + "poisson2d.c:88:5: note: considering unrolling loop 2 at BB 18\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:88:5: optimized: loop unrolled 7 times (header execution count 99)\n", + "poisson2d.c:74:9: note: considering unrolling loop 11 at BB 9\n", + "considering unrolling loop with constant number of iterations\n", + "considering unrolling loop with runtime-computable number of iterations\n", + "poisson2d.c:74:9: optimized: loop unrolled 3 times (header execution count 9604)\n", + "poisson2d.c:72:5: note: considering unrolling loop 1 at BB 14\n" + ] + } + ], + "source": [ + "!make poisson2d_ref_info" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Comparing the annotations generated of a plain `-Ofast` optimization level and the one generated at `-Ofast` and profile directed feedback, we observe that many more optimizations are possible due to profile information.\n", + "\n", + "For instance you will see annotations such as\n", + "```\n", + "poisson2d.c:114:25: optimized: loop unrolled 3 times (header execution count 436550)\n", + "```\n", + "\n", + "The execution count indicates the dynamic execution count of the node at runtime. This information determines which paths are hotter and subsequently facilitate additional optimizations." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### References\n", + "\n", + "1. https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html\n", + "2. https://perf.wiki.kernel.org/index.php/Tutorial" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[Back to Top](#top)\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Task 2:<a name=\"task2\"></a> Impact of Prefetching on Performance\n", + "\n", + "\n", + "### Overview\n", + "\n", + "* Study the difference of program execution time of different optimization levels with and without software prefetching.\n", + "* Verify the impact by measuring cache counters with and without prefetching.\n", + "* Learn how to modify contents of DSCR (*Data Stream Control Register*) using IBM XL compiler and study the impact with different values to DSCR. \n", + "\n", + "But first, lets change directory to that of Task 2" + ] + }, + { + "cell_type": "code", + "execution_count": 85, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/autofs/nccsopen-svm1_home/aherten/SC19-Tutorial/3-Optimizing_POWER/Handson/Task2\n" + ] + } + ], + "source": [ + "%cd ../Task2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Part A: Software Prefetching" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**TASK**: Look at the Makefile and work on the TODOs. \n", + "\n", + "- First generate a `-Ofast`-optimised binary and note down the performance in terms of cycles, seconds, and L3 misses. This is our baseline!\n", + "- Modify the `Makefile` to add the option for software prefetching (`-fprefetch-loop-arrays`). Compare performance of `-Ofast` with and without software prefetching" + ] + }, + { + "cell_type": "code", + "execution_count": 97, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "rm -f poisson2d poisson2d*.o\n" + ] + } + ], + "source": [ + "!make clean" + ] + }, + { + "cell_type": "code", + "execution_count": 88, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "make: `poisson2d' is up to date.\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", + "Job <24911> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "2.39user 0.01system 0:02.40elapsed 100%CPU (0avgtext+0avgdata 24256maxresident)k\n", + "0inputs+0outputs (0major+480minor)pagefaults 0swaps\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,r168a4 ./poisson2d\n", + "Job <24912> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "\n", + " Performance counter stats for './poisson2d':\n", + "\n", + " 8271503902 cycles:u \n", + " 481152478 r168a4:u \n", + "\n", + " 2.412224884 seconds time elapsed\n", + "\n" + ] + } + ], + "source": [ + "!make poisson2d CC=gcc\n", + "!make run\n", + "!make l3missstats" + ] + }, + { + "cell_type": "code", + "execution_count": 98, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc -std=c99 -DUSE_DOUBLE -Ofast -mcpu=power9 -mvsx -maltivec -fprefetch-loop-arrays -fprefetch-loop-arrays poisson2d.c -o poisson2d_pref -lm\n", + "cp poisson2d_pref poisson2d\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", + "Job <24919> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "1.92user 0.00system 0:01.93elapsed 99%CPU (0avgtext+0avgdata 24256maxresident)k\n", + "256inputs+0outputs (0major+480minor)pagefaults 0swaps\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,r168a4 ./poisson2d\n", + "Job <24920> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "\n", + " Performance counter stats for './poisson2d':\n", + "\n", + " 6586609284 cycles:u \n", + " 459879452 r168a4:u \n", + "\n", + " 1.925399505 seconds time elapsed\n", + "\n" + ] + } + ], + "source": [ + "!make poisson2d_pref CC=gcc\n", + "!make run\n", + "!make l3missstats" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**TASK**: Repeat the experiment with the `-O3` flag. Have a look at the `Makefile` and the outlined TODO. There's a position to easily adapt `-Ofast`\u2192`-O3`!" + ] + }, + { + "cell_type": "code", + "execution_count": 100, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc -std=c99 -DUSE_DOUBLE -O3 -mcpu=power9 -mvsx -maltivec poisson2d.c -o poisson2d -lm\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", + "Job <24923> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "4.73user 0.00system 0:04.73elapsed 99%CPU (0avgtext+0avgdata 24256maxresident)k\n", + "256inputs+0outputs (0major+479minor)pagefaults 0swaps\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,r168a4 ./poisson2d\n", + "Job <24924> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "\n", + " Performance counter stats for './poisson2d':\n", + "\n", + " 16445764669 cycles:u \n", + " 645094089 r168a4:u \n", + "\n", + " 4.792567763 seconds time elapsed\n", + "\n" + ] + } + ], + "source": [ + "!make poisson2d CC=gcc -B\n", + "!make run\n", + "!make l3missstats" + ] + }, + { + "cell_type": "code", + "execution_count": 101, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc -std=c99 -DUSE_DOUBLE -O3 -mcpu=power9 -mvsx -maltivec -fprefetch-loop-arrays poisson2d.c -o poisson2d_pref -lm\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", + "Job <24925> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "4.74user 0.00system 0:04.74elapsed 99%CPU (0avgtext+0avgdata 24256maxresident)k\n", + "0inputs+0outputs (0major+480minor)pagefaults 0swaps\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS perf stat -e cycles,r168a4 ./poisson2d\n", + "Job <24926> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "\n", + " Performance counter stats for './poisson2d':\n", + "\n", + " 16239159454 cycles:u \n", + " 631061431 r168a4:u \n", + "\n", + " 4.730144897 seconds time elapsed\n", + "\n" + ] + } + ], + "source": [ + "!make poisson2d_pref CC=gcc -B\n", + "!make run\n", + "!make l3missstats" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Do you notice the impact difference with optimization levels? At what optimization level does software prefetching help the most?" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "exercise": "solution" + }, + "source": [ + "Observing the results, we see that SW Prefetching seems to help at `-Ofast` but not at `-O3`. We can use the steps described in the the next section to verify that the compiler has not inserted any SW prefetch operations at`-O3` at all. That is because in the `-O3` binary the time is dominated by `__fmax` call which causes the compiler to come to the conclusion that whatever benefit we obtain by adding SW prefetch will be overshadowed by the penalty of `fmax()`\n", + "GCC may add further loop optimizations such as unrolling upon invocation of `\u2013fprefetch-loop-arrays`.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Part B: Analysis of Instructions\n", + "\n", + "Compilation of the `-Ofast` binary with the software prefetching flag causes the compiler to generate the `dcb*` instructions that prefetch memory values to L3." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**TASK**: \n", + "Run `$(SC19_SUBMIT_CMD) objdump -lSd` on each binary file (`-O3`, `-Ofast` with prefetch/no prefetch).\n", + "Look for instructions beginning with `dcb`\n", + "At what optimization levels does the compiler generate software prefetching instructions?" + ] + }, + { + "cell_type": "code", + "execution_count": 114, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc -std=c99 -DUSE_DOUBLE -Ofast -mcpu=power9 -mvsx -maltivec -fprefetch-loop-arrays poisson2d.c -o poisson2d_pref -lm\n" + ] + } + ], + "source": [ + "!make CC=gcc -B poisson2d_pref\n", + "!objdump -lSd ./poisson2d_pref > poisson2d.dis" + ] + }, + { + "cell_type": "code", + "execution_count": 116, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " 10000b28:\t2c d2 00 7c \tdcbt 0,r26\n", + " 10000b30:\t2c ba 00 7c \tdcbt 0,r23\n", + " 10000b38:\t2c b2 00 7c \tdcbt 0,r22\n", + " 10000b50:\t2c d2 00 7c \tdcbt 0,r26\n", + " 10000b58:\tec b9 00 7c \tdcbtst 0,r23\n", + " 10000b80:\t2c d2 00 7c \tdcbt 0,r26\n", + " 10000e64:\t2c 92 00 7c \tdcbt 0,r18\n", + " 10000e68:\t2c 9a 00 7c \tdcbt 0,r19\n", + " 10000e6c:\t2c a2 00 7c \tdcbt 0,r20\n", + " 10000e70:\t2c aa 00 7c \tdcbt 0,r21\n", + " 10000e7c:\t2c b2 00 7c \tdcbt 0,r22\n", + " 10000e80:\t2c d2 00 7c \tdcbt 0,r26\n", + " 10000e94:\tec b9 00 7c \tdcbtst 0,r23\n" + ] + } + ], + "source": [ + "!grep dcb poisson2d.dis" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Part C: Changing Values of DSCR via compiler flags\n", + "\n", + "This task requires using the IBM XL compiler. It should be already in your environment.\n", + "\n", + "\n", + "We saw the impact of software prefetching in the previous subsection. \n", + "In certain cases, tuning the hardware prefetcher through compiler options can also help improve performance. \n", + "In this exercise we shall see some compiler options that can be used to modify the DSCR value which controls aggressiveness of prefetching. It can be also used to turn off hardware prefetching. \n", + "\n", + "IBM XL compiler has an option `-qprefetch=dscr=<val>` that can be used for this purpose.\n", + "Compiling with `-qprefetch=dscr=1` turns off the prefetcher. One can give various values such as `-qprefetch=dscr=4`, `-qprefetch=dscr=7` etc. to control aggressiveness of prefetching.\n", + "\n", + "For this exercise we use `make CC=xlc_r` to illustrate the performance impact.\n", + " \n", + "\n", + "**Task** Generate a XL-compiled binary by compiling using the following cells. After you've generated a baseline, start editing the `Makefile`: Add `qprefetch=dscr=1` to the `CFLAGS` and rebuild the application and note the performance. Which one is faster? \n", + "\n", + "In general, applications benefit with the default settings of hardware DSCR register (`-qprefetch=dscr=0`). However, certain applications also benefit with prefetching turned off. \n", + "\n", + "It is to be noted that DSCR values are highly sensitive to the application. One value that works well for Application A may not help Application B. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Measure performance of the application compiled with XL at default DSCR value" + ] + }, + { + "cell_type": "code", + "execution_count": 117, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "xlc_r -std=c99 -DUSE_DOUBLE -Ofast -qarch=pwr9 -qtune=pwr9 -DINLINE_LIBS poisson2d.c -o poisson2d -lm\n", + " 1500-036: (I) The NOSTRICT option (default at OPT(3)) has the potential to alter the semantics of a program. Please refer to documentation on the STRICT/NOSTRICT option for more information.\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", + "Job <24927> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 50.149062\n", + " 200, 99.849327\n", + " 300, 149.352369\n", + " 400, 198.659746\n", + " 500, 247.773000\n", + " 600, 296.693652\n", + " 700, 345.423208\n", + " 800, 393.963155\n", + " 900, 442.314962\n", + "2.26user 0.00system 0:02.27elapsed 99%CPU (0avgtext+0avgdata 24256maxresident)k\n", + "256inputs+0outputs (0major+477minor)pagefaults 0swaps\n" + ] + } + ], + "source": [ + "!make CC=xlc_r -B poisson2d\n", + "!make run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Measure performance of the application compiled with XL with DSCR value turned off" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "xlc_r -std=c99 -DUSE_DOUBLE -Ofast -qarch=pwr9 -qtune=pwr9 -DINLINE_LIBS -qprefetch=dscr=1 poisson2d.c -o poisson2d_dscr -lm\n", + " 1500-036: (I) The NOSTRICT option (default at OPT(3)) has the potential to alter the semantics of a program. Please refer to documentation on the STRICT/NOSTRICT option for more information.\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", + "Job <24929> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "4.58user 0.00system 0:04.59elapsed 99%CPU (0avgtext+0avgdata 24192maxresident)k\n", + "0inputs+0outputs (0major+476minor)pagefaults 0swaps\n" + ] + } + ], + "source": [ + "!make poisson2d_dscr CC=xlc_r -B\n", + "!make run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Does Hardware prefetcher help this application? How much impact do you see when you turn off the hardware prefetcher? " + ] + }, + { + "cell_type": "markdown", + "metadata": { + "exercise": "solution" + }, + "source": [ + "The DSCR register controls the operation of the HW Prefetcher on POWER9. It can be modified in the command line by `ppc64_cpu --dscr=<value>`. However this needs admin privileges. IBM XL offers a compiler flag to set the value through the compiler. `-qprefetch=dscr=1` turns off the prefetcher. Observing the results we see that the performance without the HW prefetcher is twice as bad as that with default prefetching. So we can conclude that Prefetching helps the Jacobi application. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### References\n", + "\n", + "1. https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html\n", + "2. https://www.gnu.org/software/gcc/projects/prefetch.html\n", + "3. https://openpowerfoundation.org/?resource_lib=power-isa-version-3-0\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[Back to Top](#top)\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Task 3: OpenMP\n", + "<a name=\"task3\"></a>\n", + "\n", + "\n", + "### Overview\n", + "\n", + "We add OpenMP shared-memory parallelism to the application. Also, we study the effect of binding the multi-thread processes to certain cores on the resulting application performance. We do this study for both GCC and XL compilers inorder to learn about the appropriate options that need to be used.\n", + "First, we need to change directory to that of Task3. For Task 3 we modify poisson2d.c to invoke an exact copy of the main jacobi loop which is `poisson2d_reference`. We parallelize only the main loop but not `poisson2d_reference`. The speedup is the performance gain seen in the main loop as compared to the reference loop." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/autofs/nccsopen-svm1_home/aherten/SC19-Tutorial/3-Optimizing_POWER/Handson/Task3\n" + ] + } + ], + "source": [ + "%cd ../Task3" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Part A: Implement OpenMP Pragmas; Compilation\n", + "\n", + "**Task**: Please add the correct OpenMP directives to poisson2d.c and compilations flags in the Makefile to enable OpenMP with GCC and XL compilers.\n", + "\n", + "* **Directives**: Look at the TODOs in [`poisson2d.c`](poisson2d.c) to add OpenMP parallelism. The pragmas in question are `#pragma omp parallel for` (and once it's `#pragma omp parallel for reduction(max:error)` \u2013\u00a0can you guess where?)\n", + "* **Compilation**: Please add compilation flags enabling OpenMP in GCC and XL to the `Makefile`. For GCC, we need to add `-fopenmp` and the application needs to be linked with `-lgomp`. For XL, we need to add `-qsmp=omp` to the list of compilation flags. \n", + "\n", + "Afterwards, compile and run the application with the following commands." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gcc -c -std=c99 -DUSE_DOUBLE -O3 -mcpu=power9 -mvsx -maltivec -fopenmp -lgomp poisson2d_reference.c -o poisson2d_reference.o -lm\n", + "gcc -std=c99 -DUSE_DOUBLE -O3 -mcpu=power9 -mvsx -maltivec -fopenmp -lgomp poisson2d.c poisson2d_reference.o -o poisson2d -lm \n" + ] + } + ], + "source": [ + "!make poisson2d CC=gcc" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The command to submit a job to the batch system is prepared in an environment variable `$SC19_SUBMIT_CMD`; use it together with `eval`. In the following cell, it is shown how to invoke the application using the batch system. " + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Job <24951> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate reference solution and time with serial CPU execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 0.248997\n", + " 200, 0.248007\n", + " 300, 0.247025\n", + " 400, 0.246050\n", + " 500, 0.245084\n", + " 600, 0.244124\n", + " 700, 0.243173\n", + " 800, 0.242228\n", + " 900, 0.241291\n", + "1000x1000: Ref: 4.7430 s, This: 3.9363 s, speedup: 1.20\n" + ] + } + ], + "source": [ + "!eval $SC19_SUBMIT_CMD ./poisson2d 1000 1000 1000" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Inorder to run the parallel application, we need to set the number of threads using `OMP_NUM_THREADS`\n", + "What is the best performance you can reach by setting the number of threads via `OMP_NUM_THREADS=N` with `N` being the number of threads? Feel free to play around with the command in the following cell, using 1 thread as an example. \n", + "We added `--bind none` to prevent `jsrun`, the scheduler of Ascent, from overlaying binding options. Also, we use `-c ALL_CPUS` to make all CPUs on the compute nodes available to you." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "1000x1000: Ref: 4.7288 s, This: 4.9791 s, speedup: 0.95\n" + ] + } + ], + "source": [ + "!OMP_NUM_THREADS=1 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep speedup " + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "1000x1000: Ref: 4.7125 s, This: 2.4914 s, speedup: 1.89\n" + ] + } + ], + "source": [ + "!OMP_NUM_THREADS=2 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep speedup " + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "1000x1000: Ref: 2.1065 s, This: 1.3836 s, speedup: 1.52\n" + ] + } + ], + "source": [ + "!OMP_NUM_THREADS=4 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep speedup " + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "1000x1000: Ref: 2.3868 s, This: 0.5272 s, speedup: 4.53\n" + ] + } + ], + "source": [ + "!OMP_NUM_THREADS=8 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep speedup " + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "1000x1000: Ref: 2.3912 s, This: 0.4612 s, speedup: 5.18\n" + ] + } + ], + "source": [ + "!OMP_NUM_THREADS=10 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep speedup " + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "1000x1000: Ref: 2.3864 s, This: 0.4037 s, speedup: 5.91\n" + ] + } + ], + "source": [ + "!OMP_NUM_THREADS=20 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep speedup " + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "1000x1000: Ref: 2.3773 s, This: 0.3045 s, speedup: 7.81\n" + ] + } + ], + "source": [ + "!OMP_NUM_THREADS=40 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep speedup " + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "1000x1000: Ref: 2.3819 s, This: 0.3081 s, speedup: 7.73\n" + ] + } + ], + "source": [ + "!OMP_NUM_THREADS=80 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep speedup " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Part B: Bindings\n", + "\n", + "Different CPU architectures and models come with different configuration of cores. The configuration plays an important role in the run time of the application. We need to optimize for it!\n", + "\n", + "There are applications which can be used to determine the configuration of the processor. Among those are:\n", + "\n", + "* `lscpu`: Can be used to determine the number of sockets, number of cores, and numb of threads. It gives a very good overview and is available on most Linux systems.\n", + "* `ppc64_cpu --smt`: Specifically for POWER, this tool can give information about the number of simulations threads running per core (*SMT*, Simulataion Multi-Threading).\n", + "\n", + "Run `ppc64_cpu --smt` to find out about the threading configuration of Ascent!" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Job <24465> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "SMT=4\n" + ] + } + ], + "source": [ + "!eval $SC19_SUBMIT_CMD ppc64_cpu --smt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are more sources information available\n", + "\n", + "* `/proc/cpuinfo`: Holds information about virtual cores, including model and clock speed. Available on most Linux system. Usually used together with `cat`\n", + "* `/sys/devices/system/cpu/cpu0/topology/thread_siblings_list`: Holds information about thread siblings for given CPU core (`cpu0` in this case). Use it to find out which thread is mapped to which core." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Job <24949> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "0-3\n", + "Job <24950> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "4-7\n" + ] + } + ], + "source": [ + "!$$SC19_SUBMIT_CMD cat /sys/devices/system/cpu/cpu0/topology/thread_siblings_list\n", + "!$$SC19_SUBMIT_CMD cat /sys/devices/system/cpu/cpu5/topology/thread_siblings_list" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are various environment variables available within OpenMP (some specific to GCC) that hold across compilers to specify binding of threads to cores. See, for instance, the [OMP_PLACES environment Variable](https://www.openmp.org/spec-html/5.0/openmpse53.html). We also have a GNU specific variable which can also be used to control affinity - `GOMP_CPU_AFFINITY`. Setting `GOMP_CPU_AFFINITY` is specific to GCC binaries but it internally serves the same function as setting `OMP_PLACES`. \n", + "\n", + "**Task**: Run the application enabled with OpenMP from Part A with different binding configurations. Make sure to at least run a) binding all threads to a single core and b) binding threads to different cores.\n", + "\n", + "Adapt the following command with your configuration \u2013 or follow along accordingly in the non-interactive version of the Notebook.\n", + "\n", + "What's your maximum speedup?" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "exercise": "solution" + }, + "source": [ + "Running with two different configurations 1) Binding all threads to the same core 2) Binding all threads to different cores, we see a higher speedup in case of binding all threads to different cores." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "exercise": "solution" + }, + "source": [ + "Using `OMP_PLACES` for binding, and using some magical Python-Bash interplay:" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Affinity: {0},{1},{2},{3}\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + " OMP_PLACES = '{0},{1},{2},{3}'\n", + "1000x1000: Ref: 4.7315 s, This: 3.9090 s, speedup: 1.21\n", + "Affinity: {0},{5},{9},{13}\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + " OMP_PLACES = '{0},{5},{9},{13}'\n", + "1000x1000: Ref: 4.6485 s, This: 1.2829 s, speedup: 3.62\n" + ] + } + ], + "source": [ + "for affinity in [\"{0},{1},{2},{3}\", \"{0},{5},{9},{13}\"]:\n", + " print(\"Affinity: {}\".format(affinity))\n", + " !eval OMP_DISPLAY_ENV=true OMP_PLACES=$affinity OMP_NUM_THREADS=4 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep \"OMP_PLACES\\|speedup\"" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "exercise": "solution" + }, + "source": [ + "In this case, we carry out the same experiment using `GOMP_CPU_AFFINITY` which essentially sets the same environment variable `OMP_PLACES`. Running with two different configurations 1) Binding all threads to the same core 2) Binding all threads to different cores, we see a higher speedup in case of binding all threads to different cores." + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Affinity: 0,1,2,3\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + " OMP_PLACES = '{0},{1},{2},{3}'\n", + "1000x1000: Ref: 2.3964 s, This: 2.1361 s, speedup: 1.12\n", + "Affinity: 0,5,9,13\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + " OMP_PLACES = '{0},{5},{9},{13}'\n", + "1000x1000: Ref: 2.3925 s, This: 0.7030 s, speedup: 3.40\n" + ] + } + ], + "source": [ + "for affinity in [\"0,1,2,3\", \"0,5,9,13\"]:\n", + " print(\"Affinity: {}\".format(affinity))\n", + " !eval OMP_DISPLAY_ENV=true GOMP_CPU_AFFINITY=$affinity OMP_NUM_THREADS=4 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep \"OMP_PLACES\\|speedup\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Great!\n", + "\n", + "If you still have time: The same experiments can be repeated with the IBM XL compiler. \n", + "The corresponding compiler flag to enable OpenMP parallelism that needs to be used for XL is `-qsmp=omp`\n", + "\n", + "**Task**: In the Makefile add the OpenMP flag and generate XL binaries with OpenMP and run the application with various number of threads and note the performance speedup." + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "xlc_r -c -std=c99 -DUSE_DOUBLE -O3 -qhot -qtune=pwr9 -DINLINE_LIBS -qsmp=omp poisson2d_reference.c -o poisson2d_reference.o -lm \n", + " 1500-036: (I) The NOSTRICT option (default at OPT(3)) has the potential to alter the semantics of a program. Please refer to documentation on the STRICT/NOSTRICT option for more information.\n", + "xlc_r -std=c99 -DUSE_DOUBLE -O3 -qhot -qtune=pwr9 -DINLINE_LIBS -qsmp=omp poisson2d.c poisson2d_reference.o -o poisson2d -lm\n", + " 1500-036: (I) The NOSTRICT option (default at OPT(3)) has the potential to alter the semantics of a program. Please refer to documentation on the STRICT/NOSTRICT option for more information.\n", + "bsub -W 60 -nnodes 1 -Is -P TRN003 jsrun -n 1 -c 1 -g ALL_GPUS time ./poisson2d\n", + "Job <24956> is submitted to default queue <batch>.\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "Jacobi relaxation calculation: max 1000 iterations on 1000 x 1000 mesh\n", + "Calculate reference solution and time with serial CPU execution.\n", + " 0, 0.249995\n", + " 100, 50.149062\n", + " 200, 99.849327\n", + " 300, 149.352369\n", + " 400, 198.659746\n", + " 500, 247.773000\n", + " 600, 296.693652\n", + " 700, 345.423208\n", + " 800, 393.963155\n", + " 900, 442.314962\n", + "Calculate current execution.\n", + " 0, 0.249995\n", + " 100, 50.149062\n", + " 200, 99.849327\n", + " 300, 149.352369\n", + " 400, 198.659746\n", + " 500, 247.773000\n", + " 600, 296.693652\n", + " 700, 345.423208\n", + " 800, 393.963155\n", + " 900, 442.314962\n", + "1000x1000: Ref: 5.6783 s, This: 2.6528 s, speedup: 2.14\n", + "21.56user 6.18system 0:08.37elapsed 331%CPU (0avgtext+0avgdata 23040maxresident)k\n", + "3200inputs+0outputs (2major+1098minor)pagefaults 0swaps\n" + ] + } + ], + "source": [ + "!make CC=xlc_r -B run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Run the parallel application with varying numbre of threads (`OMP_NUM_THREADS`) and note the performance improvement. " + ] + }, + { + "cell_type": "markdown", + "metadata": { + "exercise": "solution" + }, + "source": [ + "Just as in the GCC binary we see a similar speedup with higher number of threads until a certain point beyond which the benefit tapers off. " + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "1000x1000: Ref: 2.2561 s, This: 2.6432 s, speedup: 0.85\n" + ] + } + ], + "source": [ + "!OMP_NUM_THREADS=1 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep speedup " + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "1000x1000: Ref: 2.3071 s, This: 1.5343 s, speedup: 1.50\n" + ] + } + ], + "source": [ + "!OMP_NUM_THREADS=2 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep speedup " + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "1000x1000: Ref: 2.2617 s, This: 0.6936 s, speedup: 3.26\n" + ] + } + ], + "source": [ + "!OMP_NUM_THREADS=4 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep speedup " + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "1000x1000: Ref: 2.2728 s, This: 0.3402 s, speedup: 6.68\n" + ] + } + ], + "source": [ + "!OMP_NUM_THREADS=8 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep speedup " + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "1000x1000: Ref: 2.1678 s, This: 0.2869 s, speedup: 7.56\n" + ] + } + ], + "source": [ + "!OMP_NUM_THREADS=10 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep speedup " + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "1000x1000: Ref: 2.2813 s, This: 0.1452 s, speedup: 15.71\n" + ] + } + ], + "source": [ + "!OMP_NUM_THREADS=20 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep speedup " + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "1000x1000: Ref: 2.3284 s, This: 0.0981 s, speedup: 23.75\n" + ] + } + ], + "source": [ + "!OMP_NUM_THREADS=40 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep speedup " + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + "1000x1000: Ref: 2.2918 s, This: 0.1439 s, speedup: 15.92\n" + ] + } + ], + "source": [ + "!OMP_NUM_THREADS=80 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep speedup " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we repeat the exercise of using the right binding of threads for the XL binary. `OMP_PLACES` pertains to the XL binary as well as it is an OpenMP variable. `GOMP_CPU_AFFINITY` is specific to GCC binary so that cannot be used to set the binding.\n", + "\n", + "**Task**: Run the application enabled with OpenMP from Part A with different binding configurations. Make sure to at least run a) binding all threads to a single core and b) binding threads to different cores.\n", + "\n", + "Adapt the following command with your configuration \u2013 or follow along accordingly in the non-interactive version of the Notebook.\n", + "\n", + "We are mixing Python with Bash (`!`) here, so don't get confused (because of this, if we want to use Bash environment variables, we need to use two `$$`)\n", + "\n", + "What's your maximum speedup?" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "exercise": "solution" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Affinity: {0},{1},{2},{3}\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + " OMP_PLACES='{0},{1},{2},{3}' custom\n", + "1000x1000: Ref: 5.9792 s, This: 2.4122 s, speedup: 2.48\n", + "Affinity: {0},{5},{9},{13}\n", + "<<Waiting for dispatch ...>>\n", + "<<Starting on login1>>\n", + " OMP_PLACES='{0},{5},{9},{13}' custom\n", + "1000x1000: Ref: 2.3101 s, This: 0.6884 s, speedup: 3.36\n" + ] + } + ], + "source": [ + "for affinity in [\"{0},{1},{2},{3}\", \"{0},{5},{9},{13}\"]:\n", + " print(\"Affinity: {}\".format(affinity))\n", + " !eval OMP_DISPLAY_ENV=true OMP_PLACES=$affinity OMP_NUM_THREADS=4 $$SC19_SUBMIT_CMD -c ALL_CPUS --bind none ./poisson2d 1000 1000 1000 | grep \"OMP_PLACES\\|speedup\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Likewise we see a higher speedup when we bind the threads to different cores rather than to a single core. This handson illustrates that apart from compiler level tuning, system level tuning is also equally important to obtain performance improvements \n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### References\n", + "1. https://gcc.gnu.org/onlinedocs/libgomp/GOMP_005fCPU_005fAFFINITY.html\n", + "2. https://www.openmp.org/spec-html/5.0/openmpse53.html" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[Back to Top](#top)\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Survey<a name=\"survey\"></a>\n", + "\n", + "Please rememeber to take some time and fill out the [survey](http://bit.ly/sc19-eval)." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "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.7.0" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/3-Optimizing_POWER/Handson/Solution-Notebook/HandsOnPerformanceOptimization.pdf b/3-Optimizing_POWER/Handson/Solution-Notebook/HandsOnPerformanceOptimization.pdf index 4357a6fc0c527eb59248f5962db5e7b0e70630f3..283819d6780fad07fd929bfced380e2ad08bb0c1 100644 GIT binary patch literal 129532 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�budb@B6zTTt^ir+2 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 Ks=E5SaRC5PVtTs( literal 74614 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 -- GitLab