มาทำให้ Python เร็วเท่า Go Lang กัน
หลายๆคนคงทราบกันดีอยู่แล้วว่า Python เป็น ภาษาโปรแกรมที่เขียนง่าย ตรงไปตงมา แต่ว่าความที่มันง่ายและตรงไปตรงมานั้น เลยเป็นดาบลองคม ทำให้ตัว Python เองนั้นมีความเร็วของตัวเองที่ต่ำกว่าภาษาอื่นๆ หลายเท่าตัว เช่น ทดสอบโครงสร้างของต้นไม้ (tree structure) ก็จะทำงานได้ช้ากว่าภาษาอื่นอย่าง GoLang
โค็ต Python ในการทำสอบนี้
def make_tree(d):
if d > 0:
d -= 1
return (make_tree(d), make_tree(d))
return (None, None)
def check_tree(node):
(l, r) = node
return 1 if l is None else 1 + check_tree(l) + check_tree(r)
def make_check(d, make=make_tree, check=check_tree):
return check(make(d))
def main(n, min_depth=4):
max_depth = max(min_depth + 2, n)
stretch_depth = max_depth + 1
print('stretch tree of depth {0}\t check: {1}'.format(
stretch_depth, make_check(stretch_depth)))
long_lived_tree = make_tree(max_depth)
mmd = max_depth + min_depth
for d in range(min_depth, stretch_depth, 2):
i = 2 ** (mmd - d)
cs = sum(make_check(d) for _ in range(i))
print('{0}\t trees of depth {1}\t check: {2}'.format(i, d, cs))
print('long lived tree of depth {0}\t check: {1}'.format(
max_depth, check_tree(long_lived_tree)))
import timeit
import platform
starttime = timeit.default_timer()
n = 18
minDepth = 4
print("Tree recursion ==> ",n)
print("Python Version ==> ",platform.python_version())
main(n,minDepth)
print("The time difference is :", timeit.default_timer() - starttime," seconds")
โค๊ต Golang ในการทดสอบนี้
package main
import (
"fmt"
"runtime"
"time"
)
type Node struct {
left, right *Node
}
func makeTree(d int) *Node {
if d > 0 {
d -= 1
return &Node{makeTree(d), makeTree(d)}
}
return nil
}
func checkTree(node *Node) int {
if node == nil {
return 1
}
return 1 + checkTree(node.left) + checkTree(node.right)
}
func makeCheck(d int) int {
return checkTree(makeTree(d))
}
func run(n, minDepth int) {
maxDepth := max(minDepth+2, n)
stretchDepth := maxDepth + 1
fmt.Printf("stretch tree of depth %d\t check: %d\n", stretchDepth, makeCheck(stretchDepth))
longLivedTree := makeTree(maxDepth)
mmd := maxDepth + minDepth
for d := minDepth; d <= stretchDepth; d += 2 {
i := 1 << (mmd - d)
cs := 0
for j := 0; j < i; j++ {
cs += makeCheck(d)
}
fmt.Printf("%d\t trees of depth %d\t check: %d\n", i, d, cs)
}
fmt.Printf("long lived tree of depth %d\t check: %d\n", maxDepth, checkTree(longLivedTree))
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
func main() {
starttime := time.Now()
n, minDepth := 18, 4
fmt.Printf("Tree recursion %d\n", n)
fmt.Printf("Go Version ==> %s\n", runtime.Version())
run(n, minDepth)
fmt.Println("The time difference is :", time.Since(starttime))
}
เป็นไปได้ไหม ที่เราจะทำให้ Python มาความเร็วใกล้เคียงกับ Golang คำตอบคือ เป็นไรได้ครับ
จะเห็นได้ว่า Golang 1.20.6 เร็วกว่าด้วยการทดสอบนี้
Golang (797 มิลลิวินาที)
Python3.11 (7.5 วินาที)
Python3.8 (12.8 วินาที)
ผู้เขียนขอแนะนำ PYPY https://www.pypy.org/ ชึ่งจะทำหน้าที่คล้ายกับ Python ทุกอย่างและยังรองรับ Package จาก pip install อีกด้วย และยังสามารถใช้งานร่วมกับ Framework อย่าง Fastapi ได้อีกด้วยครับ
มาลองดูความเร็วที่เปลี่ยนไป หลังจากได้ติดตั้ง PYPY ลงไปแทนที่ Python ซึ่งเป็น pypy3.10 และ 3.9 นะครับ เนื่องจาก ณ วันที่เขียนบทความนี้ pypy ยังไม่มี pypy3.11 นะครับ
จะเห็นได้ว่า Golang 1.20.6 เร็วกว่า หนิดหน่อย เพียง 90-100ms เท่านั้น
Golang (790 มิลลิวินาที)
pypy3.10 (873 มิลลิวินาที)
pypy3.9 (879 มิลลิวินาที)
นอกจาก pypy แล้วยังมี Cython อีกตัวนะครับ เราสามารถใช้ Cython ช่วยในการทำให้ Python เร็วขึ้นด้วยการแปลงเป็น Byte Code ก่อนก็ได้ครับ จะได้ความเร็วเพิ่มขึ้น 4-5 เท่าตัวเลย แต่ๆๆๆๆ Cython นั้นก็มีความยุ้งยากในการใช้งานค่อนข้างเยอะ เพราะไหนจะต้องแปลงโค๊ต เพื่อ Run คำสั่ง หรือบางงานก็ไม่เหมาะที่จะทำการแปลงโค๊ตตลอดเวลา หรือบางครั้งก็มีไฟล์เยอะไป การแปลงเลยทำได้ยาก
ใครที่ใช้ FastAPI ดู สามารถอ่านบทความนี้เพิ่มเติมได้ครับ