測試Python 3.12.10版本與目前使用的3.87版本 差異
參考以下文章
在 Python 3.8.6 vs 3.12.10 上跑同一組程式,比較不同類型任務的效能差異。
成果彙整

程式碼參考如下
🧮 數值運算密集 (CPU bound)
import time
def numeric_heavy(n=20_000_000):
s = 0
for i in range(n):
s += (i * i) ^ (i << 2) ^ (i >> 1)
return s
def benchmark(fn, *args, repeat=3):
times = []
for _ in range(repeat):
t0 = time.perf_counter()
fn(*args)
t1 = time.perf_counter()
times.append(t1 - t0)
print(f"{fn.__name__} -> {times}, avg={sum(times)/len(times):.4f}s")
if __name__ == "__main__":
benchmark(numeric_heavy, 20_000_000)
3.8.6

3.12.10

🔤 字串操作密集 (string processing)
import time
def string_heavy(n=500_000):
lst = []
for i in range(n):
s = str(i) * 3
lst.append(s[::-1]) # 字串翻轉
joined = ",".join(lst)
return len(joined)
def benchmark(fn, *args, repeat=3):
times = []
for _ in range(repeat):
t0 = time.perf_counter()
fn(*args)
t1 = time.perf_counter()
times.append(t1 - t0)
print(f"{fn.__name__} -> {times}, avg={sum(times)/len(times):.4f}s")
if __name__ == "__main__":
benchmark(string_heavy, 500_000)
3.8.6

3.12.10

📂 IO 密集 (檔案讀寫)
import time
import os
def io_heavy(filename="tmp_test.txt", n=1000, line_size=1000):
# 寫入
with open(filename, "w", encoding="utf-8") as f:
for i in range(n):
f.write("X" * line_size + "\n")
# 讀取
with open(filename, "r", encoding="utf-8") as f:
data = f.readlines()
os.remove(filename)
return len(data)
def benchmark(fn, *args, repeat=3):
times = []
for _ in range(repeat):
t0 = time.perf_counter()
fn(*args)
t1 = time.perf_counter()
times.append(t1 - t0)
print(f"{fn.__name__} -> {times}, avg={sum(times)/len(times):.4f}s")
if __name__ == "__main__":
benchmark(io_heavy, "tmp_test.txt", 2000, 2000)
3.8.6

3.12.10

純迴圈效能(for-loop + range)
這是最常見的 micro-benchmark,測試 Python 解譯器本身的迴圈執行速度。
import time
def loop_test(n=200_000_000):
s = 0
for i in range(n):
s += i
return s
if __name__ == "__main__":
t0 = time.perf_counter()
loop_test(200_000_000)
t1 = time.perf_counter()
print("Loop test:", t1 - t0, "s")
3.8.6

3.10.12

