ก่อนจะขึ้นเรื่องการใช้ library MPI ขอพูดถึงประเภทของการเขียนโปรแกรมแบบ Parallel ก่อน
โดย คอนเซ็ปแล้ว ถ้าเรามีคอมพิวเตอร์หนึ่งเครื่องมันสามารถทำงานเสร็จได้ใน 10 นาที ... วั้นถ้าเราอยากให้งานมันเสร็จเร็วขึ้น ก็แค่เพิ่มจำนวนคอมพิวเตอร์เข้าไปไง ใช้หลักการที่ว่า "หลายหัวดีกว่าหัวเดียว" มีหลายคนช่วยกันคิดก็น่าจะเสร็จเร็วกว่าคนเดียวคิดอยู่แล้ว (เหรอ? O__O)
Shared Memory vs. Message Passing
เรื่องเมม เรื่องใหญ่นะ!
เวลาเราเขียนโปรแกรมอยู่ในเครื่องเดียวตามปกติ มันก็ไม่ค่อยจะมีปัญหาเรื่องนี้เท่าไหร่หรอกนะ เพราะว่าคอมพิวเตอร์1เครื่อง ก็มี 1 CPU แล้วก็มี 1 Memory
อย่างที่เรารู้ว่าถ้า โปรแกรมมีการใช้ตัวแปร มันก็ต้องเก็บไว้ในหน่วยความจำหลัก Main Memory = RAM (ใครที่ยังไม่รู้ก็ถือว่ารู้ไปตอนนี้เลยนะ)
ดังนั้น ... เมื่อเราใช้คอมหลายเครื่อง แล้วหน่วยความจำละ? จะวางไว้ที่ไหนดีนะ
โซลูชั่นที่เขาใช้กันก็มี 2 แบบนั่นคือ
Shared Memory
หากว่าใช้รูปแบบนี้ หมายความว่าไม่ว่าเราจะมีคอมกี่เครื่องก็ตาม ทุกตัวจะใช้ Memory ร่วมกัน
ดัง นั้นหากว่า CPU1 ทำการวางค่า X = 1 เอาไว้ ก็มีโอกาสที่ CPU ตัวอื่นๆ จะเข้ามาอ่านค่า X หรือแม้แต่จะเขียนค่า X ใหม่ลงไปก็ยังได้เลย!
ข้อดี - CPU ทุกตัวสามารถเห็นตัวแปรที่ CPU ตัวอื่นถืออยู่ได้ ทำให้เวลาจะส่งข้อมูลให้กันทำได้เร็วมาก
ข้อเสีย - การที่ตัวแปรมันใสวางอยู่ที่เดียวกัน ใครจะใช้ก็ได้ทำให้บางครั้งมีการเรียกใช้ตัวแปรพร้อมๆ กัน ... อ่าว แล้วเกิดไรขึ้น มันก็ชนกันไงล่ะ ตู้ม กลายเป็นโกโก้ครั้นช์!
Message Passing
อีกวิธีที่เป็นที่นิยมไม่แพ้กันคือการส่ง message คุยกันระหว่าง CPU วิธีนี้คอมทุกเครื่องจะมี Memory เป็นของตัวเอง CPU ตัวอื่นจะไม่เห็นตัวแปรที่เราถืออยู่ ทำให้ไม่เกิดปัญหาการจะใช้ตัวแปร แล้วดันไปชนกับตัวอื่น
แต่เมื่อมันไม่เป็นตัวแปรของตัว อื่น ก็ต้องใช้วิธีว่าถ้าอยากให้ตัวอื่นช่วยคิดอะไรก็ต้อง "Send" ข้อมูลไปให้ตัวนั้น ซึ่งส่วนนี้จำทำให้เสียเวลาไปเล็กน้อย
ข้อดี - CPU แต่ละตัวมี Memory เป็นของตัวเอง ไม่ต้องกลัวไปชนกับ CPU ตัวอื่น คิดอะไรก็ได้ สบายใจ
ข้อเสีย - เมื่อมันไม่เห็นกัน พอมันจะคุยกันก็ต้องทำการ "Send" ข้อมูลให้กัน ทำให้เสียเวลานะ
เขียน Parallel ภาษาซีด้วย MPI Library
MPI (Message Passing Interface) เป็น library ตัวช่วยสำหรับเขียนโปรแกรมแบบ Parallel ในตัวอย่างนี้หยิบภาษา C มาใช้
แล้ว ตามชื่อมันก็บอกอยู่แล้วว่า library ตัวนี้เป็นโครงสร้างโปรแกรม Parallel ประเภทไหน เนอะ... (ยังไม่รู้เหรอ! เป็น Message Passing ไงล่ะ ชื่อก็บอกอยู่ทงโท่ขนาดนี้)
โอเค! เมื่อมันเป็น Message Passing มันก็ต้องใช้วิธี "Send" ข้อมูลคุยกันระหว่าง CPU (ในเรื่องMPIจะขอเรียกว่า Process แทนละกัน)
งั้นมาดูโครงสร้างโปรแกรมที่ใช้ MPI กันดีกว่า
#include "mpi.h" #include <stdio.h> int main( int argc, char *argv[] ) { MPI_Init( &argc, &argv ); printf( "Hello, world!" ); MPI_Finalize(); return 0; }
จะใช้ library อย่างแรกก็ต้อง "include" มาก่อนนะ (ประมาณ import class ในภาษา Java นั่นแล~)
ส่วนต่อมาเป็นการกำหนดว่า "โค้ดตั้งแต่ตรงนี้ถึงตรงนี้นะ ที่จะให้รันแบบพาราเรล"
ซึ่งเราจะใช้
MPI_Init( &argc, &argv )
เป็นจุดที่เอาไว้กำหนอกว่าตั้งแต่ตรงนี้เป็นต้นไปเราจะรันโค้ดต่อไปนี้แบบพาราเรลละนะ
MPI_Finalize()
บอกว่าหยุดทำการรันโค้ดแบบพาราเรลได้แล้วล่ะ จบกันแค่นี้!
แนวคิดการเขียน Parallel แบบ MPI
ถึงจะพอเข้าใจว่าการเขียนโปรแกรม แบบพาราเรลคือการช่วยกันทำงาน (หรือโยนงานไปให้คนอื่นนั่นแหละ) แต่มีมุมมองการเขียนโปรแกรม มันต้องเข้าใจลึกกว่านั้น
---
สมมุติว่าเรามีคอมอยู่ 4 เครื่องพร้อมกับ "Problem" อย่างหนึ่งที่ต้องการแก้ ขอยกตัวอย่างเป็นโจทย์เบสิกว่าเราต้องการบวกเลขตั้งแต่ 1 ถึง 100 ว่าได้เท่าไหร่ละกัน
1 + 2 + 3 + 4 + 5 + ... + 98 + 99 + 100 = ?
แนวคิดคือในเมื่อ problem size ของเราเป็น100ตัว + มีคอม4เครื่อง -> ก็จัดการแยกมันออกเป็น 4 ส่วนซะสิ
มี 4 เครื่อง (SIZE = 4) ก็จะมี Process ตั้งแต่เบอร์ 0 - 3 (RANK = 0 to 3)
100มัน แบ่ง4ส่วนได้ 25 โดยเราจะต้องกำหนดเครื่องหนึ่งขึ้นมาเป็นคนแบ่งงาน ส่วนใหญ่ก็จะใช้เจ้า Process 0 ที่เป็นคนแรกนั้นแหละ (เรียกว่า root)
ดังนั้น
- Process 0 จะต้องจัดการบวกเลข 1-25 ให้เสร็จ
- Process 1 จะต้องจัดการบวกเลข 26-50 ให้เสร็จ
- Process 2 จะต้องจัดการบวกเลข 51-75 ให้เสร็จ
- Process 3 จะต้องจัดการบวกเลข 76-100 ให้เสร็จ
เอาล่ะ หลังจากโยนงานให้คนอื่น เอ๊ย แบ่งงานเสร็จแล้ว แต่ละ Process ก็จะไปบวกเลขในช่วงที่ตัวเองรับผิดชอบมา จากเดิมที่มีคอมเครื่องเดียว ต้องบวกทั้งหมด 100 ครั้ง ก็จะเหลือแค่ 25 ครั้งเท่านั้น! ดูเร็วขึ้นเยอะ
แล้วพอแต่ละ Process บวกเสร็จแล้วก็จะส่งค่ากลับมาให้ root บวกค่ารวมทั้งหมดอีกที
Comm World
เวลาใช้ MPI มันจะมองว่าคอมทุกเครื่องที่เชื่อมต่อกันอยู่เป็น "World" ตัวนึง ในการรันแต่ละครั้งจำนวนคอมไม่จำเป็นต้องเท่ากันก็ได้ เราก็เลยมักจะไม่เขียนโปรแกรมแบบ Fix ค่าว่าโจทย์อันนี้ใช้คอม 4 ตัว แต่จะถามจำนวนคอมพิวเตอร์ที่มีจากคำสั่งหา size แทน
เราสามารถใช้คำสั่งใน MPI หาว่าตอนนี้มีคอมกี่เครื่อง (size) และคอมตัวนี้เป็นตัวที่เท่าไหร่ (rank) โดยใช้คำสั่ง 2 ตัวนี้คือ
MPI_Comm_size( MPI_COMM_WORLD, &size )
เอาไว้หาว่าใน world นี้น่ะมันมี process กี่ตัว
MPI_Comm_rank( MPI_COMM_WORLD, &rank )
หาว่า process ตัวนี้น่ะ เป็นตัวที่เท่าไหร่ (เริ่มที่ 0)
#include "mpi.h"
#include <stdio.h>
int main(int argc,char *argv[] )
{
int rank;
int size;
MPI_Init( &argc, &argv );
MPI_Comm_rank( MPI_COMM_WORLD, &rank );
MPI_Comm_size( MPI_COMM_WORLD, &size );
printf( "rank = %d and size = %d \n", rank, size);
MPI_Finalize();
return 0;
}
ในตัวอย่างนี้มีการสร้างตัวแปรขึ้นมา 2 ตัวคือ rank กับ size เอาไว้เก็บค่า rank กับ size นั้นแหละนะ = ="
แล้วก็สั่งให้มัน print ค่า rank + size ออกมาให้ดู
ถ้า n-process ของเราเป็น คอม 4 เครื่อง ผลที่ได้จากการรันก็อาจจะออกมาเป็นแบบนี้
rank = 2 and size = 4 rank = 0 and size = 4 rank = 3 and size = 4 rank = 1 and size = 4
จะเห็นว่ามันไม่ได้รันแบบ process 0 -> process 1 -> process 2 -> process 3 นะ
แต่การรันโปรแกรมพาราเรลนั้น คอมพิวเตอร์แต่ละเครื่องจะรันไปพร้อมๆ กัน เลยบอกไม่ได้ว่าการรันครั้งนี้ process ไหนจะทำงานเสร็จก่อนนะ
โครงสร้างแบบ Master & Slave
อย่าง ที่บอกไปตอนต้นแล้วว่าเมื่อมันมีคอมหลายเครื่อง ก็ต้องมีคอมตัวแทนกลุ่ม 1 เครื่องที่ทำหน้าที่เป็นคนสั่งงาน + จ่ายงานให้คนอื่นเอาไปทำ ซึ่งเราจะเรียกเจ้านั่นว่า Master (หรือ root) ส่วนคนที่รับคำสั่งไปทำจะเรียกว่า Slave
ส่วนมากแล้ว Master จะมีแค่ 1 เครื่องส่วน Slave จะมีเยอะกว่า
และวิธีการแบ่งว่า process ไหนเป็น Master ตัวไหนเป็น Slave ก็ไม่ยากเลย เราก็ใช้ rank ของมันนั่นแหละเป็นตัวกำหนด
rank = 0 ก็เป็น Master
rank > 0 คือตั้งแต่ 1, 2, 3, ... ไปเรื่อยๆ ก็เป็น Slave
การ เขียนโค้ดก็ใช้ if-else ธรรมดาๆ เลย ถ้ามันเป็น rank = 0 มันก็จะเขียนไปทำโค้ดในส่วนที่เรากำหนดไว้ว่าเดี๋ยวจะให้ Master มารัน ส่วน rank อื่นๆ ก็จะไปรันโค้ดชุดที่เราเตรียมไว้ให้ Slave ทำ
วันนี้เอาไว้แค่เบสิกๆ แค่นี้ก่อน
ครั้งหน้าจะมาต่อเรื่องการ Message Passing ส่งข้อมูลคุยกันระหว่าง process ด้วยคำสั่ง MPI_Send และ MPI_Recv