【问题标题】:Pyglet App Running SlowlyPyglet App 运行缓慢
【发布时间】:2018-06-29 09:35:31
【问题描述】:

我有这个应用程序正在创建一个名为 Pizza Clicker 的 cookie Clicker 的替代版本。这是非常基本的,但运行速度非常慢,我不明白为什么。

import pyglet
window = pyglet.window.Window(fullscreen=True, caption="Click For Pizzas", resizable=True)
win_width = window.width
win_height = window.height
window.set_fullscreen(False)
window.set_size(win_width, win_height)
image_height = round(int(win_width/5)/1.4, 1)
class Main(object):
    def __init__(self):
        self.label = pyglet.text.Label('Pizzas: 0', font_size=100, color=(0, 0, 0, 255),
                                   x=win_width//2, y=win_height - 100,
                                   anchor_x='left', anchor_y='top')
        self.points = 0
        self.number = 1
    def background(self):
        background_img = pyglet.resource.image('pizza_clicker.png')
        background_img.width = (win_width/5)*4
        background_img.height = win_height
        background_img.blit(int(win_width/5), 0, 0.5)
    def drawSidebar(self):
        width = int(win_width/5)
        height = int(win_height)
        sidebar_pattern = pyglet.image.SolidColorImagePattern(color=(100, 100, 100, 100))
        sidebar = sidebar_pattern.create_image(width, height)
        sidebar.blit(0, 0)
        pizza = []
        images = ('pizza_1.png', 'pizza_5.png', 'pizza_5.png', 'pizza_5.png')
        for i in range (0, len(images)):
          divideby = 1.4 / (i + 1)
          pizza.append(pyglet.resource.image(images[i]))
          pizza[i].width = width
          pizza[i].height = round(width/1.4, 1)
          pizza[i].blit(0, window.height - (round(width/divideby, 1)))
    def getNumber(self, y):
        if y > window.height - int(image_height):
            self.number = 1
        elif y > window.height - (int(image_height)*2):
            self.number = 5
        elif y > window.height - (int(image_height)*3):
            self.number = 10
        elif y > window.height - (int(image_height)*4):
            self.number = 20
    def addPoint(self):
       self.points += self.number
       self.label.text = 'Pizzas: %s' %self.points


@window.event
def on_mouse_press(x, y, buttons, modifiers):
    if buttons & pyglet.window.mouse.LEFT and x > win_width/5:
        main.addPoint()
    elif buttons & pyglet.window.mouse.LEFT and x < win_width/5:
        main.getNumber(y)

@window.event
def on_draw():
    window.clear()
    main.background()
    main.label.draw()
    main.drawSidebar()

main = Main()

pyglet.app.run()

所以问题是当我点击窗口的右侧时,它应该立即添加一个(或多个)点,但它会滞后几秒钟。此外,为了没有人感到困惑,代码确实可以工作,但速度很慢。我该怎么解决?

