<jupyter_start><jupyter_text>- Most computers nowaway come with mutiple cores- Taking advantage of mutltiple cores can help improving execution speed and resource utilization  Concurrent Programming == Parallel Programming? - **Concurrent Programming**: Facilitate the progression of multiple tasks at the same time. - **Parallel Programming**: Facilitate the execution of multiple tasks at the same time.   Notebook Source:https://github.com/bfortuner/ml-study/blob/master/multitasking_python.ipynb<jupyter_code>from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
import time
import numpy as np
import matplotlib.pyplot as plt
import glob
import random
import string
%matplotlib inline<jupyter_output><empty_output><jupyter_text>https://docs.python.org/3/library/concurrent.futures.html<jupyter_code>MULTITHREADING_TITLE="Multithreading"
MULTIPROCESSING_TITLE="Multiprocessing"<jupyter_output><empty_output><jupyter_text>- When a **program** is run, the operating system original spawns a single **process** to execute all instructions inside this program. - A **thread** is a path of execution inside a **process**. - There can be **multiple threads** inside a process. - There can be **multiple processes** as requested by a program.  - **Process** versus **Thread**  - **Thread** is also called *lightweight process*  - All **threads** within a process share that process' memory space.  - Each **process** within the same program has its own memory space.   Why Thread?- Faster context switch.- Effective utilization of multiple processor system.- More convenient resource sharing. - Easier and faster communication.   Why Process?- Threads share memory space, creating potential risk of inconsistency.  - Reading from stale memory location.   - Writing into supposedly static memory location. - For Python, you don't have to deal with GIL (Global Intepreter Lock) **Use the right tool for the job**<jupyter_code>def visualize_runtimes(results, title):
    start,stop = np.array(results).T
    plt.barh(range(len(start)),stop-start,left=start)
    plt.grid(axis='x')
    plt.ylabel("Tasks")
    plt.xlabel("Seconds")
    plt.title(title)
    return stop[-1]-start[0]
#https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.Executor.map

def multithreading(func, args, workers):
    begin_time = time.time()
    with ThreadPoolExecutor(max_workers=workers) as executor:
        res = executor.map(func, args, [begin_time for i in range(len(args))])
    return list(res)
        
def multiprocessing(func, args, workers):
    begin_time = time.time()
    with ProcessPoolExecutor(max_workers=workers) as executor: 
        res = executor.map(func, args, [begin_time for i in range(len(args))])
    return list(res)<jupyter_output><empty_output><jupyter_text>API Calls<jupyter_code>from urllib.request import urlopen

def download(url, base):
    start = time.time() - base
    try:
        resp = urlopen(url)
    except Exception as e:
        print ('ERROR: %s' % e)
    stop = time.time() - base
    return start,stop
N = 16
URL = 'http://scholar.princeton.edu/sites/default/files/oversize_pdf_test_0.pdf'
urls = [URL for i in range(N)]<jupyter_output><empty_output><jupyter_text>Serial<jupyter_code>%timeit -n 1 [download(url, 1) for url in urls]<jupyter_output>6.19 s ± 294 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)<jupyter_text>Multithreading<jupyter_code>visualize_runtimes(multithreading(download, urls, 1), "Single Thread")
visualize_runtimes(multithreading(download, urls, 2),MULTITHREADING_TITLE)
visualize_runtimes(multithreading(download, urls, 4),MULTITHREADING_TITLE)<jupyter_output><empty_output><jupyter_text>Multiprocessing<jupyter_code>visualize_runtimes(multiprocessing(download, urls, 1), "Single Process")
visualize_runtimes(multiprocessing(download, urls, 2), MULTIPROCESSING_TITLE)
visualize_runtimes(multiprocessing(download, urls, 4), MULTIPROCESSING_TITLE)<jupyter_output><empty_output><jupyter_text>IO Heavy<jupyter_code>def io_heavy(text,base):
    start = time.time() - base
    f = open('output.txt', 'wt', encoding='utf-8')
    f.write(text)
    f.close()
    stop = time.time() - base
    return start,stop
N=12
TEXT = ''.join(random.choice(string.ascii_lowercase) for i in range(10**4*5))<jupyter_output><empty_output><jupyter_text>Serial<jupyter_code>%timeit -n 1 [io_heavy(TEXT,1) for i in range(N)]<jupyter_output>5.22 s ± 314 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)<jupyter_text>MultithreadingShould see good benefit from this<jupyter_code>visualize_runtimes(multithreading(io_heavy, [TEXT for i in range(N)], 1),"Single Thread")
visualize_runtimes(multithreading(io_heavy, [TEXT for i in range(N)], 2),MULTITHREADING_TITLE)
visualize_runtimes(multithreading(io_heavy, [TEXT for i in range(N)], 4),MULTITHREADING_TITLE)<jupyter_output><empty_output><jupyter_text>MultiprocessingShould see good benefit from this<jupyter_code>visualize_runtimes(multiprocessing(io_heavy, [TEXT for i in range(N)], 1),"Single Process")
visualize_runtimes(multiprocessing(io_heavy, [TEXT for i in range(N)], 2),MULTIPROCESSING_TITLE)
visualize_runtimes(multiprocessing(io_heavy, [TEXT for i in range(N)], 4),MULTIPROCESSING_TITLE)<jupyter_output><empty_output><jupyter_text>CPU Intensive<jupyter_code>def cpu_heavy(n,base):
    start = time.time() - base
    count = 0
    for i in range(n):
        count += i
    stop = time.time() - base
    return start,stop
N = 10**7
ITERS = 10<jupyter_output><empty_output><jupyter_text>Serial<jupyter_code>%timeit -n 1 [cpu_heavy(N, time.time()) for i in range(ITERS)]<jupyter_output>39.4 s ± 1.22 s per loop (mean ± std. dev. of 7 runs, 1 loop each)<jupyter_text>MultithreadingNo benefit on CPU-intensive tasks<jupyter_code>visualize_runtimes(multithreading(cpu_heavy, [N for i in range(ITERS)], 1),"Single Thread")
visualize_runtimes(multithreading(cpu_heavy, [N for i in range(ITERS)], 2),MULTITHREADING_TITLE)
visualize_runtimes(multithreading(cpu_heavy, [N for i in range(ITERS)], 4),MULTITHREADING_TITLE)<jupyter_output><empty_output><jupyter_text>MultiprocessingShows benefits on CPU-intensive tasks<jupyter_code>visualize_runtimes(multiprocessing(cpu_heavy, [N for i in range(ITERS)], 1),"Single Process")
visualize_runtimes(multiprocessing(cpu_heavy, [N for i in range(ITERS)], 2),MULTIPROCESSING_TITLE)
visualize_runtimes(multiprocessing(cpu_heavy, [N for i in range(ITERS)], 4),MULTIPROCESSING_TITLE)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1134466769706337, 'fraction_numerical': 0.01035548686244204, 'mean_word_length': 6.262626262626263, 'pattern_counts': {'":': 0, '<': 58, '<?xml version=': 0, '>': 58, 'https://': 3, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '15', 'dir': 'jupyter-structured-clean-dedup', 'id': '185368', 'license': 'MIT', 'n_tokens_mistral': 2315, 'n_tokens_neox': 2151, 'n_words': 540, 'path': 'concurent_python/multitasking_python.ipynb', 'repo_name': 'linhbngo/python-examples', 'size': '6470'}