我想讓底下的Python程式碼更快
import sys
import pygame
from pygame.locals import QUIT
import math
import time
pygame.init()
window = pygame.display.set_mode((500, 500))
window.fill((255, 255, 255))
start = time.time()
for i in range(500):
for j in range(500):
r,g,b,z=window.get_at((i,j))
m=1-math.sqrt((250 - i)** 2+(250 - j)**2)/math.sqrt(250 ** 2+250**2)
window.set_at((i,j),(r*m,g*m,b*m,z))
print(time.time() - start)
pygame.display.update()
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
現在的電腦大多是4核心以上的CPU。
看你的程式碼每個i迴圈
都要處理j迴圈的數目500次
。
如果每次的運算都是可以獨立/未耦合
的話,建議拆成四支程式碼同時
跑,耗時直接縮減成原先的1/4。
如下:
#程式一:
...
...
for i in range(125):
for j in range(500):
...
#程式二:
...
...
for i in range(125, 250):
for j in range(500):
...
#程式三:
...
...
for i in range(250, 375):
for j in range(500):
...
#程式四:
...
...
for i in range(375, 500):
for j in range(500):
...
不要浪費了多核心CPU運算的算力了!
我這是我改的程式碼,但能不能把它用到1/60秒以內
import sys
import pygame
from pygame.locals import QUIT
import math
import time
import _thread as thread
pygame.init()
window = pygame.display.set_mode((500, 500))
window.fill((255, 255, 255))
start = time.time()
def Thread(x1,x2):
for i in range(x1,x2):
for j in range(500):
r,g,b,z=window.get_at((i,j))
m=1-math.sqrt((250 - i)** 2+(250 - j)**2)/math.sqrt(250 ** 2+250**2)
window.set_at((i,j),(r*m,g*m,b*m,z))
pygame.display.update()
print(time.time() - start)
thread.start_new_thread(Thread,(0,125))
thread.start_new_thread(Thread,(125,250))
thread.start_new_thread(Thread,(250,375))
thread.start_new_thread(Thread,(375,500))
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
我爬一下python thread的特性。
8核的話可以再持續分拆:
...
def Thread_1():
for i in range(62):
for j in range(500):
...
def Thread_2():
for i in range(62, 124):
for j in range(500):
...
def Thread_3():
for i in range(124, 186):
for j in range(500):
...
def Thread_4():
for i in range(186, 248):
for j in range(500):
...
def Thread_5():
for i in range(248, 310):
for j in range(500):
...
def Thread_6():
for i in range(310, 372):
for j in range(500):
...
def Thread_7():
for i in range(372, 434):
for j in range(500):
...
def Thread_8():
for i in range(434, 500):
for j in range(500):
...
然後每個 thread 的
pygame.display.update()
print(time.time() - start)
搬到 thread.start_new_thread()後面處理,只跑一次程式會掛嗎?
1/60 sec = 16.7 ms,有挑戰!
加油!
我的電腦只到4核,所以能夠在不超過4核的前提下提升速度嗎
針對樓主的回答給一些回饋:
python 並不是在任何情況下都能享受到多核心給額外的運算能力,主要在於 CPython (官方的 python 實作) 存在一個叫做 GIL 的全局控制鎖。因為 GIL 的關係,在 python 上的 thread 其實還是只能跑在一個核心上。
稍微快了一點點,參考看看
import sys
import math
import time
start = time.time()
for i in range(500):
for j in range(500):
m=1-math.sqrt((250 - i)** 2+(250 - j)**2)/math.sqrt(250 ** 2+250**2)
t1 = time.time() - start
print(t1)
start = time.time()
div = math.sqrt(250 ** 2+250**2)
lst = []
for i in range(500):
lst.append((250 - i)** 2)
for i in range(500):
for j in range(500):
m=1-math.sqrt(lst[i]+lst[j])/div
t2 = time.time() - start
print(t2)
我這是我改的程式碼,速度提升了1.5倍,但能不能把它用到1/60秒以內
import sys
import pygame
from pygame.locals import QUIT
import math
import time
import _thread as thread
pygame.init()
window = pygame.display.set_mode((500, 500))
window.fill((255, 255, 255))
div = math.sqrt(250 ** 2+250**2)
lst=[]
for i in range(500):
lst.append((250 - i)** 2)
start = time.time()
def Thread(x1,x2):
for i in range(x1,x2):
for j in range(500):
r,g,b,z=window.get_at((i,j))
m=1-math.sqrt(lst[i]+lst[j])/div
window.set_at((i,j),(r*m,g*m,b*m,z))
pygame.display.update()
print(time.time() - start)
thread.start_new_thread(Thread,(0,125))
thread.start_new_thread(Thread,(125,250))
thread.start_new_thread(Thread,(250,375))
thread.start_new_thread(Thread,(375,500))
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
(250 - i)** 2
改成 (250 - i)*(250 - i)
其實也會快一點點
畢竟 **
好像可以接受浮點數次方,算法有可能類似 Pow
import numpy as np
x = np.fromfunction(lambda i, j, k: 255 * (1-((250 - i)**2+(250 - j)**2)**0.5/(2*250**2)**0.5), (500, 500, 3), dtype=float)
print(x)
這樣?我沒實際運算過你的數值沒有驗證。而且pygame我不熟,只能看出個大概。
你是要讓本來是白色的畫面有漸層?感覺算出來的是有問題的。
然後整個畫面你該看成一個 3D array去運算比較快。
可以利用對稱性,例如下圖這些點與原點的距離是相等的。
所以計算一次距離大多數能填滿 8 個點(不含十字與45度斜線)
程式碼大概長這樣
注意要另外處理 x=0 或 y=0 的 case
(因為沒完全對稱,其中一邊會多 1 個像素)
import math
import time
# 檢查結果是否相同
def compareArr(arr1, arr2):
for i in range(250000):
t = arr1[i] - arr2[i]
if t < -1e-7 or t > 1e-7:
return (i//500, i % 500, arr1[i], arr2[i])
return 'pass'
# 版本一
arr1 = [-0.1]*250000
start = time.time()
for i in range(500):
for j in range(500):
m = 1-math.sqrt((250 - i) ** 2+(250 - j)**2)/math.sqrt(250 ** 2+250**2)
arr1[i*500+j] = m
t1 = time.time() - start
print(t1)
# 版本二
arr2 = [-0.2]*250000
start = time.time()
div = math.sqrt(250 ** 2+250**2)
lst = []
for i in range(500):
lst.append((250 - i) ** 2)
for i in range(500):
for j in range(500):
m = 1-math.sqrt(lst[i]+lst[j])/div
arr2[i*500+j] = m
t2 = time.time() - start
print(t2)
print(compareArr(arr1, arr2))
# 版本三
arr3 = [-0.3]*250000
start = time.time()
sqr2 = math.sqrt(2)
maxR = 250 * sqr2
for i in range(1, 250):
for j in range(i + 1, 250):
m = 1 - math.sqrt(i * i + j * j) / maxR
arr3[(250+i)*500+250+j] = m
arr3[(250+i)*500+250-j] = m
arr3[(250-i)*500+250+j] = m
arr3[(250-i)*500+250-j] = m
arr3[(250+j)*500+250+i] = m
arr3[(250+j)*500+250-i] = m
arr3[(250-j)*500+250+i] = m
arr3[(250-j)*500+250-i] = m
# 十字
m = 1 - i / maxR
arr3[(250+i)*500 + 250 + 0] = m
arr3[(250-i)*500 + 250 + 0] = m
arr3[250*500 + 250+i] = m
arr3[250*500 + 250-i] = m
# 45度斜線
m = 1 - i * sqr2 / maxR
arr3[(250+i)*500+250+i] = m
arr3[(250+i)*500+250-i] = m
arr3[(250-i)*500+250+i] = m
arr3[(250-i)*500+250-i] = m
# x=0 或 y=0 處
m = 1 - math.sqrt(250 * 250 + i * i) / maxR
arr3[(0)*500 + 250+i] = m
arr3[(0)*500 + 250-i] = m
arr3[(250+i)*500 + 0] = m
arr3[(250-i)*500 + 0] = m
m = 1 - 250 / maxR
arr3[250*500 + 0] = m
arr3[0*500 + 250] = m
m = 1
arr3[250*500 + 250] = m
m = 0
arr3[0*500 + 0] = m
t3 = time.time() - start
print(t3)
print(compareArr(arr1, arr3))
除了這邊,也許也要考慮一下 window.set_at((i,j),(r*m,g*m,b*m,z))
是不是會同時更新畫面?
如果會直接更新畫面的話,看有沒有能夠預先把像素資訊寫在記憶體再一次輸出的方式,可能會比較快
你把我那張圖的原點當作 (250, 250)
然後假設 (i, j) = (4, 2)
這時候算 m = 1 - sqrt(4 x 4 + 2 x 2) ÷ 對角線長
這個 m 可以讓 8 個點一起用:
A 點座標 (250 + 4, 250 + 2)
B 點座標 (250 + 2, 250 + 4)
C 點座標 (250 - 2, 250 + 4)
D 點座標 (250 - 4, 250 + 2)
E 點座標 (250 - 4, 250 - 2)
F 點座標 (250 - 2, 250 - 4)
G 點座標 (250 + 2, 250 - 4)
H 點座標 (250 + 4, 250 - 2)
我有再修過程式碼
把結果寫入陣列並測試各種寫法結果是否一致
目前跑的結果
我是覺得要做漸層應該呼叫 API
有些 API 會直接用硬體去跑
而不是自己一個像素一個像素算
另外如果背景圖都一樣的話
看有沒有內建 cache 圖片的功能
這樣計算一次後,後續就直接貼上就好,不用重新計算
沒有內建的話就自己弄個陣列儲存先前的計算結果
後面直接拿來用就好