【Python】threadingによる並列処理(マルチスレッド)

threadingというライブラリを使用する
今いるスレッドを確認

import threading
import warnings
warnings.simplefilter('ignore')

print(threading.currentThread().getName())

$ python3 main.py
MainThread

### threading.Threadでmainとは別にスレッドを作成する

import threading
import time
import warnings
warnings.simplefilter('ignore')

def boil_udon():
    print(" ■ thread :", threading.currentThread().getName())

    print(' うどんを茹でます。')
    time.sleep(3)
    print(' うどんが茹で上がりました。')

if __name__ == "__main__":
    print(" ■ thread :", threading.currentThread().getName())

    print('うどんを作ります。')

    # スレッドを作成
    thread1 = threading.Thread(target=boil_udon)
    thread1.start()
    thread1.join()

    print('うどんの盛り付けをします。')
    print('うどんができました。')

$ python3 main.py
■ thread : MainThread
うどんを作ります。
■ thread : Thread-1 (boil_udon)
うどんを茹でます。
うどんが茹で上がりました。
うどんの盛り付けをします。
うどんができました。

### スレッドを更に追加する

import threading
import time
import warnings
warnings.simplefilter('ignore')

def boil_udon():
    print(" ■ thread :", threading.currentThread().getName())

    print(' うどんを茹でます。')
    time.sleep(3)
    print(' うどんが茹で上がりました。')

def make_tuyu():
    print(" ■ thread :", threading.currentThread().getName())

    print(' うどんの汁を作ります。')
    time.sleep(2)
    print(' うどんの汁ができました。')


if __name__ == "__main__":
    print(" ■ thread :", threading.currentThread().getName())

    print('うどんを作ります。')

    # スレッドを作成
    thread1 = threading.Thread(target=boil_udon)
    thread2 = threading.Thread(target=make_tuyu)

    thread1.start()
    thread2.start()

    thread1.join()
    thread2.join()

    print('うどんの盛り付けをします。')
    print('うどんができました。')

$ python3 main.py
■ thread : MainThread
うどんを作ります。
■ thread : Thread-1 (boil_udon)
うどんを茹でます。
■ thread : Thread-2 (make_tuyu)
うどんの汁を作ります。
うどんの汁ができました。
うどんが茹で上がりました。
うどんの盛り付けをします。
うどんができました。

threadが作られると、同時に処理されていることがわかる
ThreadPoolExecutorの場合は、単純に1つの処理を複数のスレッドで実行するが、threadingの場合は、プログラム内容を指定してスレッドを作成することができる

Pythonでマルチスレッドとマルチプロセス

### シングルスレッド

from concurrent.futures import ThreadPoolExecutor
import time
def func():
    time.sleep(1)
start = time.time()
for i in range(8):
    func()
print(time.time() - start)

$ python3 single.py
8.064586639404297

### マルチスレッド(同時に実行)

from concurrent.futures import ThreadPoolExecutor
import time
def func():
    time.sleep(1)
start = time.time()
with ThreadPoolExecutor(max_workers=4) as e:
    for i in range(8):
        e.submit(func)
print(time.time() - start)

$ python3 multithread.py
2.0498673915863037

### マルチプロセス(複数のプロセスで同時に処理)

from concurrent.futures import ProcessPoolExecutor
import time
def func():
    time.sleep(1)
start = time.time()
with ProcessPoolExecutor(max_workers=4) as e:
    for i in range(8):
        e.submit(func)
print(time.time() - start)

$ python3 multiprocess.py
2.1424620151519775

import os
import time
import datetime
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor

x1, x2, y1, y2 = -1.8, 1.8, -1.8, 1.8
c_real, c_imag = -0.62772, -0.42193

def calculate_z_serial_purepython(maxiter, zs, cs):
    output = [0] * len(zs)
    for i in range(len(zs)):
        n = 0
        z = zs[i]
        c = cs[i]
        if (i % 100) == 0:
            time.sleep(0.0001)
        while abs(z) < 2 and n < maxiter:
            z = z * z + c
            n += 1
        output[i] = n
    return output

