简体   繁体   English

为什么我的 Python Turtle 程序运行时间越长越慢?

[英]Why is my Python Turtle program slowing down drastically the longer it runs?

I am trying to make a simple galaga type game where the ship moves back and forth along the bottom of the screen and shoots automatically.我正在尝试制作一个简单的 galaga 类型游戏,其中船沿着屏幕底部来回移动并自动射击。 It runs as expected at first but slows down drastically pretty quickly.它一开始按预期运行,但很快就大大减慢了速度。 I thought it was maybe slowing as it had more and more bullets to keep track of, but limiting the number of bullets on-screen didn't seem to help at all.我认为它可能会变慢,因为它有越来越多的子弹要跟踪,但限制屏幕上的子弹数量似乎根本没有帮助。

The main loop:主循环:

while game_is_on:

    screen.update()
    ship.slide()
    bullet_manager.shoot(ship.xcor())
    bullet_manager.move_bullets()

from bullet_manager:来自子弹管理器:

def shoot(self, xcor):
    self.chance +=1
    if self.chance %6 == 0:
        new_bullet = Turtle("square")
        new_bullet.color("red")
        new_bullet.shapesize(stretch_wid=.1)
        new_bullet.pu()
        new_bullet.seth(90)
        new_bullet.goto(xcor, -200)
        self.all_bullets.append(new_bullet)


def move_bullets(self):
    for bullet in self.all_bullets:
        bullet.forward(10)
        self.all_bullets = self.all_bullets[-10:]

self.all_bullets = self.all_bullets[-10:] prunes turtles from your local list, but not from the application's memory. self.all_bullets = self.all_bullets[-10:]从本地列表中修剪海龟,但不从应用程序的 memory 中修剪海龟。 There are still turtles being managed by the turtle module even if they're not in your list.即使它们不在您的列表中,仍然有海龟由turtle模块管理。 You can take a peek at turtle.turtles() to see how many turtles are being tracked internally.你可以看一下turtle.turtles() ,看看有多少海龟在内部被跟踪。

Here's a minimal reproduction:这是一个最小的复制:

import turtle
from random import randint
from time import sleep


turtle.tracer(0)
turtles = []

while True:
    t = turtle.Turtle()
    turtles.append(t)

    if len(turtles) > 10:
        turtles = turtles[-10:]

    for t in turtles:
        if randint(0, 9) < 1:
            t.left(randint(0, 360))

        t.forward(randint(2, 10))

    print(len(turtles), len(turtle.turtles()))
    turtle.update()
    sleep(0.1)

You'll see the screen flood with turtles, and while your list stays at length 10, the turtle module's length keeps going up.你会看到屏幕上到处都是海龟,当你的列表长度保持在 10 时,海龟模块的长度一直在增加。

The first thought might be to chop off the turtle's internal list, but I prefer not to mess with library-managed resources without being given permission to do so.第一个想法可能是砍掉海龟的内部列表,但我不喜欢在没有获得许可的情况下乱搞图书馆管理的资源。 A quick attempt using memory stat code from this answer leaks:使用此答案中的 memory 统计代码的快速尝试泄漏:

import os, psutil
import turtle
from random import randint
from time import sleep


turtle.tracer(0)

while True:
    t = turtle.Turtle()

    if len(turtle.turtles()) > 10:
        turtle.turtles()[:] = turtle.turtles()[:10]

    for t in turtle.turtles():
        if randint(0, 9) < 1:
            t.left(randint(0, 360))

        t.forward(randint(2, 10))

    print(len(turtle.turtles()))
    process = psutil.Process(os.getpid())
    print(process.memory_info().rss)  # in bytes
    turtle.update()
    sleep(0.1)

How to fully delete a turtle is the canonical resource for deleting turtles, but a probably better solution for this use case is to pre-allocate a turtle pool and recycle turtles from it. 如何完全删除海龟是删除海龟的规范资源,但对于这个用例,一个可能更好的解决方案是预先分配一个海龟池并从中回收海龟。 When a bullet (or any other entity) is created, borrow a turtle from the pool and store it as a property on your object .创建子弹(或任何其他实体)时,从池中借一只海龟并将其作为属性存储在您的 object 上 When the bullet leaves the screen (or meets some other termination condition), return the turtle that the bullet instance borrowed back to the pool.当子弹离开屏幕(或满足其他终止条件)时,将子弹实例借用的海龟返回池中。

Here's an example, possibly overengineered for your use case, but you can use the pool as a library or adapt the high-level concept.这是一个示例,可能针对您的用例进行了过度设计,但您可以将池用作库或调整高级概念。

import turtle
from random import randint


class TurtlePool:
    def __init__(self, initial_size=8, capacity=32):
        if initial_size > capacity:
            raise ArgumentError("initial_size cannot be greater than capacity")

        self.capacity = capacity
        self.allocated = 0
        self.dead = []

        for _ in range(initial_size):
            self._allocate()

    def available(self):
        return bool(self.dead) or self.allocated < self.capacity

    def acquire(self):
        if not self.dead:
            if self.allocated < self.capacity:
                self._allocate()

        return self.dead.pop()

    def give_back(self, t):
        self.dead.append(t)
        self._clean_turtle(t)

    def _allocate(self):
        t = turtle.Turtle()
        self.allocated += 1
        assert self.allocated == len(turtle.turtles())
        self.dead.append(t)
        self._clean_turtle(t)

    def _clean_turtle(self, t):
        t.reset()
        t.hideturtle()
        t.penup()


class Bullet:
    def __init__(self, x, y, speed, angle):
        self.speed = speed
        self.turtle = turtle_pool.acquire()
        self.turtle.goto(x, y)
        self.turtle.setheading(angle)
        self.turtle.showturtle()

    def forward(self):
        self.turtle.forward(self.speed)

    def destroy(self):
        turtle_pool.give_back(self.turtle)

    def in_bounds(self):
        x, y = self.turtle.pos()
        return (
            x >= w / -2 and x < w / 2 and
            y >= h / -2 and y < h / 2
        )


def tick():
    if randint(0, 1) == 0 and turtle_pool.available():
        bullets.append(Bullet(
            x=0,
            y=0,
            speed=8,
            angle=randint(0, 360)
        ))

    next_gen = []

    for b in bullets:
        b.forward()

        if b.in_bounds():
            next_gen.append(b)
        else:
            b.destroy()

    bullets[:] = next_gen
    turtle.update()
    turtle.Screen().ontimer(tick, frame_delay_ms)


frame_delay_ms = 1000 // 30
turtle.tracer(0)
turtle_pool = TurtlePool()
w = turtle.window_width()
h = turtle.window_height()
bullets = []
tick()
turtle.exitonclick()

Keep in mind, this doesn't reuse Bullet objects and allocates a new list per frame, so there's room for further optimization.请记住,这不会重用Bullet对象并为每帧分配一个新列表,因此还有进一步优化的空间。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM