diff --git a/hw4_parallel_prog/HW_4.ipynb b/hw4_parallel_prog/HW_4.ipynb new file mode 100644 index 0000000..7916af8 --- /dev/null +++ b/hw4_parallel_prog/HW_4.ipynb @@ -0,0 +1,967 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "807eeb67-c734-4bd7-87aa-573d1d55366d", + "metadata": {}, + "source": [ + "В формулировке заданий будет использоваться понятие **worker**. Это слово обозначает какую-то единицу параллельного выполнения, в случае питона это может быть **поток** или **процесс**, выбирайте то, что лучше будет подходить к конкретной задаче\n", + "\n", + "В каждом задании нужно писать подробные аннотиции типов для:\n", + "1. Аргументов функций и классов\n", + "2. Возвращаемых значений\n", + "3. Классовых атрибутов (если такие есть)\n", + "\n", + "В каждом задании нужно писать докстроки в определённом стиле (какой вам больше нравится) для всех функций, классов и методов" + ] + }, + { + "cell_type": "markdown", + "id": "6d3b4f3a-f226-4ea4-a39f-dfb6e45bd983", + "metadata": {}, + "source": [ + "# Задание 1 (7 баллов)" + ] + }, + { + "cell_type": "markdown", + "id": "ba5a5046-e619-4e55-8a36-f0e72f7f8d4a", + "metadata": {}, + "source": [ + "В одном из заданий по ML от вас требовалось написать кастомную реализацию Random Forest. Её проблема состоит в том, что она работает медленно, так как использует всего один поток для работы. Добавление параллельного программирования в код позволит получить существенный прирост в скорости обучения и предсказаний.\n", + "\n", + "В данном задании от вас требуется добавить возможность обучать случайный лес параллельно и использовать параллелизм для предсказаний. Для этого вам понадобится:\n", + "1. Добавить аргумент `n_jobs` в метод `fit`. `n_jobs` показывает количество worker'ов, используемых для распараллеливания\n", + "2. Добавить аргумент `n_jobs` в методы `predict` и `predict_proba`\n", + "3. Реализовать функционал по распараллеливанию в данных методах\n", + "\n", + "В результате код `random_forest.fit(X, y, n_jobs=2)` и `random_forest.predict(X, y, n_jobs=2)` должен работать в ~1.5-2 раза быстрее, чем `random_forest.fit(X, y, n_jobs=1)` и `random_forest.predict(X, y, n_jobs=1)` соответственно\n", + "\n", + "Если у вас по каким-то причинам нет кода случайного леса из ДЗ по ML, то вы можете написать его заново или попросить у однокурсника. *Детали* реализации ML части оцениваться не будут, НО, если вы поломаете логику работы алгоритма во время реализации параллелизма, то за это будут сниматься баллы\n", + "\n", + "В задании можно использовать только модули из **стандартной библиотеки** питона, а также функции и классы из **sklearn** при помощи которых вы изначально писали лес" + ] + }, + { + "cell_type": "code", + "execution_count": 275, + "id": "af9d8b2d-3b0f-4789-a5f3-dfad537c916e", + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.base import BaseEstimator\n", + "from sklearn.datasets import make_classification\n", + "\n", + "\n", + "class RandomForestClassifierCustom(BaseEstimator):\n", + " # Ваш код здесь. Писать всё заново не надо, возьмите за основу код из ДЗ по ML\n", + " \n", + "\n", + "X, y = make_classification(n_samples=100000)" + ] + }, + { + "cell_type": "code", + "execution_count": 276, + "id": "5eecd633-1ae5-49fb-8cd0-4fd4ec1d6c24", + "metadata": {}, + "outputs": [], + "source": [ + "random_forest = RandomForestClassifierCustom(max_depth=30, n_estimators=10, max_features=2, random_state=42)" + ] + }, + { + "cell_type": "code", + "execution_count": 277, + "id": "85861ddf-1fdd-4dce-8e91-67e2a20e294d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 19.1 ms, sys: 66 ms, total: 85.1 ms\n", + "Wall time: 4.06 s\n" + ] + } + ], + "source": [ + "%%time\n", + "\n", + "_ = random_forest.fit(X, y, n_jobs=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 278, + "id": "04e62ac4-91d0-4cac-9de9-b4be0b582e7b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 11.1 ms, sys: 81 ms, total: 92.1 ms\n", + "Wall time: 256 ms\n" + ] + } + ], + "source": [ + "%%time\n", + "\n", + "preds_1 = random_forest.predict(X, n_jobs=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 279, + "id": "a4696384-b02b-4195-8ca7-60ae55aed197", + "metadata": {}, + "outputs": [], + "source": [ + "random_forest = RandomForestClassifierCustom(max_depth=30, n_estimators=10, max_features=2, random_state=42)" + ] + }, + { + "cell_type": "code", + "execution_count": 280, + "id": "9f14517f-7a80-4219-aff6-90097b59b085", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 782 µs, sys: 84.4 ms, total: 85.2 ms\n", + "Wall time: 2.18 s\n" + ] + } + ], + "source": [ + "%%time\n", + "\n", + "_ = random_forest.fit(X, y, n_jobs=2)" + ] + }, + { + "cell_type": "code", + "execution_count": 281, + "id": "c7f01f97-4ad4-4bed-a977-c8bd3ae962fd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 21 ms, sys: 75.4 ms, total: 96.4 ms\n", + "Wall time: 140 ms\n" + ] + } + ], + "source": [ + "%%time\n", + "\n", + "preds_2 = random_forest.predict(X, n_jobs=2)" + ] + }, + { + "cell_type": "code", + "execution_count": 284, + "id": "547ba1fd-9535-4267-93f8-3cc927f71e03", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 284, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(preds_1 == preds_2).all() # Количество worker'ов не должно влиять на предсказания" + ] + }, + { + "cell_type": "markdown", + "id": "0a2ea6af-f73d-436a-94d9-f7310ac9a4e6", + "metadata": {}, + "source": [ + "#### Какие есть недостатки у вашей реализации параллельного Random Forest (если они есть)? Как это можно исправить? Опишите словами, можно без кода (+1 дополнительный балл)" + ] + }, + { + "cell_type": "markdown", + "id": "19f2f04d-2c35-4d7d-a8fe-7f88d538fb79", + "metadata": {}, + "source": [ + "Ответ пишите тут" + ] + }, + { + "cell_type": "markdown", + "id": "f020bb22-a29e-4e39-aba9-d41e79a80732", + "metadata": {}, + "source": [ + "# Задание 2 (9 баллов)" + ] + }, + { + "cell_type": "markdown", + "id": "e3a8a0d2-f35b-4edb-9fb3-61da119255e9", + "metadata": {}, + "source": [ + "Напишите декоратор `memory_limit`, который позволит ограничивать использование памяти декорируемой функцией.\n", + "\n", + "Декоратор должен принимать следующие аргументы:\n", + "1. `soft_limit` - \"мягкий\" лимит использования памяти. При превышении функцией этого лимита должен будет отображён **warning**\n", + "2. `hard_limit` - \"жёсткий\" лимит использования памяти. При превышении функцией этого лимита должно будет брошено исключение, а функция должна немедленно завершить свою работу\n", + "3. `poll_interval` - интервал времени (в секундах) между проверками использования памяти\n", + "\n", + "Требования:\n", + "1. Потребление функцией памяти должно отслеживаться **во время выполнения функции**, а не после её завершения\n", + "2. **warning** при превышении `soft_limit` должен отображаться один раз, даже если функция переходила через этот лимит несколько раз\n", + "3. Если задать `soft_limit` или `hard_limit` как `None`, то соответствующий лимит должен быть отключён\n", + "4. Лимиты должны передаваться и отображаться в формате `X`, где `X` - символ, обозначающий порядок единицы измерения памяти (\"B\", \"K\", \"M\", \"G\", \"T\", ...)\n", + "5. В тексте warning'ов и исключений должен быть указан текщий объём используемой памяти и величина превышенного лимита\n", + "\n", + "В задании можно использовать только модули из **стандартной библиотеки** питона, можно писать вспомогательные функции и/или классы\n", + "\n", + "В коде ниже для вас предопределены некоторые полезные функции, вы можете ими пользоваться, а можете не пользоваться" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "78299479-f7e3-409c-b144-d9a14bb1715a", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import psutil\n", + "import time\n", + "import warnings\n", + "\n", + "\n", + "def get_memory_usage(): # Показывает текущее потребление памяти процессом\n", + " process = psutil.Process(os.getpid())\n", + " mem_info = process.memory_info()\n", + " return mem_info.rss\n", + "\n", + "\n", + "def bytes_to_human_readable(n_bytes):\n", + " symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')\n", + " prefix = {}\n", + " for idx, s in enumerate(symbols):\n", + " prefix[s] = 1 << (idx + 1) * 10\n", + " for s in reversed(symbols):\n", + " if n_bytes >= prefix[s]:\n", + " value = float(n_bytes) / prefix[s]\n", + " return f\"{value:.2f}{s}\"\n", + " return f\"{n_bytes}B\"\n", + "\n", + "\n", + "def memory_limit(softcap=None, hardcap=None, poll_interval=1):\n", + " # Ваш код здесь" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "e5466565-e46c-425e-a7bc-556a13633a0d", + "metadata": {}, + "outputs": [], + "source": [ + "@memory_limit(soft_limit=\"512M\", hard_limit=\"1.5G\", poll_interval=0.1)\n", + "def memory_increment():\n", + " \"\"\"\n", + " Функция для тестирования\n", + " \n", + " В течение нескольких секунд достигает использования памяти 1.89G\n", + " Потребление памяти и скорость накопления можно варьировать, изменяя код\n", + " \"\"\"\n", + " lst = []\n", + " for i in range(50000000):\n", + " if i % 500000 == 0:\n", + " time.sleep(0.1)\n", + " lst.append(i)\n", + " return lst" + ] + }, + { + "cell_type": "markdown", + "id": "4bf0a185-3fd7-4d48-bfeb-cf6ce8b12893", + "metadata": {}, + "source": [ + "# Задание 3 (11 баллов)" + ] + }, + { + "cell_type": "markdown", + "id": "77eec6f5-3d52-43fc-b241-9114eb9c813c", + "metadata": {}, + "source": [ + "Напишите функцию `parallel_map`. Это должна быть **универсальная** функция для распараллеливания, которая эффективно работает в любых условиях.\n", + "\n", + "Функция должна принимать следующие аргументы:\n", + "1. `target_func` - целевая функция (обязательный аргумент)\n", + "2. `args_container` - контейнер с позиционными аргументами для `target_func` (по-умолчанию `None` - позиционные аргументы не передаются)\n", + "3. `kwargs_container` - контейнер с именованными аргументами для `target_func` (по-умолчанию `None` - именованные аргументы не передаются)\n", + "4. `n_jobs` - количество workers, которые будут использованы для выполнения (по-умолчанию `None` - количество логических ядер CPU в системе)\n", + "\n", + "Функция должна работать аналогично `***PoolExecutor.map`, применяя функцию к переданному набору аргументов, но с некоторыми дополнениями и улучшениями\n", + " \n", + "Поскольку мы пишем **универсальную** функцию, то нам нужно будет выполнить ряд требований, чтобы она могла логично и эффективно работать в большинстве ситуаций\n", + "\n", + "1. `target_func` может принимать аргументы любого вида в любом количестве\n", + "2. Любые типы данных в `args_container`, кроме `tuple`, передаются в `target_func` как единственный позиционный аргумент. `tuple` распаковываются в несколько аргументов\n", + "3. Количество элементов в `args_container` должно совпадать с количеством элементов в `kwargs_container` и наоборот, также значение одного из них или обоих может быть равно `None`, в иных случаях должна кидаться ошибка (оба аргумента переданы, но размеры не совпадают)\n", + "\n", + "4. Функция должна выполнять определённое количество параллельных вызовов `target_func`, это количество зависит от числа переданных аргументов и значения `n_jobs`. Сценарии могут быть следующие\n", + " + `args_container=None`, `kwargs_container=None`, `n_jobs=None`. В таком случае функция `target_func` выполнится параллельно столько раз, сколько на вашем устройстве логических ядер CPU\n", + " + `args_container=None`, `kwargs_container=None`, `n_jobs=5`. В таком случае функция `target_func` выполнится параллельно **5** раз\n", + " + `args_container=[1, 2, 3]`, `kwargs_container=None`, `n_jobs=5`. В таком случае функция `target_func` выполнится параллельно **3** раза, несмотря на то, что `n_jobs=5` (так как есть всего 3 набора аргументов для которых нам нужно получить результат, а лишние worker'ы создавать не имеет смысла)\n", + " + `args_container=None`, `kwargs_container=[{\"s\": 1}, {\"s\": 2}, {\"s\": 3}]`, `n_jobs=5`. Данный случай аналогичен предыдущему, но здесь мы используем именованные аргументы\n", + " + `args_container=[1, 2, 3]`, `kwargs_container=[{\"s\": 1}, {\"s\": 2}, {\"s\": 3}]`, `n_jobs=5`. Данный случай аналогичен предыдущему, но здесь мы используем и позиционные, и именованные аргументы\n", + " + `args_container=[1, 2, 3, 4]`, `kwargs_container=None`, `n_jobs=2`. В таком случае в каждый момент времени параллельно будет выполняться **не более 2** функций `target_func`, так как нам нужно выполнить её 4 раза, но у нас есть только 2 worker'а.\n", + " + В подобных случаях (из примера выше) должно оптимизироваться время выполнения. Если эти 4 вызова выполняются за 5, 1, 2 и 1 секунды, то параллельное выполнение с `n_jobs=2` должно занять **5 секунд** (не 7 и тем более не 10)\n", + "\n", + "5. `parallel_map` возвращает результаты выполнения `target_func` **в том же порядке**, в котором были переданы соответствующие аргументы\n", + "6. Работает с функциями, созданными внутри других функций\n", + "\n", + "Для базового решения от вас не ожидается **сверххорошая** оптимизация по времени и памяти для всех возможных случаев. Однако за хорошо оптимизированную логику работы можно получить до **+3 дополнительных баллов**\n", + "\n", + "Вы можете сделать класс вместо функции, если вам удобнее\n", + "\n", + "В задании можно использовать только модули из **стандартной библиотеки** питона\n", + "\n", + "Ниже приведены тестовые примеры по каждому из требований" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "a917005b-bc1e-4229-9eef-d33bfe7050c2", + "metadata": {}, + "outputs": [], + "source": [ + "def parallel_map(target_func,\n", + " args_container=None,\n", + " kwargs_container=None,\n", + " n_jobs=None):\n", + " # Ваш код здесь" + ] + }, + { + "cell_type": "code", + "execution_count": 156, + "id": "0f41b1c8-0d26-483e-8114-1e7638006e34", + "metadata": {}, + "outputs": [], + "source": [ + "import time\n", + "\n", + "\n", + "# Это только один пример тестовой функции, ваша parallel_map должна уметь эффективно работать с ЛЮБЫМИ функциями\n", + "# Поэтому обязательно протестируйте код на чём-нибудбь ещё\n", + "def test_func(x=1, s=2, a=1, b=1, c=1):\n", + " time.sleep(s)\n", + " return a*x**2 + b*x + c" + ] + }, + { + "cell_type": "code", + "execution_count": 157, + "id": "54d48c99-6e8a-4990-8944-e5da424efdbd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 395 µs, sys: 8.26 ms, total: 8.65 ms\n", + "Wall time: 2.01 s\n" + ] + }, + { + "data": { + "text/plain": [ + "[3, 7.0, (-8-3j), 21]" + ] + }, + "execution_count": 157, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%time\n", + "\n", + "# Пример 2.1\n", + "# Отдельные значения в args_container передаются в качестве позиционных аргументов\n", + "parallel_map(test_func, args_container=[1, 2.0, 3j-1, 4]) # Здесь происходят параллельные вызовы: test_func(1) test_func(2.0) test_func(3j-1) test_func(4)" + ] + }, + { + "cell_type": "code", + "execution_count": 158, + "id": "dd0da716-5bbf-4925-b87a-7efe82c51cf9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 7.18 ms, sys: 7.73 ms, total: 14.9 ms\n", + "Wall time: 3.01 s\n" + ] + }, + { + "data": { + "text/plain": [ + "[3, 7.0, (-8-3j), 21]" + ] + }, + "execution_count": 158, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%time\n", + "\n", + "# Пример 2.2\n", + "# Элементы типа tuple в args_container распаковываются в качестве позиционных аргументов\n", + "parallel_map(test_func, [(1, 1), (2.0, 2), (3j-1, 3), 4]) # Здесь происходят параллельные вызовы: test_func(1, 1) test_func(2.0, 2) test_func(3j-1, 3) test_func(4)" + ] + }, + { + "cell_type": "code", + "execution_count": 159, + "id": "e2a0e6fd-1d92-4f06-9e4b-51290501c2da", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 5.89 ms, sys: 8.84 ms, total: 14.7 ms\n", + "Wall time: 3.02 s\n" + ] + }, + { + "data": { + "text/plain": [ + "[3, 7, 13, 21]" + ] + }, + "execution_count": 159, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%time\n", + "\n", + "# Пример 3.1\n", + "# Возможна одновременная передача args_container и kwargs_container, но количества элементов в них должны быть равны\n", + "parallel_map(test_func,\n", + " args_container=[1, 2, 3, 4],\n", + " kwargs_container=[{\"s\": 3}, {\"s\": 3}, {\"s\": 3}, {\"s\": 3}])\n", + "\n", + "# Здесь происходят параллельные вызовы: test_func(1, s=3) test_func(2, s=3) test_func(3, s=3) test_func(4, s=3)" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "34cda7e6-b24a-4564-958f-c4cf20b7ed7c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 6.54 ms, sys: 6.06 ms, total: 12.6 ms\n", + "Wall time: 3.02 s\n" + ] + }, + { + "data": { + "text/plain": [ + "[3, 3, 3, 3]" + ] + }, + "execution_count": 42, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%time\n", + "\n", + "# Пример 3.2\n", + "# args_container может быть None, а kwargs_container задан явно\n", + "parallel_map(test_func,\n", + " kwargs_container=[{\"s\": 3}, {\"s\": 3}, {\"s\": 3}, {\"s\": 3}])" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "e75853e1-1ed8-43c5-b045-e30dd34a9f34", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 4.11 ms, sys: 9.2 ms, total: 13.3 ms\n", + "Wall time: 2.01 s\n" + ] + }, + { + "data": { + "text/plain": [ + "[3, 7, 13, 21]" + ] + }, + "execution_count": 43, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%time\n", + "\n", + "# Пример 3.3\n", + "# kwargs_container может быть None, а args_container задан явно\n", + "parallel_map(test_func,\n", + " args_container=[1, 2, 3, 4])" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "55bd628f-0456-4b3f-8cea-ccbfc0e0dd72", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 500 µs, sys: 43.3 ms, total: 43.8 ms\n", + "Wall time: 2.04 s\n" + ] + }, + { + "data": { + "text/plain": [ + "[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%time\n", + "\n", + "# Пример 3.4\n", + "# И kwargs_container, и args_container могут быть не заданы\n", + "parallel_map(test_func)" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "2aa43efd-6628-46e3-970a-018be26fef3f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 500 µs, sys: 43.3 ms, total: 43.8 ms\n", + "Wall time: 2.04 s\n" + ] + }, + { + "data": { + "text/plain": [ + "[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%time\n", + "\n", + "# Пример 3.4\n", + "# И kwargs_container, и args_container могут быть не заданы\n", + "parallel_map(test_func)" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "bcdf8016-8363-4876-b05d-d1b536261f01", + "metadata": {}, + "outputs": [ + { + "ename": "ValueError", + "evalue": "Numbers of positional arguments and keyword arguments do not match: 4 and 3", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", + "File \u001b[0;32m:8\u001b[0m, in \u001b[0;36m\u001b[0;34m\u001b[0m\n", + "\u001b[0;31mValueError\u001b[0m: Numbers of positional arguments and keyword arguments do not match: 4 and 3" + ] + } + ], + "source": [ + "%%time\n", + "\n", + "# Пример 3.5\n", + "# При несовпадении количеств позиционных и именованных аргументов кидается ошибка\n", + "parallel_map(test_func,\n", + " args_container=[1, 2, 3, 4],\n", + " kwargs_container=[{\"s\": 3}, {\"s\": 3}, {\"s\": 3}])" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "115e5fea-0692-424e-acb5-e2d97391bad5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 9.3 ms, sys: 51.2 ms, total: 60.5 ms\n", + "Wall time: 2.06 s\n" + ] + }, + { + "data": { + "text/plain": [ + "[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]" + ] + }, + "execution_count": 45, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%time\n", + "\n", + "# Пример 4.1\n", + "# Если функция не имеет обязательных аргументов и аргумент n_jobs не был передан, то она выполняется параллельно столько раз, сколько ваш CPU имеет логических ядер\n", + "# В моём случае это 24, у вас может быть больше или меньше\n", + "parallel_map(test_func)" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "212fcdbe-a9f3-4eba-8d3f-7da0b853bffd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 2.06 ms, sys: 5.92 ms, total: 7.99 ms\n", + "Wall time: 2.01 s\n" + ] + }, + { + "data": { + "text/plain": [ + "[3, 3]" + ] + }, + "execution_count": 47, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%time\n", + "\n", + "# Пример 4.2\n", + "# Если функция не имеет обязательных аргументов и передан только аргумент n_jobs, то она выполняется параллельно n_jobs раз\n", + "parallel_map(test_func, n_jobs=2)" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "202675af-11b9-485c-baa4-cc1ef8eb919d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 314 µs, sys: 8.69 ms, total: 9 ms\n", + "Wall time: 2.01 s\n" + ] + }, + { + "data": { + "text/plain": [ + "[3, 7, 13]" + ] + }, + "execution_count": 48, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%time\n", + "\n", + "# Пример 4.3\n", + "# Если аргументов для target_func указано МЕНЬШЕ, чем n_jobs, то используется такое же количество worker'ов, сколько было передано аргументов\n", + "parallel_map(test_func,\n", + " args_container=[1, 2, 3],\n", + " n_jobs=5) # Здесь используется 3 worker'a" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "a0952cde-c347-438a-ad0b-15e1218e5b6f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 1.26 ms, sys: 9.47 ms, total: 10.7 ms\n", + "Wall time: 3.01 s\n" + ] + }, + { + "data": { + "text/plain": [ + "[3, 3, 3]" + ] + }, + "execution_count": 49, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%time\n", + "\n", + "# Пример 4.4\n", + "# Аналогичный предыдущему случай, но с именованными аргументами\n", + "parallel_map(test_func,\n", + " kwargs_container=[{\"s\": 3}, {\"s\": 3}, {\"s\": 3}],\n", + " n_jobs=5) # Здесь используется 3 worker'a" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "b2c7d286-6036-44b4-9686-d941297e9598", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 7.88 ms, sys: 0 ns, total: 7.88 ms\n", + "Wall time: 3.01 s\n" + ] + }, + { + "data": { + "text/plain": [ + "[3, 7, 13]" + ] + }, + "execution_count": 50, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%time\n", + "\n", + "# Пример 4.5\n", + "# Комбинация примеров 4.3 и 4.4 (переданы и позиционные и именованные аргументы)\n", + "parallel_map(test_func,\n", + " args_container=[1, 2, 3],\n", + " kwargs_container=[{\"s\": 3}, {\"s\": 3}, {\"s\": 3}],\n", + " n_jobs=5) # Здесь используется 3 worker'a" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "8b4649e2-a75b-4ede-8a46-32a0139fe998", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 7.88 ms, sys: 0 ns, total: 7.88 ms\n", + "Wall time: 3.01 s\n" + ] + }, + { + "data": { + "text/plain": [ + "[3, 7, 13]" + ] + }, + "execution_count": 50, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%time\n", + "\n", + "# Пример 4.6\n", + "# Если аргументов для target_func указано БОЛЬШЕ, чем n_jobs, то используется n_jobs worker'ов\n", + "parallel_map(test_func,\n", + " args_container=[1, 2, 3, 4],\n", + " kwargs_container=None,\n", + " n_jobs=2) # Здесь используется 2 worker'a" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "627aa784-15b5-41ca-b232-73c88c5ebed9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 3.03 ms, sys: 11 ms, total: 14 ms\n", + "Wall time: 5.01 s\n" + ] + }, + { + "data": { + "text/plain": [ + "[3, 3, 3, 3]" + ] + }, + "execution_count": 51, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%time\n", + "\n", + "# Пример 4.7\n", + "# Время выполнения оптимизируется, данный код должен отрабатывать за 5 секунд\n", + "parallel_map(test_func,\n", + " kwargs_container=[{\"s\": 5}, {\"s\": 1}, {\"s\": 2}, {\"s\": 1}],\n", + " n_jobs=2)" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "55bbfb49-4891-48c8-9cd7-782567b2a8fa", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['first', 'second', 'third', 'fourth', 'fifth']" + ] + }, + "execution_count": 57, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def test_func2(string, sleep_time=1):\n", + " time.sleep(sleep_time)\n", + " return string\n", + "\n", + "# Пример 5\n", + "# Результаты возвращаются в том же порядке, в котором были переданы соответствующие аргументы вне зависимости от того, когда завершился worker\n", + "arguments = [\"first\", \"second\", \"third\", \"fourth\", \"fifth\"]\n", + "parallel_map(test_func2,\n", + " args_container=arguments,\n", + " kwargs_container=[{\"sleep_time\": 5}, {\"sleep_time\": 4}, {\"sleep_time\": 3}, {\"sleep_time\": 2}, {\"sleep_time\": 1}])" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "id": "a5c2bcab-baa8-4f95-aceb-8cd6635844d6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[None, None, None]" + ] + }, + "execution_count": 58, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%time\n", + "\n", + "\n", + "def test_func3():\n", + " def inner_test_func(sleep_time):\n", + " time.sleep(sleep_time)\n", + " return parallel_map(inner_test_func, args_container=[1, 2, 3])\n", + "\n", + "# Пример 6\n", + "# Работает с функциями, созданными внутри других функций\n", + "test_func3()" + ] + } + ], + "metadata": { + "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.11.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}