เขียนโปรแกรมแบบ Parallel ด้วย MPI – ตอนที่1 แนะนำการใช้MPIเบื้องต้น

ก่อนจะขึ้นเรื่องการใช้ 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

1455 Total Views 3 Views Today
Ta

Ta

สิ่งมีชีวิตตัวอ้วนๆ กลมๆ เคลื่อนที่ไปไหนโดยการกลิ้ง .. ถนัดการดำรงชีวิตโดยไม่โดนแสงแดด
ปัจจุบันเป็น Senior Software Engineer อยู่ที่ Centrillion Technology
งานอดิเรกคือ เขียนโปรแกรม อ่านหนังสือ เขียนบทความ วาดรูป และ เล่นแบดมินตัน

ใส่ความเห็น

อีเมลของคุณจะไม่แสดงให้คนอื่นเห็น ช่องที่ต้องการถูกทำเครื่องหมาย *