การเขียน Code Python ให้มีประสิทธิภาพ

การเขียน Code Python ให้มีประสิทธิภาพ

Python Language เป็นภาษาสมัยใหม่ที่สามารถเข้าใจได้ไม่ยากนัก มีรูปแบบการเขียนที่คล้ายภาษามนุษย์ จึงทำให้ปัจจุบันภาษา Python ค่อนข้างเป็นที่นิยมสำหรับการเริ่มต้นเขียน Program แต่สิ่งหนึ่งที่เป็นสิ่งสำคัญและ programmer ส่วนใหญ่อาจจะยังไม่ค่อยสนใจเท่าที่ควร นั่นคือ การเขียน code ให้มีประสิทธิภาพ (Performance Turning) สาเหตุอาจจะเป็นเพราะการทดสอบระบบที่มีข้อมูลน้อย หรือไม่ซับซ้อนมากนัก ทำให้ยังไม่เห็นความสำคัญมากเท่าที่ควร
หากข้อมูลเริ่มมีขนาดที่ใหญ่ขึ้น เวลาการทำงานของระบบก็จะยาวนานตามไปด้วย ทำให้ผลกระทบถูกส่งต่อเป็นทอดๆ เพื่อแก้ไขปัญหานี้ควรจะเขียน code ให้มีประสิทธิภาพตั้งแต่ต้นจะช่วยเราประหยัดเวลาได้เยอะเลยทีเดียว
การวน Loops
สิ่งที่แทบทุกคนน่าจะรู้จัก ใช้กันเยอะที่สุดและมีปัญหาความเร็วตรงส่วนนี้มากที่สุด คือการวน for loop แต่การวน for loop บางครั้งเราสามารถใช้คำสั่งอื่นเพื่อให้มีประสิทธิภาพได้
Straightforward
ส่วนใหญ่แล้วน่าจะคุ้นชินกับการใช้ for loop ด้วยวิธีนี้ เพราะเป็นวิธีที่ง่ายที่สุด
def test_straightforward(oldlist):
    newlist = []
    for word in oldlist:
        newlist.append(word.upper())

List comprehensions

วิธีนี้จะคล้ายกับก่อนหน้านี้ เพียงแต่กำหนดให้เป็น list อยู่แล้วจึงไม่ต้องใช้ append
def test_list_comprehensions(oldlist):
    newlist = [word.upper() for word in oldlist]
Generator Expressions
วิธีนี้เขียนคล้ายกับ List comprehensions แต่ return ค่าเป็น generator object

def test_list_comprehensions(oldlist):
    newlist = (word.upper() for word in oldlist)

Map
วิธีนี้จะใช้ได้เมื่อเราอยากให้ทุกค่าถูกเปลี่ยนด้วยหลักเดียวกัน เช่น แปลง string ให้เป็นตัวพิมพ์ใหญ่ทุกตัว

def test_map(oldlist):
    newlist = map(str.upper, oldlist)

ทดสอบเวลาการทำงาน

จะเห็นว่า function map จะทำงานได้เร็วที่สุด แต่ทั้งนี้ทั้งนั้น ขึ้นอยู่กับการใช้งานจริงในแต่ละงานมากกว่า เช่น map ถึงแม้จะมีความเร็วมาก แต่ก็เหมาะกับการใช้งานที่จำกัด และประยุกต์ใช้ค่อนข้างยากกว่า function อื่น หรือ Straightforward ที่ช้าที่สุดแต่มีความยืดหยุ่นมาก จึงเหมาะสำหรับการทำบางอย่างให้แต่ละค่าที่ไม่เหมือนกัน

หลีกเลี่ยงการใช้จุดใน for loop
การวน for loop หากเราใช้ function เดียวกันซ้ำๆ ไม่จำเป็นที่จะต้องเขียน function ใน loops เพื่อลดการทำงานของระบบได้ อย่างเช่น ตัวอย่าง Straightforward จะเห็นว่ามีการใช้ .append ซ้ำกันทุกครั้ง หากเป็นปกติแล้วระบบจะต้องเรียก append ทุก loops ยิ่งมีเยอะก็ยิ่งช้า ฉะนั้นวิธีแก้ไขคือเรียกใช้ append ไว้ตั้งแต่ก่อนวน loops และ upper ที่มีการทำซ้ำทุก loops

def test_straightforward(oldlist):
    newlist = []
    upper = str.upper
    append = newlist.append
    for word in oldlist:
        append(upper())

ทดสอบเวลาการทำงาน

จะสังเกตว่าการใช้ dot ใน for loops จะไม่ค่อยเห็นผล หากข้อมูลไม่ได้มีการ loops ที่เยอะ ฉะนั้นขึ้นอยู่กับการใช้งานของแต่ละคนมากกว่า


การ loops นอก function หลัก
การเรียก function ใน loops เป็นสิ่งหนึ่งที่มีผลกับความเร็ว

def new_function(word):
    return word.upper()

def main(oldlist):
    newlist = []
    for word in oldlist:
        newlist.append(new_function(word))

