Python 3.13 Free Threading Test
หลังจาก Python 3.13 ได้ออกตัวจริงมาสักพัก และมีความสนใจที่จะลองใช้งาน Free Threading ที่เพิ่มมาใหม่ เท่าที่ทราบตัวติดตั้งบน Windows มีตัวเลือกให้เลือก แต่สำหรับ Linux และ macOS ต้องติดตั้งเอง เพราะ binary ที่มากับตัวติดตั้งไม่มีเปิดใช้งาน Free Threading มาให้ ก็มีทางเลือกสองทางคือ Build จาก source หรือใช้ตัวจัดการ package อื่น ๆ ที่มี Free Threading มาให้
แนะนำ uv
ก่อนจะคุยเรื่อง Python 3.13 Free Threading มาทำความรู้จัก uv กันสักนิด เอาแบบคร่าว ๆ uv เป็นเครื่องมือจัดการ package และ project สำหรับ Python ที่ทรงพลังมาก ถ้ามองในแง่ความเร็วในการติดตั้ง package เทียบกับ pip ถ้าใครที่ยังไม่รู้จัก แต่เคยใช้เครื่องมือตัวอื่นเช่น Poetry, Hatch, PDM หรือ Rye ก็จะทำงานในลักษณะคล้าย ๆ กัน คือ
- Package management
- Environment management
- Package development
- Python version management
โดยตัว uv เองเป็นน้องใหม่ในวงการและถือว่าอยู่ในระหว่างการพัฒนา แต่ก็จี๊ดจ๊าดมาก ๆ ในเรื่องความเร็วและความสามารถ uv พัฒนาด้วยภาษา Rust การันตีได้ว่าเร็วถึงใจแน่นอน เอาเป็นว่าแนะนำคร่าว ๆ แค่นี้ก่อน ใครสนใจลองอ่านต่อได้ที่ uv document
การติดตั้ง uv
การติดตั้ง uv มีหลายวิธีด้วยกัน ขึ้นกับสถานการณ์การใช้งาน จะติดตั้งแบบ standalone หรือติดตั้งเป็น package ผ่าน pip ก็ได้ หรือจะใช้งานผ่าน Docker ก็ได้ แต่ในที่นี้จะใช้วิธีติดตั้งผ่าน script ติดตั้งของ uv โดยใช้คำสั่ง
สำหรับ Linux และ macOS
curl -LsSf https://astral.sh/uv/install.sh | sh
สำหรับ Windows
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
แค่นี้ก็เรียบร้อยแล้ว สามารถใช้งาน uv ได้เลย
การใช้งาน uv เบื้องต้น
เราจะใช้ uv แทน pip หรือจะจัดการ environment ก็ได้ พิมพ์ยาวกว่าเดิมหน่อยแต่คุ้มค่ามาก ๆ และเร็วกว่าเดิมด้วย
การสร้าง environment ใหม่
ตัวอย่างการสร้าง environment ใหม่ เช่น สร้าง environment ชื่อ .uvenv ใหม่ โดยใช้คำสั่ง
uv venv .uvenv
เมื่อสร้าง environment แล้วเราสามารถเข้าไปใช้งานได้โดยใช้คำสั่งเหมือนกับการใช้งาน venv ของ Python คือ
source .uvenv/bin/activate
และเมื่อเราต้องการออกจาก environment ก็ใช้คำสั่ง
deactivate
และถ้าเราอยากจะระบุ Python version ที่เราต้องการใช้งานก็ใช้คำสั่ง
uv venv .uvenv --python=3.13
หรือ
uv venv .uvenv -p3.13t
หลังจากที่เรา source environment แล้วเราสามารถตรวจสอบ Python path และ Python version ที่เราใช้งานได้โดยใช้คำสั่ง
ตรวจสอบ Python path
which python
ตรวจสอบ Python version
python -V
หรือถ้าจะดูข้อมูลที่เยอะขึ้นก็ใช้คำสั่ง
python -VV
การติดตั้ง package ใน environment
เมื่อเราสร้าง environment แล้วเราสามารถติดตั้ง package ได้โดยใช้คำสั่งที่คล้าย ๆ กับ pip หรือมันก็คือ pip แต่เป็นของ uv อาจจะมีคำสั่งไม่เท่ากันทั้งหมด แต่สามารถใช้งานได้เหมือนกัน โดยใช้คำสั่ง
uv pip install numpy
ถ้ามีข้อความ warning ขึ้นมาเกี่ยวกับการ link file เราสามารถลองใช้คำสั่งนี้เพื่อปิด warning ได้
uv pip install numpy --link-mode=copy
จบตัวอย่างการใช้งาน uv แบบทดแทน pip และ venv แบบง่าย ๆ แล้ว หากต้องการรู้เพิ่มเติมสามารถอ่านได้ที่ uv document
การใช้ uv จัดการ Project อย่างง่าย
ก่อนหน้านี้เราได้ทดลองใช้งาน uv ในการจัดการ environment และ package แล้ว แต่ถ้าเราต้องการจัดการ project อย่างง่าย ๆ ก็สามารถใช้ uv ได้เช่นกัน โดยเราสามารถสร้าง project ใหม่ได้โดยใช้คำสั่ง
uv init
จะเป็นการสร้าง project ใหม่สำหรับ python application โดย uv จะสร้างไฟล์ pyproject.toml
, README.md
และ hello.py
ให้
หรือถ้าเราต้องการปรับแต่ง project ตอน init เพิ่มเติม เช่น ชื่อ project หรือไม่ต้องสร้างไฟล์ README.md
หรือกำหนด Python version ที่เราต้องการใช้งาน ก็สามารถใช้คำสั่ง
uv init --name=project_name --no-readme --python=3.13
การเพิ่ม package ใน project
เมื่อเราสร้าง project แล้วเราสามารถเพิ่ม package ใน project ได้เหมือนกับเจ้าอื่น ๆ ซึ่งจะใช้คำสั่ง
uv add numpy
เช่นกันถ้ามี warning ขึ้นมาเกี่ยวกับการ link file เราสามารถลองใช้คำสั่งนี้เพื่อปิด warning ได้
uv add numpy --link-mode=copy
การลบ package ใน project
เราสามารถลบ package ใน project ได้โดยใช้คำสั่ง
uv remove numpy
การ sync package ใน project
ในกรณีที่เราสร้าง project และมีการ clone ไปยังเครื่องอื่น โดยไม่มี package ที่เราต้องการใน project นั้น โดยเราสามารถ sync package ที่เราต้องการใน project นั้น หรือการ sync เพื่อ update โดยใช้คำสั่ง
uv sync
หรือถ้ามี uv.lock ใน project แล้วเราสามารถ sync package ที่เราต้องการใน project นั้นโดยยังคง package ที่อยู่ใน uv.lock โดยใช้คำสั่ง
uv sync --frozen
หรือจะ update package ที่เราต้องการใน project ก็ใช้คำสั่ง
uv sync --upgrade
ข้างบนเป็นตัวอย่างการใช้งาน uv ในการจัดการ project อย่างง่าย ๆ และสามารถอ่านเพิ่มเติมได้ที่ uv document
Run script python ด้วย uv
โดยปกติเราสามารถใช้ uv สลับตัว Python version ต่าง ๆ โดยไม่ต้องติดตั้ง Python เองซึ่งมีความง่ายมาก ๆ เพราะ uv จะ download Python version ที่เราต้องการใช้งานให้เอง
วิธี run script python โดยใช้ uv เช่น
uv run script.py
uv ก็จะเลือก Python version ที่เหมาะสมมา run script ให้เราโดยอัตโนมัติ แต่ถ้าเราต้องการเลือก Python version ที่เราต้องการใช้งานเองก็สามารถใช้คำสั่ง
uv run -p3.12 script.py
หรือเราจะเลือก python version ตั้งแต่ตอน init project ตามที่แนะนำไว้ข้างบนก็ได้ ส่วนวิธีระบุตอน run script uv ก็จะหา python version ที่เราต้องการใช้งานให้เอง ถ้ามีอยู่แล้วในระบบก็ใช้ได้เลย ถ้าไม่มีก็จะไป download มาให้เอง ถ้าเราอยากรู้ว่า uv มี python version อะไรให้เราใช้งานได้บ้างก็ใช้คำสั่ง
uv python list
แต่ทั้งนี้ถ้าเราใช้วิธี init project ไว้ต้องตรวจสอบข้อจำกัดของ python version ใน pyproject.toml และ .python-version ด้วย เช่นถ้าระบุไว้ในไฟล์ข้างต้นเป็น 3.13 เราจะใช้ -p3.12 ก็ไม่ได้นะ
ลองใช้งาน Free Threading ใน Python 3.13
คราวนี้เรามาลองใช้ Free Threading ใน Python 3.13 กัน
ก่อนอื่นผมให้ ChatGPT สร้าง script สำหรับตรวจสอบ Python version, platform และ GIL status ให้ดังนี้
ไฟล์ pyinfo.py
import sys
import platform
# Check Python version
python_version = sys.version
# Check platform
platform_info = platform.platform()
# Check GIL status
# ใน Python 3.13 สามารถตรวจสอบ GIL ได้ผ่าน sys.get_asyncgen_hooks()
def check_gil_status():
try:
return "Enabled" if sys.flags.gil else "Disabled"
except AttributeError:
return "Cannot determine - Python version may not support GIL status check"
print(f"Python Version: {python_version}")
print(f"Platform: {platform_info}")
print(f"GIL Status: {check_gil_status()}")
run โดยใช้ Python version 3.13 ปกติ
uv run -p3.13 pyinfo.py
เมื่อ run คำสั่งข้างต้นถ้าระบบยังไม่มี python 3.13 อยู่ uv จะ download มาให้เอง และ run script ให้เรา และผลลัพธ์ที่ได้ประมาณนี้
Python Version: 3.13.0 (main, Oct 8 2024, 01:04:00) [Clang 18.1.8 ]
Platform: Linux-5.15.0-122-generic-x86_64-with-glibc2.36
GIL Status: Enabled
เราสามารถเห็นได้ว่า GIL status ถูกเปิดใช้งานอย่างถูกต้อง
ถ้าเราลองสั่งปิด GIL ด้วยคำสั่ง
PYTHON_GIL=0 uv run -p3.13 pyinfo.py
ผลลัพธ์ที่ได้จะเป็น
Fatal Python error: config_read_gil: Disabling the GIL is not supported by this build
Python runtime state: preinitialized
เราจะเห็นได้ว่าเราไม่สามารถปิด GIL ได้เนื่องจากไม่ได้รองรับใน build นี้ซึ่งเป็นปกติสำหรับ official build ของ Python 3.13
แต่ถ้าเราจะใช้งาน Free Threading สามารถระบุ Python version เป็น 3.13t (มี t ต่อท้าย) ได้โดยใช้คำสั่ง
uv run -p3.13t pyinfo.py
ผลลัพธ์ที่ได้จะเป็น
Python Version: 3.13.0 experimental free-threading build (main, Oct 8 2024, 01:06:48) [Clang 18.1.8 ]
Platform: Linux-5.15.0-122-generic-x86_64-with-glibc2.36
GIL Status: Disabled
เราจะเห็นได้ว่า GIL status ถูกปิดใช้งาน และเราสามารถใช้งาน Free Threading ได้แล้ว
ถ้าเราลองสั่งเปิด GIL จาก 3.13t สามารถทำได้โดยใช้คำสั่ง
PYTHON_GIL=1 uv run -p3.13t pyinfo.py
ผลลัพธ์ที่ได้จะเป็น
Python Version: 3.13.0 experimental free-threading build (main, Oct 8 2024, 01:06:48) [Clang 18.1.8 ]
Platform: Linux-5.15.0-122-generic-x86_64-with-glibc2.36
GIL Status: Enabled
เปรียบเทียบผลลัพธ์ระหว่าง Python 3.13 และ Python 3.13t
ผมลองให้ ChatGPT สร้าง script สำหรับทดสอบการใช้งาน Free Threading ใน Python 3.13 และ Python 3.13t โดยการ run 3 แบบ คือ
- Sequential
- Thread
- Multiprocessing
ไฟล์ threading_test.py
import time
import multiprocessing
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
# ฟังก์ชันสำหรับทดสอบ - คำนวณค่า fibonacci
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
# ฟังก์ชันสำหรับทดสอบ CPU-intensive task
def heavy_computation(x):
return sum(fibonacci(n) for n in range(x))
def run_sequential(numbers):
start_time = time.time()
results = [heavy_computation(n) for n in numbers]
end_time = time.time()
return end_time - start_time
def run_threaded(numbers):
start_time = time.time()
with ThreadPoolExecutor(max_workers=multiprocessing.cpu_count()) as executor:
results = list(executor.map(heavy_computation, numbers))
end_time = time.time()
return end_time - start_time
def run_multiprocess(numbers):
start_time = time.time()
with ProcessPoolExecutor(max_workers=multiprocessing.cpu_count()) as executor:
results = list(executor.map(heavy_computation, numbers))
end_time = time.time()
return end_time - start_time
def main():
# จำนวน CPU cores
num_cores = multiprocessing.cpu_count()
print(f"\nRunning on system with {num_cores} CPU cores")
# สร้างข้อมูลทดสอบ
test_numbers = [25, 26, 27, 28] # ปรับขนาดตามต้องการ
print("\nStarting performance comparison...")
print("Task: Computing Fibonacci sequences")
print(f"Input numbers: {test_numbers}")
# ทดสอบแบบ Sequential
sequential_time = run_sequential(test_numbers)
print(f"\n1. Sequential Execution Time: {sequential_time:.2f} seconds")
# ทดสอบแบบ Threaded
threaded_time = run_threaded(test_numbers)
print(f"2. Threaded Execution Time: {threaded_time:.2f} seconds")
print(f" Thread Speedup: {sequential_time/threaded_time:.2f}x")
# ทดสอบแบบ Multiprocess
multiprocess_time = run_multiprocess(test_numbers)
print(f"3. Multiprocess Execution Time: {multiprocess_time:.2f} seconds")
print(f" Multiprocess Speedup: {sequential_time/multiprocess_time:.2f}x")
print("\nPerformance Summary:")
print("-" * 50)
print(f"Sequential: 100% (baseline)")
print(f"Threaded: {(threaded_time/sequential_time)*100:.1f}% of sequential time")
print(f"Multiprocess: {(multiprocess_time/sequential_time)*100:.1f}% of sequential time")
if __name__ == '__main__':
main()
เราสามารถ run script ด้วย Python version 3.13 ได้โดยใช้คำสั่ง
uv run -p3.13 threading_test.py
ผลลัพธ์ที่ได้จะประมาณนี้
Python Version: 3.13.0 (main, Oct 8 2024, 01:04:00) [Clang 18.1.8 ]
Platform: Linux-5.15.0-122-generic-x86_64-with-glibc2.36
GIL Status: Enabled
Running on system with 16 CPU cores
Starting performance comparison...
Task: Computing Fibonacci sequences
Input numbers: [25, 26, 27, 28]
1. Sequential Execution Time: 0.12 seconds
2. Threaded Execution Time: 0.12 seconds
Thread Speedup: 0.97x
3. Multiprocess Execution Time: 0.06 seconds
Multiprocess Speedup: 1.84x
Performance Summary:
--------------------------------------------------
Sequential: 100% (baseline)
Threaded: 103.2% of sequential time
Multiprocess: 54.2% of sequential time
เราจะเห็นได้ว่า Threaded ไม่ได้เพิ่มความเร็วในการทำงานเท่าไหร่ แต่ Multiprocess ได้เพิ่มความเร็วในการทำงานมากเกือบ 2 เท่า ซึ่งเป็นเรื่องปกติสำหรับ Python ที่มี GIL เปิดใช้งานอยู่
แต่ถ้าเรา run script ด้วย Python version 3.13t โดยใช้คำสั่ง
uv run -p3.13t threading_test.py
ผลลัพธ์ที่ได้จะประมาณนี้
Python Version: 3.13.0 experimental free-threading build (main, Oct 8 2024, 01:06:48) [Clang 18.1.8 ]
Platform: Linux-5.15.0-122-generic-x86_64-with-glibc2.36
GIL Status: Disabled
Running on system with 16 CPU cores
Starting performance comparison...
Task: Computing Fibonacci sequences
Input numbers: [25, 26, 27, 28]
1. Sequential Execution Time: 0.22 seconds
2. Threaded Execution Time: 0.09 seconds
Thread Speedup: 2.46x
3. Multiprocess Execution Time: 0.10 seconds
Multiprocess Speedup: 2.22x
Performance Summary:
--------------------------------------------------
Sequential: 100% (baseline)
Threaded: 40.6% of sequential time
Multiprocess: 44.9% of sequential time
เราจะเห็นได้ว่า Threaded ได้เพิ่มความเร็วในการทำงานขึ้นมามากกว่า 2 เท่า แต่ถ้าเทียบกับ Multiprocess ก็ยังไม่ได้เร็วเท่าไหร่ แต่เบื้องหลังการงานของ Free Threading จะใช้งานได้เต็มที่บน CPU cores ทั้งหมดโดย overhead จะน้อยกว่า Multiprocess
สรุป
ผมได้นำเสนอการใช้งาน uv ฉบับย่อ ๆ เพื่อเป็นแนวทางสำหรับใครที่หาตัวจัดการ project python ที่ง่ายและรวดเร็ว และได้ทดลองใช้งาน Free Threading ใน Python 3.13 ซึ่งเป็น feature ใหม่แต่ยังไม่ถูกเปิดใช้งานอย่างเป็นทางการ ส่วนข้อมูลที่ผมให้อาจจะมีข้อผิดพลาดไม่ตรงตามหลักวิชาการบ้างก็ต้องขออภัยไว้ ณ ที่นี้ และยินดีน้อมรับคำแนะนำจากทุก ๆ ท่าน`