NumPy 與 OpenCV
- 自動產生接近2,0000,000(2e7)畫素的單通道灰階圖(或 RGB 可選)
- 針對 NumPy 做矩陣乘法(用 sub-block 或隨機矩陣)、元素級運算、FFT 等(以大陣列為基礎)
- 針對 OpenCV 做 GaussianBlur、Canny、Resize、findContours、connectedComponentsWithStats
- 每個操作做
repeat次,計算平均耗時並印出 - 接受命令列參數:
--pixels、--channels、--repeat
"""
benchmark_big_image.py
用途:針對大圖 (預設 20,000,000 pixels) 比較 NumPy / OpenCV 常見運算平均耗時。
範例:
python benchmark_big_image.py # 使用預設 (20M pixels, gray, repeat=10)
python benchmark_big_image.py --repeat 50 --channels 3
python benchmark_big_image.py --pixels 20000000 --channels 1 --repeat 100
"""
import time
import argparse
import math
import numpy as np
import cv2
import sys
def make_image(total_pixels=20_000_000, channels=1, dtype=np.uint8, seed=42):
"""產生接近 total_pixels 的影像 (channels=1 or 3).
回傳 numpy array, shape = (h, w, channels) 或 (h, w) if channels==1
"""
np.random.seed(seed)
# 找最接近方形的寬高
side = int(math.sqrt(total_pixels))
h = side
w = int(total_pixels / h)
# 若 channels == 1, 回傳 (h,w);否則 (h,w,channels)
if channels == 1:
img = np.random.randint(0, 256, size=(h, w), dtype=dtype)
else:
img = np.random.randint(0, 256, size=(h, w, channels), dtype=dtype)
actual_pixels = img.shape[0] * img.shape[1]
print(f"Created image shape={img.shape}, actual_pixels={actual_pixels:,}")
return img
def timeit(fn, repeat=10):
times = []
for _ in range(repeat):
t0 = time.perf_counter()
fn()
t1 = time.perf_counter()
times.append(t1 - t0)
avg = sum(times) / len(times)
return avg, times
# ---------- NumPy tests ----------
def numpy_big_tests(img, repeat=10):
print("\n=== NumPy tests ===")
# For heavy numeric linear algebra we avoid creating huge random matrices each repeat.
# We'll create once and reuse.
# For matrix-multiply, we need 2D float matrices. If img is huge (single channel), convert to float and use smaller square matrices for matmul to keep memory sane.
h, w = img.shape[:2]
# build moderately sized float matrices derived from image but not too huge:
mat_size = min(2000, int(math.sqrt(h * w / 10_000))) # heuristic to avoid insane matmul sizes
A = np.random.rand(mat_size, mat_size).astype(np.float64)
B = np.random.rand(mat_size, mat_size).astype(np.float64)
def matmul_op():
# measure only dot product
_ = A.dot(B)
return None
avg_matmul, times_matmul = timeit(matmul_op, repeat=repeat)
print(f"Matrix multiply (size={mat_size}x{mat_size}) avg: {avg_matmul:.6f} s over {repeat} runs")
def element_wise_op():
# element-wise operations on the full image (convert to float)
x = img.astype(np.float32)
y = x * 1.234 + (x ** 2) * 0.0001
# reduce
_ = y.mean()
return None
avg_elem, _ = timeit(element_wise_op, repeat=repeat)
print(f"Element-wise ops on full image avg: {avg_elem:.6f} s over {repeat} runs")
def fft_op():
# FFT on a smaller block to be realistic (full 20M FFT is huge); use a central crop
ch, cw = min(1024, h), min(1024, w)
crop = img[:ch, :cw].astype(np.float32)
_ = np.fft.fft2(crop)
return None
avg_fft, _ = timeit(fft_op, repeat=repeat)
print(f"FFT on {min(1024,h)}x{min(1024,w)} crop avg: {avg_fft:.6f} s over {repeat} runs")
return {
"matmul_avg": avg_matmul,
"elementwise_avg": avg_elem,
"fft_avg": avg_fft
}
# ---------- OpenCV tests ----------
def opencv_big_tests(img, repeat=10):
print("\n=== OpenCV tests ===")
# ensure gray image for some ops
if img.ndim == 3:
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
else:
gray = img
def gaussian_op():
_ = cv2.GaussianBlur(gray, (5, 5), 0)
return None
def canny_op():
_ = cv2.Canny(gray, 100, 200)
return None
def resize_op():
_ = cv2.resize(gray, (gray.shape[1] // 2, gray.shape[0] // 2))
return None
# prepare binary for contour/cc
_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
def findcontours_op():
# note: OpenCV's return signature depends on version; ignore results
_ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
return None
def connected_components_op():
_ = cv2.connectedComponentsWithStats(binary, connectivity=8)
return None
avg_gauss, _ = timeit(gaussian_op, repeat=repeat)
print(f"GaussianBlur avg: {avg_gauss:.6f} s over {repeat} runs")
avg_canny, _ = timeit(canny_op, repeat=repeat)
print(f"Canny avg: {avg_canny:.6f} s over {repeat} runs")
avg_resize, _ = timeit(resize_op, repeat=repeat)
print(f"Resize avg: {avg_resize:.6f} s over {repeat} runs")
avg_findcontours, _ = timeit(findcontours_op, repeat=repeat)
print(f"findContours avg: {avg_findcontours:.6f} s over {repeat} runs")
avg_conncomp, _ = timeit(connected_components_op, repeat=repeat)
print(f"connectedComponentsWithStats avg: {avg_conncomp:.6f} s over {repeat} runs")
return {
"gauss_avg": avg_gauss,
"canny_avg": avg_canny,
"resize_avg": avg_resize,
"findcontours_avg": avg_findcontours,
"conncomp_avg": avg_conncomp
}
def parse_args():
p = argparse.ArgumentParser()
p.add_argument("--pixels", type=int, default=20_000_000, help="total pixels (e.g. 20000000)")
p.add_argument("--channels", type=int, default=1, choices=[1,3], help="1=gray, 3=BGR")
p.add_argument("--repeat", type=int, default=10, help="repeat count for each op (default 10). Warning: large repeat * large image = long time")
p.add_argument("--seed", type=int, default=42, help="random seed")
return p.parse_args()
def main():
args = parse_args()
total_pixels = args.pixels
channels = args.channels
repeat = args.repeat
print(f"Benchmarking with total_pixels={total_pixels:,}, channels={channels}, repeat={repeat}")
img = make_image(total_pixels=total_pixels, channels=channels, seed=args.seed)
np_results = numpy_big_tests(img, repeat=repeat)
cv_results = opencv_big_tests(img, repeat=repeat)
# summary
print("\n=== Summary (averages in seconds) ===")
for k,v in {**np_results, **cv_results}.items():
print(f"{k}: {v:.6f}")
if __name__ == "__main__":
main()
3.8.6

3.12.10






