จากตัวอย่างด้านบนระบบจะต้อง loops แต่ละครั้งและเรียก function new_function ทุก loops ทำให้ 1 loop เรียก 1 function ถ้ามี 100 loops ก็ต้องเรียก 100 function วิธีแก้คือการ สร้าง function ใหม่และส่งค่าที่ต้องการวน loops เข้าไปด้วย เพื่อวน loops ใน function ใหม่ทีเดียว

def new_function(oldlist):
    newlist = []
    for word in oldlist:
        newlist.append(word.upper())
    return newlist

def main(oldlist):
    newlist = new_function(oldlist)

ทดสอบเวลาการทำงาน

การใช้ continue ใน loops เพื่อลดการทำงาน
ในการวน loops บางค่าเราอาจจะไม่จำเป็นต้องทำ แต่เพราะอยู่ใน loops เลยหลีกเลี่ยงไม่ได้ ซึ่งวิธีแก้ไขคือการใช้ continue เพื่อข้ามการทำงานนี้ไป ก่อนจะใช้ continue จะต้องมั่นใจก่อนว่าค่าข้างใน loops เราจำเป็นต้องใช้หรือไม่ เพราะการใช้ continue ไม่ถูกจังหวะอาจจะทำให้ค่าที่เราต้องการเปลี่ยนแปลงไปได้

ตัวอย่าง
การใช้ continue ก่อนที่จะทำ function จะทำให้ x มีค่าเท่ากับ จำนวนใน wdict ที่มี word

def loops_continue_before(words):
    wdict = {}
    x = 0
    for word in words:
        if word not in wdict:
            continue
        x += 1

การใช้ continue หลังจากทำ function จะทำให้ x มีค่าเท่ากับจำนวนใน words

def loops_continue_after(words):
    wdict = {}
    x = 0
    for word in words:
        x += 1
        if word not in wdict:
            continue

ทดสอบเวลาการทำงาน

ทั้งนี้ทั้งนั้นขึ้นอยู่กับการใช้งานของแต่ละคนว่าควรจะใช้ continue (หรือ return, break) ในจังหวะไหน แต่ควรใช้ให้อยู่บนที่สุดเท่าที่จะทำได้ เพื่อลดการทำงาน function อื่นๆที่ไม่จำเป็น

การสร้างค่าโดยใช้ Dictionary แบบมีเงื่อนไข
เปรียบเทียบวิธีการเขียน loops สร้าง ค่าใน dictionary โดยมีเงื่อนไขตามที่เราต้องการ โดยจะมีวิธีการสร้างได้หลากหลายวิธี ซึ่งแต่ละวิธีจะได้ผลลัพธ์เหมือนกัน แต่ประสิทธิภาพแตกต่างกัน

การสร้างค่าโดยใช้ Condition

def create_dict_if(words):
    wdict = {}
    for word in words:
        if word not in wdict:
            wdict[word] = 0
        wdict[word] += 1

วิธีแรก การตรวจสอบเงื่อนไข หากไม่ตรงกันก็จะไม่ต้องทำ function ภายใน ซึ่งจะช่วยลดภาระในการทำงานบางส่วนที่ไม่จำเป็นได้

การสร้างค่าโดยใช้ Try Catch

def create_dict_try_catch(words):
    wdict = {}
    for word in words:
        try:
            wdict[word] += 1
        except KeyError:
            wdict[word] = 1

วิธีที่สอง การใช้ try catch ซึ่งระบบจะเริ่มต้นทำ try ก่อน แต่เมื่อทำแล้วเกิด error ตามที่เขียนไว้ใน except ระบบก็จะเปลี่ยนมาทำใน except แทน วิธีนี้จะเป็นการแก้ไข error แบบกว้างๆ

การสร้างค่าโดย get จากค่าเดิม

def create_dict_get(word):
    wdict = {}
    get = wdict.get
    for word in words:
        wdict[word] = get(word, 0) + 1

วิธีนี้จะเขียนค่อนข้างง่าย คือ การดึงค่าเดิมมาบวกกับค่าใหม่ ซึ่งหากค่าเดิมไม่มีค่าก็จะ get 0 มาแทน

การสร้างค่าโดยใช้ Defaultdict

from collections import defaultdict

def create_dict_defaultdict(word):
    wdict = defaultdict(int)
    for word in words:
        wdict[word] += 1

วิธีนี้จะใช้ package ของ python ในการช่วย lookup data ที่เราต้องการใน dict ก่อน ถ้าไม่มีก็จะ return 0 เก็บใน wdict แล้วใช้วิธีวน loops เพื่อเพิ่มค่าเข้าไปอีกครั้งหนึ่ง

ทดสอบเวลาการทำงาน

Try catch จะทำงานได้ช้าสุด เพราะว่า ต้องทำใน try ก่อน หากไม่สำเร็จก็จะต้องกลับมาทำ except อีกรอบหนึ่ง จึงเหมือนระบบทำงาน 2 ครั้ง แต่ถ้ากรณีที่ไม่ต้องเข้า except ก็จะทำงานได้เร็วขึ้น แต่ไม่เท่ากับการใช้ get

About the Author

Leave a Reply