numpy Numba:如何加快数值模拟也需要GUI

dzhpxtsq  于 2022-11-24  发布在  其他
关注(0)|答案(1)|浏览(132)

我刚开始学习Numba来加速for循环。我读到它是impossible to call a non-jitted function from a numba jitted function。因此我不认为我可以@jitclass(spec)我的类或@njit主算法函数(compute())让我的代码保持原样,因为模拟的每一步(onestep())也会改变图像tkinter.Photoimage中像素的值,这是一个Python类型。

  • 对程序进行任何可能的逻辑变更,将GUI和数字部分充分分离,以允许应用Numba;
  • 是否有与Numba兼容的Tkinter替代品;
  • 除了Numba还有什么别的选择我可能会从中受益。

下面是我代码的简化版本:

import tkinter as tk
import numpy as np

window = tk.Tk()
window.geometry("600x600")
canv_w= 480
square_w = 16 #size of one element of the matrix
canvas=tk.Canvas(window,width=480,height=480)
canvas.pack()
my_image=tk.PhotoImage(width=480,height=480)
canvas.create_image((3, 3),image=my_image,anchor="nw",state="normal")
running =0

def pixel(self, i,j):

        if self.matrix[i,j]==-1:
            temp="#cc0000" #red
        elif self.matrix[i,j]==0:
            temp= "#fffafa" #white
        elif self.matrix[i,j]==1:
            temp="#7CFC00" #green
        my_image.put(temp,to=(i*square_w,j*square_w,(i+1)*square_w,(j+1)*square_w))

class myClass:

   def __init__(self, size):
        self.L=size
        self.matrix=np.random.choice([-1, 0, 1], (self.L,self.L), p=[0.45,0.1,0.45])
        self.white_number=len(np.where(self.matrix==0)[0])
        self.iteration=0

        for i in range(self.L):
            for j in range(self.L):
                pixel(self,i,j)

   def onestep(self): 
        whites=np.where(self.matrix==0)# find position of all white squares
        my_v= np.random.choice(self.white_number)# randomly pick one white square...
        x=whites[0][my_v]
        y=whites[1][my_v]

        num= np.random.choice([0,1,2,3]) #...randomly pick one of its 4 neighbours
        neighbour=[[(x + 1)% self.L, y], [x, (y + 1) % self.L], [(x -  1)% self.L, y], [x, (y - 1)% self.L]]

        #swap with neighbour
        self.matrix[x,y]=self.matrix[neighbour[num][0],neighbour[num][1]]
        self.matrix[neighbour[num][0],neighbour[num][1]]=0

        pixel(self,x,y) #update the pixel the white square has left
        pixel(self,neighbour[num][0],neighbour[num][1]) #update the pixel the white atom has jumped to

   def compute(self):
    if running:
        for j in range(1, self.white_number + 1):
            self.onestep()

        self.iteration+=1

    window.after(1000,self.compute)


running=1

myObj=myClass(30)
myObj.compute()

window.mainloop()
ymdaylpp

ymdaylpp1#

  • 除了Numba还有什么别的选择我可能会从中受益。

cython是存在的,并且比numba更成熟,但是它需要编译器,所以你只需要编译二进制文件,而不是JIT函数,它提供静态类型,并且消除了解释器的开销。

  • 有任何可能的逻辑变化的程序,这将分开GUI和数字部分足以允许Numba被应用

实际上,您可以使用numbaobjmode调用一个非JIT编译的函数,但它仍然有一些约束,请考虑将每个JIT编译的函数拆分为一个可JIT编译的部分和一个不可JIT编译的部分,如下所示

import tkinter as tk
import numpy as np
import numba

window = tk.Tk()
window.geometry("600x600")
canv_w = 480
square_w = 16  # size of one element of the matrix
canvas = tk.Canvas(window, width=480, height=480)
canvas.pack()
my_image = tk.PhotoImage(width=480, height=480)
canvas.create_image((3, 3), image=my_image, anchor="nw", state="normal")
running = 0

def pixel(matrix, i, j):
    if matrix[i, j] == -1:
        temp = "#cc0000"  # red
    elif matrix[i, j] == 0:
        temp = "#fffafa"  # white
    elif matrix[i, j] == 1:
        temp = "#7CFC00"  # green
    my_image.put(temp, to=(i * square_w, j * square_w, (i + 1) * square_w, (j + 1) * square_w))

@numba.njit
def _onestep(matrix, white_number, L):
    whites = np.where(matrix == 0)  # find position of all white squares
    my_v = np.random.choice(white_number)  # randomly pick one white square...
    x = whites[0][my_v]
    y = whites[1][my_v]

    num = np.random.choice(np.array((0, 1, 2, 3)))  # ...randomly pick one of its 4 neighbours
    neighbour = [[(x + 1) % L, y], [x, (y + 1) % L], [(x - 1) % L, y], [x, (y - 1) % L]]

    # swap with neighbour
    matrix[x, y] = matrix[neighbour[num][0], neighbour[num][1]]
    matrix[neighbour[num][0], neighbour[num][1]] = 0
    return x,y,neighbour[num][0],neighbour[num][1]


class myClass:

    def __init__(self, size):
        self.L = size
        self.matrix = np.random.choice([-1, 0, 1], (self.L, self.L), p=[0.45, 0.1, 0.45])
        self.white_number = len(np.where(self.matrix == 0)[0])
        self.iteration = 0

        for i in range(self.L):
            for j in range(self.L):
                pixel(self.matrix, i, j)

    def onestep(self):
        x,y,z1,z2 = _onestep(self.matrix, self.white_number, self.L)
        pixel(self.matrix, x, y)  # update the pixel the white square has left
        pixel(self.matrix, z1, z2)  # update the pixel the white atom has jumped to

    def compute(self):
        if running:
            for j in range(1, self.white_number + 1):
                self.onestep()

            self.iteration += 1

        window.after(1000, self.compute)

running = 1

myObj = myClass(30)
myObj.compute()

window.mainloop()

这里,onestep没有被即时修正,而执行重负载的_onestep被即时修正。
在你的程序中有一些加速(每帧有numba的11毫秒对没有numba的19毫秒),因为大部分时间花在绘图上,而不是计算上,所以它不会从任何更多的“编译”中受益。
更好的方法是将所有的屏幕数据存储为2d数组,并在numba中操作它,然后在python中一次重绘整个屏幕,而不是一部分一部分地重绘。
cython可能能够从中获得更多优化的代码,因为它可以在同一个函数中混合python和非python对象,并消除循环开销,但为它编写代码比numba更难。

相关问题