iT邦幫忙

1

如何讓程式碼更有效率

  • 分享至 

  • xImage

我想讓底下的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()
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中
2
Darwin Watterson
iT邦好手 1 級 ‧ 2022-05-27 11:48:38

現在的電腦大多是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運算的算力了! /images/emoticon/emoticon29.gif

看更多先前的回應...收起先前的回應...

我這是我改的程式碼,但能不能把它用到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()後面處理,只跑一次程式會掛嗎?/images/emoticon/emoticon19.gif
1/60 sec = 16.7 ms,有挑戰!/images/emoticon/emoticon34.gif
加油!

我的電腦只到4核,所以能夠在不超過4核的前提下提升速度嗎

sixwings iT邦研究生 4 級 ‧ 2022-05-29 20:46:14 檢舉

針對樓主的回答給一些回饋:

python 並不是在任何情況下都能享受到多核心給額外的運算能力,主要在於 CPython (官方的 python 實作) 存在一個叫做 GIL 的全局控制鎖。因為 GIL 的關係,在 python 上的 thread 其實還是只能跑在一個核心上。

0
海綿寶寶
iT邦大神 1 級 ‧ 2022-05-27 14:04:30

稍微快了一點點,參考看看
https://ithelp.ithome.com.tw/upload/images/20220527/20001787prWF7CueqQ.png

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()
淺水員 iT邦大師 6 級 ‧ 2022-05-27 17:44:23 檢舉

(250 - i)** 2 改成 (250 - i)*(250 - i) 其實也會快一點點
畢竟 ** 好像可以接受浮點數次方,算法有可能類似 Pow

0
froce
iT邦大師 1 級 ‧ 2022-05-27 16:41:32
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去運算比較快。

0
淺水員
iT邦大師 6 級 ‧ 2022-05-27 18:06:48

可以利用對稱性,例如下圖這些點與原點的距離是相等的。
所以計算一次距離大多數能填滿 8 個點(不含十字與45度斜線)
https://ithelp.ithome.com.tw/upload/images/20220527/20112943OWzozRKyZR.png

程式碼大概長這樣
注意要另外處理 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)) 是不是會同時更新畫面?
如果會直接更新畫面的話,看有沒有能夠預先把像素資訊寫在記憶體再一次輸出的方式,可能會比較快

看更多先前的回應...收起先前的回應...

感覺挺抽象的,能講的詳細點嗎

淺水員 iT邦大師 6 級 ‧ 2022-05-27 23:09:36 檢舉

你把我那張圖的原點當作 (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)

淺水員 iT邦大師 6 級 ‧ 2022-05-28 01:02:53 檢舉

我有再修過程式碼
把結果寫入陣列並測試各種寫法結果是否一致
目前跑的結果
https://ithelp.ithome.com.tw/upload/images/20220528/20112943YgbGD3PnGB.png

我是覺得要做漸層應該呼叫 API
有些 API 會直接用硬體去跑
而不是自己一個像素一個像素算

淺水員 iT邦大師 6 級 ‧ 2022-05-28 01:16:33 檢舉

另外如果背景圖都一樣的話
看有沒有內建 cache 圖片的功能
這樣計算一次後,後續就直接貼上就好,不用重新計算

沒有內建的話就自己弄個陣列儲存先前的計算結果
後面直接拿來用就好

0
rofellos
iT邦新手 2 級 ‧ 2022-05-30 09:39:47

換cpu

我要發表回答

立即登入回答