ตอนที่แล้วเราให้คอมพิวเตอร์ทำการทดลองแทนเราหลายๆครั้ง แล้วดูว่าคำตอบแต่ละครั้งมีค่าเท่าไร เอาคำตอบมาวาดฮิสโตแกรมว่ากระจุกตัวอยู่แถวไหนบ้างเพื่อดูว่าเราควรจะเชื่อคำตอบจากการทดลองของคอมพิวเตอร์หรือไม่นะครับ
เมื่อต้องมีการสั่งงานให้คอมพิวเตอร์ทำงานหลายๆครั้ง บางครั้งก็ใช้เวลานานเกินรอ ถ้ามีวิธีทำให้คอมพิวเตอร์ทำงานให้เราเร็วขึ้นเราก็ควรรู้จักไว้บ้าง
โปรแกรมภาษาไพธอนนั้นเขียนง่าย อ่านง่าย แต่ขึ้นชื่อเรื่องความช้าเมื่อต้องทำงานมากๆเมื่อเทียบกับภาษาอื่นเช่น C หรือ Java
ปกติเรายินดีที่จะเขียนโปรแกรมด้วยภาษาไพธอนเพราะงานส่วนใหญ่คอมพิวเตอร์ประมวลผลแป๊บเดียว จุดเด่นที่ภาษาไพธอนเขียนง่ายและอ่านง่ายเป็นข้อได้เปรียบที่สำคัญ
ในกรณีที่โปรแกรมภาษาไพธอนใช้เวลานานในการประมวลผล เราก็มีทางออกหลายทาง เช่นใช้ Numpy คำนวณให้เมื่อมีข้อมูลหลายๆตัวอยู่ด้วยกันเป็นลิสต์หรืออาเรย์ (รวมทั้งพวกหลายมิติแบบเวคเตอร์, แมตริกซ์, และอื่นๆ) เชิญศึกษาวิธีใช้ได้ที่เว็บนี้ครับ
ถ้าโปรแกรมเราไม่ได้เขียนให้ใช้ Numpy แต่เราอยากให้มันวิ่งเร็วขึ้น โดยไม่อยากดัดแปลงโปรแกรม เราสามารถลองใช้ PyPy มาเรียกโปรแกรมของเรา เมื่อเราติดตั้ง PyPy แล้วเราสามารถเรียกโปรแกรมของเราชื่อ my_program.py ด้วยคำสั่ง pypy my_program.py หรือ pypy3 my_program.py แทน python my_program.py ได้เลย ส่วนใหญ่โปรแกรมจะเร็วขึ้นหลายเท่าเหมือนกัน
อีกวิธีง่ายๆที่ควรลองคือใช้ Numba กับโปรแกรมของเราโดยใส่โค้ดเพิ่มเข้าไปไม่กี่บรรทัดแล้วโปรแกรมก็มักจะวิ่งเร็วขึ้นหลายๆเท่า
ยกตัวอย่างจากตอนที่ 1 และ 2 คือโปรแกรมทดลองแบ่งเส้นตรงเป็นสามส่วนแล้วดูว่าโอกาสที่ทั้งสามส่วนประกอบเป็นสามเหลี่ยมได้พอดีเป็นเท่าไร ถ้าเขียนด้วยไพธอนปกติก็มีหน้าตาแบบนี้:
import random def prob_triangle(ntrials): """ ประมาณความน่าจะเป็นที่เส้นตรงที่ถูกแบ่งเป็นสามส่วนแบบสุ่มๆ จะสามารถประกอบเป็นสามเหลี่ยมได้ ทำการทดลอง ntrials ครั้ง """ # ntriangles เก็บจำนวนครั้งที่ประกอบเป็นสามเหลี่ยมได้ ntriangles = 0 # ทำการทดลองแบ่งเส้นตรงความยาว 1 หน่วยเป็นสามส่วน # ทั้งหมดntrials ครั้ง for n in range(ntrials): # x, y คือตำแหน่งที่เราสุ่มตัดเส้นตรงสองตำแหน่ง # ทำให้แบ่งเส้นตรงเป็นสามส่วน # เราจะเรียงตำแหน่งให้ x <= y # ถ้าไม่เป็นอย่างนั้นเราจะสลับ x และy x = random.random() y = random.random() if x > y: x, y = y, x # เมื่อเราสุ่ม x, y มาตัดเส้นตรงได้แล้ว # เราจะมีเส้นตรงสามชิ้นยาว x, y-x, และ 1-y # เราจะเรียกชิ้นที่ยาวที่สุดว่า longest longest = max(x, y-x, 1-y) # ถ้าชิ้นที่ยาวที่สุดมีความยาวไม่เกิน 1/2 ของ # ความยาวเส้นตรงดั้งเดิม เราจะสามารถเอา # ทั้งสามชิ้นมาประกอบเป็นสามเหลี่ยมได้ # และเราก็จะเพิ่มจำนวนครั้งที่ประกอบเป็นสามเหลี่ยมสำเร็จ if longest < 0.5: ntriangles += 1 # ประมาณความน่าจะเป็นที่เป็นสามเหลี่ยมสำเร็จ # เท่ากับ (จำนวนสามเหลี่ยม)/(จำนวนครั้งที่ทดลอง) return ntriangles/ntrials
ลองจับเวลาให้ทดลองกับเส้นตรง 10 ล้านเส้นใช้เวลา 3.67 วินาที:
%time prob_triangle(10_000_000)
CPU times: user 3.66 s, sys: 8.11 ms, total: 3.67 s Wall time: 3.67 s 0.2499737
เราใช้ Numba โดยพิมพ์เพิ่ม 2 บรรทัดดังนี้ (บรรทัดที่ 1 และ 4)
from numba import njit #เรียกใช้ฟังก์ชั่นต่างๆใน numba import random @njit() #เพิ่มบรรทัดนี้เหนือฟังก์ชั่นที่เราเขียนไว้ มักจะทำงานได้เร็วขึ้น def prob_triangle_numba(ntrials): """ ประมาณความน่าจะเป็นที่เส้นตรงที่ถูกแบ่งเป็นสามส่วนแบบสุ่มๆ จะสามารถประกอบเป็นสามเหลี่ยมได้ ทำการทดลอง ntrials ครั้ง """ # ntriangles เก็บจำนวนครั้งที่ประกอบเป็นสามเหลี่ยมได้ ntriangles = 0 # ทำการทดลองแบ่งเส้นตรงความยาว 1 หน่วยเป็นสามส่วน # ทั้งหมดntrials ครั้ง for n in range(ntrials): # x, y คือตำแหน่งที่เราสุ่มตัดเส้นตรงสองตำแหน่ง # ทำให้แบ่งเส้นตรงเป็นสามส่วน # เราจะเรียงตำแหน่งให้ x <= y # ถ้าไม่เป็นอย่างนั้นเราจะสลับ x และy x = random.random() y = random.random() if x > y: x, y = y, x # เมื่อเราสุ่ม x, y มาตัดเส้นตรงได้แล้ว # เราจะมีเส้นตรงสามชิ้นยาว x, y-x, และ 1-y # เราจะเรียกชิ้นที่ยาวที่สุดว่า longest longest = max(x, y-x, 1-y) # ถ้าชิ้นที่ยาวที่สุดมีความยาวไม่เกิน 1/2 ของ # ความยาวเส้นตรงดั้งเดิม เราจะสามารถเอา # ทั้งสามชิ้นมาประกอบเป็นสามเหลี่ยมได้ # และเราก็จะเพิ่มจำนวนครั้งที่ประกอบเป็นสามเหลี่ยมสำเร็จ if longest < 0.5: ntriangles += 1 # ประมาณความน่าจะเป็นที่เป็นสามเหลี่ยมสำเร็จ # เท่ากับ (จำนวนสามเหลี่ยม)/(จำนวนครั้งที่ทดลอง) return ntriangles/ntrials
เมื่อเราจับเวลาจะพบว่าฟังก์ชั่นแบบใช้ Numba ของเราเร็วขึ้นมาก ใช้เวลาเพียง 0.149 วินาที แทนที่จะเป็น 3.67 วินาที หรือเร็วเป็น 25 เท่า
%time prob_triangle_numba(10_000_000)
CPU times: user 148 ms, sys: 798 µs, total: 149 ms Wall time: 149 ms 0.2499591
ถ้าเราเรียกฟังก์ชั่นเราครั้งเดียว ผลต่างระหว่าง 3 วินาที กับ 0.15 วินาทีจะดูไม่มาก แต่เมื่อเราให้คอมพิวเตอร์เรียกฟังก์ชั่นนั้นซ้ำๆกันเป็นพันครั้ง (เช่นเมื่อเราต้องการวาดฮิสโตแกรม) เวลาที่ใช้ก็จะต่างกันมากแบบ 1 ชั่วโมง vs. 2.5 นาที ดังนั้นเราควรรู้จักเทคนิคพวกนี้เมื่อสั่งให้คอมพิวเตอร์ทำงานหนักๆแทนเรา อาจจะประหยัดเวลาได้มาก
ถ้าคอมพิวเตอร์เรามีหลายๆคอร์ซีพียู (ซึ่งคอมปัจจุบันมักจะมีอย่างน้อยสองคอร์ขึ้นไปอยู่แล้ว) เราอาจขอให้ Numba พยายามใช้คอร์หลายๆคอร์ช่วยคำนวณด้วยคำสั่ง njit(parallel=True) และใช้ prange แทน range ในการคำนวณแบบนี้ (สังเกตบรรทัดที่ 1, 4, และ 23) เมื่อจับเวลาก็พบว่าเร็วขึ้นประมาณ 6 เท่าสำหรับซีพียูแบบ 8 คอร์:
from numba import njit, prange #เรียกใช้ฟังก์ชั่นต่างๆใน numba import random @njit(parallel=True) #เพิ่มบรรทัดนี้เหนือฟังก์ชั่นที่เราเขียนไว้ ฟังก์ชั่นเรามักจะทำงานได้เร็วขึ้น def prob_triangle_numba_parallel(ntrials): """ ประมาณความน่าจะเป็นที่เส้นตรงที่ถูกแบ่งเป็นสามส่วนแบบสุ่มๆ จะสามารถประกอบเป็นสามเหลี่ยมได้ ทำการทดลอง ntrials ครั้ง """ # ntriangles เก็บจำนวนครั้งที่ประกอบเป็นสามเหลี่ยมได้ ntriangles = 0 # ทำการทดลองแบ่งเส้นตรงความยาว 1 หน่วยเป็นสามส่วน # ทั้งหมดntrials ครั้ง # มีการใช้ prange แทน range เพื่อทำงานแบบพร้อมๆกัน # ตามจำนวนคอร์ใน CPU #ใช้ prange แทน range for n in prange(ntrials): # x, y คือตำแหน่งที่เราสุ่มตัดเส้นตรงสองตำแหน่ง # ทำให้แบ่งเส้นตรงเป็นสามส่วน # เราจะเรียงตำแหน่งให้ x <= y # ถ้าไม่เป็นอย่างนั้นเราจะสลับ x และy x = random.random() y = random.random() if x > y: x, y = y, x # เมื่อเราสุ่ม x, y มาตัดเส้นตรงได้แล้ว # เราจะมีเส้นตรงสามชิ้นยาว x, y-x, และ 1-y # เราจะเรียกชิ้นที่ยาวที่สุดว่า longest longest = max(x, y-x, 1-y) # ถ้าชิ้นที่ยาวที่สุดมีความยาวไม่เกิน 1/2 ของ # ความยาวเส้นตรงดั้งเดิม เราจะสามารถเอา # ทั้งสามชิ้นมาประกอบเป็นสามเหลี่ยมได้ # และเราก็จะเพิ่มจำนวนครั้งที่ประกอบเป็นสามเหลี่ยมสำเร็จ if longest < 0.5: ntriangles += 1 # ประมาณความน่าจะเป็นที่เป็นสามเหลี่ยมสำเร็จ # เท่ากับ (จำนวนสามเหลี่ยม)/(จำนวนครั้งที่ทดลอง) return ntriangles/ntrials
ถ้าจะใช้ parallel และ prange ควรอ่านเอกสารคู่มือเรื่องนี้ของ Numba ก่อนนะครับว่ามีข้อควรระวังอะไรบ้าง
หวังว่าผู้อ่านจะได้ไอเดียหรือประโยชน์บ้าง ถ้ามีคำแนะนำหรือข้อสงสัยส่งข้อความอินบ๊อกซ์ไปที่เพจวิทย์พ่อโก้บนเฟซบุ๊คได้ครับ: https://www.facebook.com/witpokosci/
(ตอนที่แล้วอยู่ที่ https://witpoko.com/?p=7485)
One thought on “คอมขยายสมอง (3): วิธีทำให้ไพธอนเร็วขึ้นหลายๆเท่า”