def calc_pure_python(desired_width, max_iterations):
    x_step = (float(x2 - x1) / float(desired_width))
    y_step = (float(y1 - y2) / float(desired_width))
    x = []
    y = []
    ycoord = y2
    while ycoord > y1:
        y.append(ycoord)
        ycoord += y_step
    xcoord = x1
    while xcoord < x2:
        x.append(xcoord)
        xcoord += x_step

    zs = []
    cs = []
    for ycoord in y:
        for xcoord in x:
            zs.append(complex(xcoord, ycoord))
            cs.append(complex(c_real, c_imag))
    
    output = calculate_z_serial_purepython(max_iterations, zs, cs)

if __name__ == "__main__":

    max_workers = os.cpu_count()
    start = datetime.datetime.now()
    for i in range(16):
        calc_pure_python(desired_width=500, max_iterations=100)
    elapsed = datetime.datetime.now() - start
    print("SingleThread: {}ms".format(elapsed.seconds*1000 + elapsed.microseconds/1000))

    start = datetime.datetime.now()
    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        for i in range(16):
            executor.submit(calc_pure_python, 500, 100)
    elapsed = datetime.datetime.now() - start
    print("MultiThread: {}ms".format(elapsed.seconds*1000 + elapsed.microseconds/1000))

    # マルチプロセスの場合
    start = datetime.datetime.now()
    with ProcessPoolExecutor(max_workers=max_workers) as executor:
        for i in range(16):
            executor.submit(calc_pure_python, 500, 100)
    elapsed = datetime.datetime.now() - start
    print("MultiProcess: {}ms".format(elapsed.seconds*1000 + elapsed.microseconds/1000))

$ python3 main.py
SingleThread: 17934.699ms
MultiThread: 11256.051ms
MultiProcess: 8493.925ms

os.cpu_count() でCPUのコアを取得できるんか…
処理に時間がかかる箇所をマルチプロセスで実装すれば良さそうではあるな…

マルチスレッドとマルチタスク

– マルチコアシステムは2つ以上のコアが搭載されたシングルプロセッサCPU。L2キャッシュやフロントサイドバスなどを共有。コストを大幅に削減

– マルチタスクは複数のタスクでCPUなどの処理リソースを共有する方式。マルチタスクは各計算タスクを素早く切り替えることで、同時に行われているように見せる機能

– マルチコアは、異なるタスクに対して、複数の計算エンジンが独立して動作する。プロセスを別々のCPUコア間で分割することで、複数のアプリケーションを効率的に実行できる

– マルチスレッド処理: マルチタスク処理の理論をアプリケーションに適用したもので、1つのアプリケーション内のSiriを個々のスレッドに分割し、各スレッドは並列に実行される

【CPU】Task Status Segment

レジスタの状態をメモリに書き込む

struct TSS32 {
	int backlink, esp0, ss0, esp1, ss1, esp2, ss2, cr3;  // タスクの設定
	int eip, eflags, eax, ecx, ebx, esp, ebp, esi, edi;  // 32bitレジスタ eipは拡張命令ポインタ 
	int es, cs, ss, ds, fs, gs;                          // 16bitレジスタ 
	int ldtr, iomap;
}

コンピュータの構造

### レジスタ
汎用レジスタ、フラグレジスタ、プログラムカウンタ(EIP)、スタックポインタ(ESP)、制御レジスタ(EFLAGS)がある
フラグレジスタは算術結果を保存
プログラムカウンタは次のどのアドレスから読み込めば良いかを示す
スタックポインタはスタックと呼ばれるメモリ上に確保された記憶領域のアドレスを表す

### 制御装置
フェッチ、デコード(機械語の解読)、指示、プログラムカウンタを更新

### リトルエンディアン
リトルエンディアンはビットにかかわらずアドレスから値を呼び出すことができる
ビッグエンディアンはビットごとに調整しなければならない

x86とは?

