{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "Paralelní výpočty jsou v dnešním mnohajádrovém světě, kte cluster, cloud, GPU, multi-threading apod. jsou všude, téměř nevyhnutelné. Python nám nabízí nemálo možností pro jejich využítí. Existují ovšem i některá zásadní omezení.\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Kdy (a jak) paralelizovat\n", "Ještě více než pro optimalizaci platí pro paralelizaci, že vždy *paralelizujeme jen po předchozí analýze funkčního (sekvenčního) programu a jen nutné části programu*. Navíc se vždy snažíme využít (trapně) jednoduchých paralelismů (embarrassingly parallel) v našem programu." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Vlákna v (C)Pythonu\n", "\n", "CPython má pro vícevláknové (*multi threading*) aplikace jedno velice zásadní omezení ve formě [*GIL - Global Interpreter Lock*](http://docs.python.org/2/c-api/init.html#threads). Ten existuje proto, že CPython interpret jako takový není vláknově bezpečný. Neznamená to ovšem, že vlákna jsou v (C)Pythonu absolutní tabu. Zmiňme předně, že např. [Jython](http://www.jython.org/jythonbook/en/1.0/Concurrency.html) nebo stále oblíbenější [PyPy](http://pypy.org/) jsou vláknově bezpečné(ější). I CPython může vlákna používat, existují tam nakonec moduly `threading` a `thread`. Přímočaře můžeme vlákna použít, pokud náš program obsahuje nějaké blokující input/output (I/O) operace. Takovou operaci lze nechat běžet ve vlákně a spustit při tom jiné vlákno. Navíc, pokud programujeme Python modul v C, lze GIL uvolnit manuálně a více využít vláken." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Vlákna v NumPy a SciPy\n", "\n", "NumPy a SciPy sice běží pouze v CPythonu (existuje sice [NumPyPy](http://buildbot.pypy.org/numpy-status/latest.html), v současnosti je ale stále nekompletní), dokáží ale právě GIL ve správnou chvíli uvolnit a některé výpočty provádět ve více vláknech současně, viz http://wiki.scipy.org/ParallelProgramming. Např. maticový součin (`dot`) toho využívá. Navíc často volají numerické knihovny (BLAS, LAPACK, případně jejich optimalizované verze ATLAS nebo Intel MKL)." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "%load_ext Cython\n", "\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "from IPython.display import Image" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Cython.parallel\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[`cython.parallel`](http://docs.cython.org/src/userguide/parallelism.html) je poněkud nenápadný modul, který ale ovšem využít vlákna (pomocí OpenMP) poměrně jednoduše. Musíme ovšem pracovat pouze s daty (tj. s proměnnými) a funkcemi mimo CPython interpret. Pak můžeme uvolnit gil, např. pomocí `with nogil` bloku a v něm použít `prange`, tak jako to děláme v následujícím příkladě.\n", "\n", "Budeme jednoduše sčítat matice. Výpočet jednoduše paralelizujeme tak, že řádky se mohou sčítat nezávisle. To nám zařídí `prange`. Také si všimněte, že výsledné pole alokujeme předem, což může pro velká pole přinést další zrychlení.\n", "Pozor, při kompilaci musíme použít `-fopenmp`, jinak se vlákna budou ignorovat." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "In file included from /Users/kuba/.cache/ipython/cython/_cython_magic_b6b9b7dc54b67138b4ffb3bef67342de.c:775:\n", "In file included from /Users/kuba/mambaforge/envs/python-fjfi/lib/python3.9/site-packages/numpy/core/include/numpy/arrayobject.h:5:\n", "In file included from /Users/kuba/mambaforge/envs/python-fjfi/lib/python3.9/site-packages/numpy/core/include/numpy/ndarrayobject.h:12:\n", "In file included from /Users/kuba/mambaforge/envs/python-fjfi/lib/python3.9/site-packages/numpy/core/include/numpy/ndarraytypes.h:1940:\n", "/Users/kuba/mambaforge/envs/python-fjfi/lib/python3.9/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-W#warnings]\n", "#warning \"Using deprecated NumPy API, disable it with \" \\\n", " ^\n", "/Users/kuba/.cache/ipython/cython/_cython_magic_b6b9b7dc54b67138b4ffb3bef67342de.c:2882:23: warning: code will never be executed [-Wunreachable-code]\n", " if ((1 == 0)) abort();\n", " ^~~~~\n", "/Users/kuba/.cache/ipython/cython/_cython_magic_b6b9b7dc54b67138b4ffb3bef67342de.c:2882:14: note: silence by adding parentheses to mark code as explicitly dead\n", " if ((1 == 0)) abort();\n", " ^\n", " /* DISABLES CODE */ ( )\n", "/Users/kuba/.cache/ipython/cython/_cython_magic_b6b9b7dc54b67138b4ffb3bef67342de.c:20619:21: warning: fallthrough annotation in unreachable code [-Wunreachable-code-fallthrough]\n", " CYTHON_FALLTHROUGH;\n", " ^\n", "/Users/kuba/.cache/ipython/cython/_cython_magic_b6b9b7dc54b67138b4ffb3bef67342de.c:370:34: note: expanded from macro 'CYTHON_FALLTHROUGH'\n", " #define CYTHON_FALLTHROUGH __attribute__((fallthrough))\n", " ^\n", "/Users/kuba/.cache/ipython/cython/_cython_magic_b6b9b7dc54b67138b4ffb3bef67342de.c:20630:21: warning: fallthrough annotation in unreachable code [-Wunreachable-code-fallthrough]\n", " CYTHON_FALLTHROUGH;\n", " ^\n", "/Users/kuba/.cache/ipython/cython/_cython_magic_b6b9b7dc54b67138b4ffb3bef67342de.c:370:34: note: expanded from macro 'CYTHON_FALLTHROUGH'\n", " #define CYTHON_FALLTHROUGH __attribute__((fallthrough))\n", " ^\n", "4 warnings generated.\n", "ld: warning: dylib (/opt/homebrew/Cellar/llvm/16.0.1/lib/libunwind.dylib) was built for newer macOS version (13.0) than being linked (11.0)\n", "ld: warning: dylib (/opt/homebrew/Cellar/llvm/16.0.1/lib/libunwind.dylib) was built for newer macOS version (13.0) than being linked (11.0)\n" ] } ], "source": [ "%%cython -f --compile-args=-fopenmp --link-args=-fopenmp\n", "cimport cython\n", "from cython.parallel import parallel, prange, threadid\n", "# import cython.parallel as parallel\n", "import numpy as np\n", "cimport numpy as np\n", "\n", "# bez kontroly velikosti polí\n", "@cython.boundscheck(False)\n", "# bez relativního indexování pomocí záporných indexů\n", "@cython.wraparound(False)\n", "cpdef double[:,:] add_arrays_2d_views(double[:,:] buf1, double[:,:] buf2, double[:,:] output=None, int nthreads=2):\n", " cdef int x, y, inner, outer\n", " # tuto proměnnou budeme potřebovat v nogil bloku\n", " cdef int _nthreads = nthreads\n", " # zkontrolujeme rozměry, jinak hrozí segmentation fault\n", " assert buf1.shape[0] == buf2.shape[0]\n", " assert buf1.shape[1] == buf2.shape[1]\n", " outer = buf1.shape[0]\n", " inner = buf1.shape[1]\n", " if output is None:\n", " output = np.zeros_like(buf1)\n", " else:\n", " assert output.shape[0] == buf1.shape[0]\n", " assert output.shape[1] == buf1.shape[1]\n", " with nogil, cython.boundscheck(False), cython.wraparound(False):\n", " for x in prange(outer, schedule='static', num_threads=_nthreads):\n", " for y in xrange(inner):\n", " output[x, y] = (buf1[x, y] + buf2[x, y])\n", " return output" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Funkci se nám (doufejme) podařilo implementovat vícevláknově. Bylo to ale za cenu přece jenom nemalé práce navíc, přestože Cython nám práci výrazně zjednodušuje. Je třeba poznamenat, že tato funkce by byla v čistém C obdobně složitá.\n", "\n", "Otestujeme vytvořenou funkci pro středně velká pole." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "n = 2**13\n", "X = np.random.rand(n, n)\n", "Y = np.random.rand(n, n)\n", "Z = np.zeros_like(X)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pro jistotu i kontrola správnosti výpočtu." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.allclose(X+Y, add_arrays_2d_views(X, Y))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Změříme čas pro 1, 2 a 4 vlákna. Měli bychom vidět zrychlení, záleží na procesoru, arhitektuře, kompilátoru apod." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "27 ms ± 209 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" ] } ], "source": [ "%timeit add_arrays_2d_views(X, Y, Z, nthreads=1)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "13.9 ms ± 302 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" ] } ], "source": [ "%timeit add_arrays_2d_views(X, Y, Z, nthreads=2)" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "12.3 ms ± 81.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" ] } ], "source": [ "%timeit add_arrays_2d_views(X, Y, Z, nthreads=4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Multiprocessing\n", "\n", "Python obsahuje vestavěnou knihovnu [`multiprocessing`](http://docs.python.org/2/library/multiprocessing.html), která poskytuje podobné služby jako vlákna, místo vláken ovšem používá procesy. To má své výhody i nevýhody. Výhodou je jednoduchost a bezpečnost -- pokud spadne jeden proces, ostatní mohou běžet dál. Hlavní nevýhodou (i když z hlediska bezpečnost je to výhoda) jsou oddělené paměťové prostory. (Pokud samozřejmě nepoužijeme sdílenou paměť na úrovni operačního systému.) To znamená, že je třeba co nejméně mezi procesy komunikovat. Ideálně se tato architektura hodí na map-reduce algoritmy, které jsme už zmínili dříve ve funkcionálním programování.\n", "\n", "Ukážeme si multiprocessing na velice jednoduchém příkladu, kde procesy se budou pouze identifikovat." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "import multiprocessing\n", "import os" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Overwriting my_tasks.py\n" ] } ], "source": [ "%%file my_tasks.py\n", "\n", "import os\n", "\n", "def task(args):\n", " return os.getpid(), args" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(74184, 'test')" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import my_tasks\n", "\n", "my_tasks.task(\"test\")" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "pool = multiprocessing.Pool(processes=4)" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[(74290, 1),\n", " (74291, 2),\n", " (74292, 3),\n", " (74293, 4),\n", " (74290, 5),\n", " (74292, 6),\n", " (74291, 7),\n", " (74290, 8)]" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "result = pool.map(my_tasks.task, range(1, 9))\n", "result" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ipyparallel (dříve IPython parallel)\n", "\n", "Ipython nám přináší (jak je jeho dobrým zvykem) velice užitečný a všestranný [nástroj na paralelní výpočty](https://ipyparallel.readthedocs.io/en/latest/), který se navíc snadno používá. S použitím message passing a technologie, která byla vyvinuta pro Qt konzoli a následně notebook, přidává `ipyparallel` ještě scheduler a hub, které ovládají a komunikují s klienty a jednotlivými Python procesy (engine).\n", "\n", "\"ipyparallel\n", "\n", "### Startujeme cluster\n", "Abychom mohli začít tento nástroj používat, musíme nastartovat procesy, ve kterých se budou provádět výpočty. Nejjednodušší je to pomocí nástroje `ipcluster` (ten se spouští v konzoli, nikoli v Pythonu)\n", "\n", " $ ipcluster start -n 4\n", "\n", "To nám spustí 4 procesy na jednom počítači. Případně můžeme použít IPython Dashboard -- úvodní stránky Jupyter Notebook.\n", "\n", "IPython cluster můžeme spustit i na více počítačích, a to pomocí ssh, mpi, různých batch systémů (PBS, Torque apod.). Můžeme použít i Amazon EC2.\n", "\n", "### Používáme cluster\n", "Jakmile jsme nastartovali cluster, můžeme ho začít používat z našeho Python programu. K tomu slouží třída `IPython.parallel.Client`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from ipyparallel import Client" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "cli = Client()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pomocí atributu `ids` se můžeme dozvědět, jaké prostředky máme k dispozici." ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[0, 1, 2, 3]" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cli.ids" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Každý z těchto procesů můžeme přímo použít pro spuštění nějaké úlohy. Zkusme nejdřív zjistit id procesu z pohledu operačního systému." ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": [ "def getpid():\n", " \"\"\" return the unique ID of the current process \"\"\"\n", " import os\n", " return os.getpid()" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "2493" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "getpid()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Nyní můžeme stejný příkaz spustit na jednom vzdáleném procesu pomocí metody `apply_sync`." ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "2580" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cli[0].apply_sync(getpid)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Velice jednoduše můžeme to samé udělat pro celý cluster." ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[2580, 2581, 2582, 2583]" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cli[:].apply_sync(getpid)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`cli[:]` nám vlastně vytvořilo objekt `DirectView`, jakýsi (přímý) pohled na cluster. Pro paralelní spuštění můžeme např. použít metodu `map_sync`, což je obdoba `multiprocessing.Pool.map`." ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[0,\n", " 1,\n", " 1024,\n", " 59049,\n", " 1048576,\n", " 9765625,\n", " 60466176,\n", " 282475249,\n", " 1073741824,\n", " 3486784401,\n", " 10000000000,\n", " 25937424601,\n", " 61917364224,\n", " 137858491849,\n", " 289254654976,\n", " 576650390625]" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dview = cli[:]\n", "dview.map_sync(lambda x: x**10, range(16))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Můžeme použít i dekorátory `parallel` a `remote`. Definujeme teď funkci, která čeká danou dobu (to nám nahradí nějaký delší výpočet)." ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [], "source": [ "@dview.parallel(block=True)\n", "def dummy_task(delay):\n", " \"\"\" a dummy task that takes 'delay' seconds to finish \"\"\"\n", " import os, time\n", "\n", " t0 = time.time()\n", " pid = os.getpid()\n", " time.sleep(delay)\n", " t1 = time.time()\n", " \n", " return [pid, t0, t1]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`dview.parallel` dekorátor způsobil, že dummy_task má metodu `map`, která spustí funkci paralelně na množinu vstupů. Použití je následující." ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[[2580, 1487081563.2175128, 1487081563.6187928],\n", " [2581, 1487081563.2196243, 1487081564.1984928],\n", " [2582, 1487081563.2139854, 1487081563.4248705],\n", " [2583, 1487081563.2320647, 1487081563.75292]]" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Vygenerujeme několik náhodných časů\n", "delay_times = numpy.random.rand(4)\n", "# a můžeme použít map \n", "dummy_task.map(delay_times)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Vidíme, že jsme dostali množinu výsledků, které byly spočítány v různých procesech.\n", "\n", "Pojďme zkusit vizualizovat zatížení jednotlivých procesů. Použijeme k tomu více vstupů a jednoduchou funkci, která vytvoří jednoduchý graf." ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [], "source": [ "def visualize_tasks(results):\n", " res = numpy.array(results)\n", " fig, ax = subplots(figsize=(10, res.shape[1]))\n", " \n", " yticks = []\n", " yticklabels = []\n", " tmin = min(res[:,1])\n", " for n, pid in enumerate(numpy.unique(res[:,0])):\n", " yticks.append(n)\n", " yticklabels.append(\"%d\" % pid)\n", " for m in numpy.where(res[:,0] == pid)[0]:\n", " ax.add_patch(Rectangle((res[m,1] - tmin, n-0.25),\n", " res[m,2] - res[m,1], 0.5, color=\"green\", alpha=0.5))\n", " \n", " ax.set_ylim(-.5, n+.5)\n", " ax.set_xlim(0, max(res[:,2]) - tmin + 0.)\n", " ax.set_yticks(yticks)\n", " ax.set_yticklabels(yticklabels)\n", " ax.set_ylabel(\"PID\")\n", " ax.set_xlabel(\"seconds\")" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "delay_times = numpy.random.rand(64)\n", "results = dummy_task.map(delay_times)\n", "visualize_tasks(results)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Tady vidíme, že jsme poměrně dobře vytížili všechny čtyři procesy pomocí poměrně jednoduché paralelizace. Ještě lepšího výsledku můžeme dosáhnout pomocí `LoadBalancedView`. Tato třída, na rozdíl od `DirectView`, rozděluje úlohy dynamicky podle zatížení jednotlivých procesů.\n", "\n", "`LoadBalancedView` dostaneme jednoduše pomocí `load_balanced_view`:" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [], "source": [ "lbview = cli.load_balanced_view()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Nyní definujeme stejnou funkci jako dummy_task, jen pomocí LoadBalancedView dekorátoru." ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [], "source": [ "@lbview.parallel(block=True)\n", "def dummy_task_load_balanced(delay):\n", " \"\"\" a dummy task that takes 'delay' seconds to finish \"\"\"\n", " import os, time\n", "\n", " t0 = time.time()\n", " pid = os.getpid()\n", " time.sleep(delay)\n", " t1 = time.time()\n", " \n", " return [pid, t0, t1]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A zkusíme opět nakreslit graf využití procesorů." ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "result = dummy_task_load_balanced.map(delay_times)\n", "visualize_tasks(result)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Vidíme, že jsme rovnoměrněji zatížili náš cluster a dasáhli o něco kratšího celkového času pro výpočet." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Více najdete v dokumentaci\n", "ipyparallel nabízí ještě mnohem více, např.\n", "\n", "* paralelní triky (`%px` apod.)\n", "* asynchronní (neblokující) spouštění\n", "* použití MPI\n", "* ..., viz https://ipyparallel.readthedocs.io/" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## MPI\n", "\n", "Pro intenzivní komunikaci mezi procesy je třeba použít efektivnějšího řešení, než je komunikace přes IPython controller, kterou jsme doposud používali (aniž bychom o tom nějak více přemýšleli). K tomu účelu obvykle slouží MPI -- Message Passign Interface. MPI je nezávislé na programovacím jazyce, neboť se jedná především o síťový protokol, který umožňuje synchronně nebo asynchronně posílat několik primitivních tipů, a to buď mezi jednotlivými procesy (point to point) nebo hromadně (broadcasting). Implementací je více, mezi běžné patři např. MPICH nebo OpenMPI. Pro Python existuje [mpi4py](http://mpi4py.scipy.org/).\n", "\n", "### Jednoduchý příklad\n", "\n", "MPI programy je třeba spouštět pomocí `mpirun`. Následující příklady se tedy pro názornost nespouští přímo v notebooku, i když IPython přímé použití MPI také podporuje." ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Writing mpitest.py\n" ] } ], "source": [ "%%file mpitest.py\n", "\n", "from mpi4py import MPI\n", "from numpy import arange\n", "\n", "comm = MPI.COMM_WORLD\n", "rank = comm.Get_rank()\n", "\n", "if rank == 0:\n", " # rank 0 je ridici proces\n", " data = [\"a\", 2.0, 3.0, {\"numpy\": arange(3)}]\n", " print(\"sending data: {}\".format(data))\n", " comm.send(data, dest=1, tag=11)\n", "elif rank == 1:\n", " # tady bychom provadeli jednotlive vypocty\n", " data = comm.recv(source=0, tag=11)\n", " print(\"received data: {}\".format(data))\n", " \n", "print(\"rank {} finished\".format(rank))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Spustíme pomocí mpirun se dvěma procesy." ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "sending data: ['a', 2.0, 3.0, {'numpy': array([0, 1, 2])}]\r\n", "rank 0 finished\r\n", "received data: ['a', 2.0, 3.0, {'numpy': array([0, 1, 2])}]\r\n", "rank 1 finished\r\n" ] } ], "source": [ "!mpirun -n 2 python mpitest.py" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Jak jsme viděli, pomocí `send` můžeme posílat celkem libovolné Python obejkty. Výstup posledního řádku může být přeházený, neboť procesy běží paralelně. Komunikace pomocí `send` je blokující, pro neblokující bychom použili `isend`." ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Writing mpitest_async.py\n" ] } ], "source": [ "%%file mpitest_async.py\n", "\n", "from mpi4py import MPI\n", "from numpy import arange\n", "\n", "comm = MPI.COMM_WORLD\n", "rank = comm.Get_rank()\n", "\n", "print(\"rank {} started\".format(rank))\n", "\n", "if rank == 0:\n", " # rank 0 je ridici proces\n", " data = [\"a\", 2.0, 3.0, {\"numpy\": arange(3)}]\n", " print(\"sending data: {}\".format(data))\n", " req = comm.isend(data, dest=1, tag=11)\n", " req.Wait()\n", "elif rank == 1:\n", " # tady bychom provadeli jednotlive vypocty\n", " data = comm.recv(source=0, tag=11)\n", " print(\"received data: {}\".format(data))\n", " \n", "print(\"rank {} finished\".format(rank))" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "rank 0 started\r\n", "sending data: ['a', 2.0, 3.0, {'numpy': array([0, 1, 2])}]\r\n", "rank 0 finished\r\n", "rank 1 started\r\n", "received data: ['a', 2.0, 3.0, {'numpy': array([0, 1, 2])}]\r\n", "rank 1 finished\r\n" ] } ], "source": [ "!mpirun -n 2 python mpitest_async.py" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Posílání polí apod.\n", "`send` dokáže poslat téměř cokoli, může to ale být neefektivní. Proto existují funkce `Send` and `Recv` (ano, rozdíl je pouze ve velikosti prvních písmen), které dokáží posílat bloky primitivních datových typů. To se velice hodí pro numpy, kde jsou pole uložené právě v takových blocích. Pozor, tady sestupujema na nižší úroveň abstrakce (podobně jako u Cythonu) a musíme daleko více explicitně řešit typy, rozměry, alokace apod.\n", "\n", "Pojďme se podívat, jak lze pomocí `Send` a `Recv` poslat numpy pole. Pro numpy pole základních typů můžeme použít automatické rozeznání tohoto typu." ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Writing numpy_mpi.py\n" ] } ], "source": [ "%%file numpy_mpi.py\n", "# from http://mpi4py.scipy.org/docs/usrman/tutorial.html#point-to-point-communication\n", "from mpi4py import MPI\n", "import numpy\n", "\n", "comm = MPI.COMM_WORLD\n", "rank = comm.Get_rank()\n", "\n", "print(\"rank {} started\".format(rank))\n", "\n", "# pass explicit MPI datatypes\n", "if rank == 0:\n", " data = numpy.arange(1000, dtype='i')\n", " comm.Send([data, MPI.INT], dest=1, tag=77)\n", "elif rank == 1:\n", " data = numpy.empty(1000, dtype='i')\n", " comm.Recv([data, MPI.INT], source=0, tag=77)\n", "\n", "# automatic MPI datatype discovery\n", "if rank == 0:\n", " data = numpy.arange(100, dtype=numpy.float64)\n", " print(\"rank {}, sum={}\".format(rank, data.sum()))\n", " comm.Send(data, dest=1, tag=13)\n", "elif rank == 1:\n", " data = numpy.empty(100, dtype=numpy.float64)\n", " comm.Recv(data, source=0, tag=13)\n", " print(\"rank {}, sum={}\".format(rank, data.sum()))\n", "print(\"rank {} finished\".format(rank))" ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "rank 4 started\n", "rank 4 finished\n", "rank 0 started\n", "rank 0, sum=4950.0\n", "rank 0 finished\n", "rank 1 started\n", "rank 1, sum=4950.0\n", "rank 1 finished\n", "rank 5 started\n", "rank 5 finished\n", "rank 3 started\n", "rank 3 finished\n", "rank 2 started\n", "rank 2 finished\n" ] } ], "source": [ "!mpirun -n 6 python numpy_mpi.py" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Další možnosti\n", "\n", "### Blaze -- blíží se nové NumPy?\n", "\n", "Blaze se snaží rozšířit NumPy (nebo spíše vytvořit nové) o paralelní výpočty, heterogenní rozměry, různě uložená data aj. [Dokumentace](http://blaze.pydata.org/docs/index.html) říká\n", "\n", "\"*Blaze is the next generation of NumPy, Python’s extremely popular array library. Blaze is designed to handle out-of-core computations on large datasets that exceed the system memory capacity, as well as on distributed and streaming data.*\n", "\n", "*Blaze will allow analysts and scientists to productively write robust and efficient code, without getting bogged down in the details of how to distribute computation, or worse, how to transport and convert data between databases, formats, proprietary data warehouses, and other silos.*\n", "\n", "*The core of Blaze consists of generic multi-dimensional Array and Table objects with an associated type system for expressing all kinds of data types and layouts, especially semi-structured, sparse, and columnar data. Blaze’s generalized calculation engine can iterate over the distributed array or table and dispatch to low-level kernels specialized for the layout and type of the data.\"*" ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
idnamebalance
01Alice100
12Bob-200
23Charlie300
34Denis400
45Edith-500
" ], "text/plain": [ " id name balance\n", "0 1 Alice 100\n", "1 2 Bob -200\n", "2 3 Charlie 300\n", "3 4 Denis 400\n", "4 5 Edith -500" ] }, "execution_count": 41, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from blaze import Data\n", "\n", "t = Data([(1, 'Alice', 100),\n", " (2, 'Bob', -200),\n", " (3, 'Charlie', 300),\n", " (4, 'Denis', 400),\n", " (5, 'Edith', -500)],\n", " fields=['id', 'name', 'balance'])\n", "t" ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
idnamebalance
02Bob-200
15Edith-500
" ], "text/plain": [ " id name balance\n", "0 2 Bob -200\n", "1 5 Edith -500" ] }, "execution_count": 42, "metadata": {}, "output_type": "execute_result" } ], "source": [ "t[t.balance < 0]" ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'apply, balance, columns, count, count_values, data, distinct, dshape, fields, head, id, isidentical, map, name, ndim, nelements, nrows, nunique, relabel, schema, shape, shift, sort, tail'" ] }, "execution_count": 43, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\", \".join((m for m in dir(t) if not m.startswith(\"_\")))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Výpočty na GPU (GPGPU)\n", "Pro GPU výpočty, což jsou obvykle SIMD (signle instruction multiple data) výpočty na mnoha jádrech, slouží v Pythonu knihovny [PyOpenCL](http://documen.tician.de/pyopencl) a [pyCUDA](http://documen.tician.de/pycuda/). Jen pro ilustraci ukázka PyOpenCL:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pyopencl as cl\n", "import numpy\n", "import numpy.linalg as la\n", "\n", "a = numpy.random.rand(50000).astype(numpy.float32)\n", "b = numpy.random.rand(50000).astype(numpy.float32)\n", "\n", "ctx = cl.create_some_context()\n", "queue = cl.CommandQueue(ctx)\n", "\n", "mf = cl.mem_flags\n", "a_buf = cl.Buffer(ctx, mf.READ_ONLY | mf.COPY_HOST_PTR, hostbuf=a)\n", "b_buf = cl.Buffer(ctx, mf.READ_ONLY | mf.COPY_HOST_PTR, hostbuf=b)\n", "dest_buf = cl.Buffer(ctx, mf.WRITE_ONLY, b.nbytes)\n", "\n", "prg = cl.Program(ctx, \"\"\"\n", " __kernel void sum(__global const float *a,\n", " __global const float *b, __global float *c)\n", " {\n", " int gid = get_global_id(0);\n", " c[gid] = a[gid] + b[gid];\n", " }\n", " \"\"\").build()\n", "\n", "prg.sum(queue, a.shape, None, a_buf, b_buf, dest_buf)\n", "\n", "a_plus_b = numpy.empty_like(a)\n", "cl.enqueue_copy(queue, a_plus_b, dest_buf)\n", "\n", "print(la.norm(a_plus_b - (a+b)), la.norm(a_plus_b))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Python + Intel Many Integrated Core Architecture\n", "\n", "Intel Many Integrated Core Architecture (MIC) je procesor podobný GPU s x86 instrukcemi. Nedávno byl uceden do prodeje první generace pod názvem Xeon Phi. Zdá se, že Python se podařilo na této architektuře zkompilovat: http://software.intel.com/en-us/forums/topic/392736. V takovém případě lze používat např. vestavěný modul `multiprocessing`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ...\n", "Další najdete na https://wiki.python.org/moin/ParallelProcessing." ] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { "display_name": "Python 3 (ipykernel)", "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.9.16" } }, "nbformat": 4, "nbformat_minor": 1 }