【问题讨论】:

    标签: python pyglet


    【解决方案1】:

    在每次draw() 迭代中,您都在做:

    background_img = pyglet.resource.image('pizza_clicker.png')
    

    这意味着您正在从硬盘驱动器加载同一张图片,每个渲染序列。您还对不同的披萨图像进行了 for 循环,您还可以从硬盘驱动器获取它们:

    for i in range (0, len(images)):
        divideby = 1.4 / (i + 1)
        pizza.append(pyglet.resource.image(images[i]))
    

    我强烈建议您阅读如何加载资源,并使用 cProfiler 分析器。

    如何分析代码的一个很好的例子是here。 由于这是一个外部来源,我将包括两个 SO 的链接,它们的效果差不多(但没有那么有力或自我解释):

    这是一个 tl-dr 版本:

    python -m cProfile -o profile_data.pyprof your_script.py
    pyprof2calltree -i profile_data.pyprof -k
    

    这应该呈现一个所谓的“调用树”,其中包含您的代码执行的所有执行、它们花费了多长时间以及它们使用了多少内存。从应用程序的开始到底部。

    但是,我强烈建议您执行1 次渲染序列,并在第一次渲染后添加exit(1)。只是这样您就可以分析 1 次运行,而不是每秒 60 次。

    要了解代码运行缓慢原因的关键字:Python、profiling、kcachegrind、cprofile、cprofiling、callstack。

    剧透警告

    要解决您的大部分问题,请将所有 I/O 密集型操作(加载图像、创建形状等)移到主类的 __init__ 调用中。

    最终产品看起来应该是这样的:

    class Main(object):
        def __init__(self):
            self.label = pyglet.text.Label('Pizzas: 0', font_size=100, color=(0, 0, 0, 255),
                                       x=win_width//2, y=win_height - 100,
                                       anchor_x='left', anchor_y='top')
            self.points = 0
            self.number = 1
    
            self.background_img = pyglet.resource.image('pizza_clicker.png')
            self.background_img.width = (win_width/5)*4
            self.background_img.height = win_height
    
            sidebar_pattern = pyglet.image.SolidColorImagePattern(color=(100, 100, 100, 100))
            self.sidebar = sidebar_pattern.create_image(width, height)
    
            self.pizzas = []
            width = int(win_width/5)
            height = int(win_height)
            self.pizza_images = ('pizza_1.png', 'pizza_5.png', 'pizza_5.png', 'pizza_5.png')
            for i in range (0, len(pizza_images)):
                resource = pyglet.resource.image(pizza_images[i])
                resource.width = width
                resource.height = round(width/1.4, 1) # Not sure why you're using width here.. meh.. keeping it -yolo-
                self.pizzas.append(resource)
    
        def background(self):
            self.background_img.blit(int(win_width/5), 0, 0.5)
    
        def drawSidebar(self):
            width = int(win_width/5)
            height = int(win_height) # You're using win_height here, but window.height later. It's strange.
            self.sidebar.blit(0, 0)
            for i in range (0, len(self.pizza_images)):
                divideby = 1.4 / (i + 1)
                self.pizzas[i].blit(0, window.height - (round(width/divideby, 1)))
    
        def getNumber(self, y):
            if y > window.height - int(image_height):
                self.number = 1
            elif y > window.height - (int(image_height)*2):
                self.number = 5
            elif y > window.height - (int(image_height)*3):
                self.number = 10
            elif y > window.height - (int(image_height)*4):
                self.number = 20
    
        def addPoint(self):
           self.points += self.number
           self.label.text = 'Pizzas: %s' %self.points
    

    但为什么要停在这里,这里大量使用了blit。 Blit 适用于一两个对象。但是很快就很难跟踪您将所有内容“传送”到的内容和位置。你还在循环和其他东西中进行大量的除法、加法和其他类型的计算。

    请记住,在渲染方面,循环是魔鬼。
    如果你在某个地方有一个循环,你几乎可以肯定开始在那里寻找性能问题(任何人看着这个评论并去“pff他不知道他在说什么”..是的,我知道,但这是一个很好的初学者提示)

    我强烈建议您将图像放入 pyglet.sprite.Sprite() 对象中。他们跟踪位置、渲染,最重要的是,他们支持batched rendering。那是你们祖国的圣杯!如果有什么可以在 pyglet 中节省您的性能......好吧......一般来说,它是批量渲染。

    你看,显卡的设计考虑了一件事.. 取一个巨大的数学方程,然后把它整个吞下去。它特别擅长获取大量信息并将其拍摄到您的屏幕上。它不擅长多个命令。这意味着,如果您将许多较小的数据包来回发送和第四次发送到显卡,由于开销和其他因素,它将执行接近最佳的无磨损。

    因此,将您的图像放入 sprite 中,并将这些 sprite 放入批次中,并且不使用任何 for 循环和渲染资源加载..

    这就是您的代码的样子:

    class Main(object):
        def __init__(self):
            self.label = pyglet.text.Label('Pizzas: 0', font_size=100, color=(0, 0, 0, 255),
                                       x=win_width//2, y=win_height - 100,
                                       anchor_x='left', anchor_y='top')
            self.points = 0
            self.number = 1
    
            self.background_layer = pyglet.graphics.OrderedGroup(0)
            self.foreground_layer = pyglet.graphics.OrderedGroup(1)
            self.batch = pyglet.graphics.Batch()
    
            self.background_img = pyglet.sprite.Sprite(pyglet.resource.image('pizza_clicker.png'), batch=self.batch, group=self.background_layer)
            self.background_img.width = (win_width/5)*4
            self.background_img.height = win_height
            self.background.x = int(win_width/5)
            self.background.y = 0
    
            sidebar_pattern = pyglet.image.SolidColorImagePattern(color=(100, 100, 100, 100))
            self.sidebar = pyglet.sprite.Sprite(sidebar_pattern.create_image(width, height), batch=self.batch, group=self.background_layer)
            self.sidebar.x = 0
            self.sidebar.y = 0
    
            self.pizzas = []
            width = int(win_width/5)
            height = int(win_height)
            self.pizza_images = ('pizza_1.png', 'pizza_5.png', 'pizza_5.png', 'pizza_5.png')
            for i in range (0, len(pizza_images)):
                divideby = 1.4 / (i + 1)
    
                resource = pyglet.sprite.Sprite(pyglet.resource.image(pizza_images[i]), batch=self.batch, group=self.foreground_layer)
                resource.width = width
                resource.height = round(width/1.4, 1) # Not sure why you're using width here.. meh.. keeping it -yolo-
                resource.x = 0
                resource.y = window.height - (round(width/divideby, 1))
    
                self.pizzas.append(resource)
    
        def draw(self):
            # This is instead of doing:
            # - self.background.draw()
            # - self.sidebar.draw()
            # - self.pizzas[i].draw()
            self.batch.draw()
            self.label.draw() # You could put this in a batch as well :)
    
        def getNumber(self, y):
            if y > window.height - int(image_height):
                self.number = 1
            elif y > window.height - (int(image_height)*2):
                self.number = 5
            elif y > window.height - (int(image_height)*3):
                self.number = 10
            elif y > window.height - (int(image_height)*4):
                self.number = 20
    
        def addPoint(self):
            self.points += self.number
            self.label.text = 'Pizzas: %s' %self.points
    
    @window.event
    def on_draw():
        window.clear()
        main.draw()
    

    现在,代码并不完美。但它有望让你了解你应该走向的方向。我也没有执行这段代码,主要是因为我没有所有的披萨图像或时间。我可能会回到这里,整理一下我遇到的(最有可能的)拼写错误。

    【讨论】:

    • 高度的奇怪使用宽度是由于比例
    • @DiogoSCF 我想了很多 :) 这只是一个不直观的变量,win_heightwindow.height 的双重使用。无论如何,你比我们任何人都更了解变量。我主要希望新代码对您有意义,并且您可以使用其中的一些:)
    • 经过一些修改后它确实可以工作,现在速度更快
    猜你喜欢
    • 1970-01-01
    • 2014-09-28
    • 2021-11-25
    • 2020-07-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多