x86はインテルがパソコンなど向けに開発製造しているマイクロプロセッサ
8086と共通の命令セットで、Intel Core i3/i5/i7/i9などにも引き継がれている
AMD64に習い64ビットプロセッサをx86-64と呼んでいいる

PowerPCとは?

PowerPCとは、Apple, IBM, Motorolaによって開発されたRISCアーキテクチャ
AppleのMacintoshやIBMのRS/6000などで採用
ゲーム機、組み込みシステム、スーパーコンピュターで使用
現在は新規採用されるケースは少ない

[kernel]マルチプロセッサ

物理メモリを共有して管理するメモリ共有型並列コンピューティング方式のこと
特定のCPUに非対称的に割り付けられた処理に依存することなく、全てのCPUに対して対称的、均一的に処理が割り付けられた並列処理方式
二つ目以降のCPUの起動は、一つ目のCPUがカーネル初期化関数(start_kernel関数)の一番最後に呼び出す関数 smp_init関数にて行われる
二つ目以降のCPU上で動作する初期化プロセスは処理が終了するとそのままアイドルプロセスへと変化する。その後は、スケジューラによりプロセスが割り当てらられるのを待ち続ける

FPGA

FPGAとは?
-> Field Programmable Gate Arrayの略
-> 現場で書き換え可能な論理回路の多数配列:ハードウェア言語で修正が出来るデバイス

ハードウェア言語とは一般的に半導体の回路記述をする際に用いる言語
論理回路とはデジタル信号を扱う回路のこと
論理合成と配置配線はハードウェア言語で記述された回路をFPGAに書き込む為のデータに変換すること

CPLDは汎用ロジックIC数百個~数千個分の回路を内部で構成できる
LSIを超える規模の回路を簡単に構成できる
PLGの一種

【組み込み系の開発工程】
要求分析→要件定義→基本設計・詳細設計→RTL設計(Verilog、VHDLなどを用いてコーディング) →論理合成→動作シミュレーション→配置配線

ビッグデータのデータ処理やディープラーニング向けの並列計算等にFPGAやASICが使われることもある

[CPU]命令セット

– 命令の集まり
– コンピュータで使われる命令の表現形式と各命令の動作を定めたもの
命令 = 操作オペランド + 対象
L ソースオペランド
L デスティネーションオペランド
オペランドとなるものはデータレジスタ、メモリ語、プログラムカウンタ、その他レジスタ

### 命令の表現形式
(1)R型: op(5:操作コード) rs(5:オペランドレジスタ) rt(5) rd(5) aux(11:実行細則)
(2)I型: op(6) rs(5) rt(5) imm/dpl(16:immediate displacement)
(3)A型: op(6) addr(26:メモリアドレス)
命令語が32ビット、命令セットの大きさが64、レジスタ数が32

アセンブリ言語表現
R型:add r2 r3 r1 0
I型:subi r2 r1 14
A型:j 1048581

算術論理演算命令(R型(整数)、I型、 R型(浮動小数点))
加算:add, addi, fadd
減算:sub, subi, fsub
乗算:mul, muli, fmul
除算:div, divi, fdiv
除余:rem, remi
絶対値:abs, , fabs
算術左シフト: sla, ,
算術右シフト: sra, ,
論理積:and, andi
論理和:or, ori
否定:not,
NOR:nor, nori
NAND:nand, nandi
排他的論理和:xor, xori
EQUIV:eq, eqi
論理左シフト:sll
論理右シフト:srl

命令の動作はオペランドがデコーダでALUに行き、レジスタのアドレスを取得して計算する

シリコンチップで集積回路を作るには、シリコン上の構造物の配置を図面に落とす必要がある
基本素子を作り、それを配置していく

ICチップを作るには、まず原材料としてシリコンウェハー(Si)が必要
ピラニア溶液(H2SO4:H2O2)、RCA1(H2O:NH3:H2O2)、RCA2(H2O:HCL:H2O2)で洗浄してHF液につけて自然酸化膜を作る

なるほど、CPUの設計って回路設計だけでなく、物理、化学、光学、電気学などかなり幅広い知識を応用してんだな。