Python 导入全局变量比局部变量更快?

Python importing globals faster than local variables?

所以我和我的朋友一起在 Pygame 制作游戏,我刚刚开始为主角编写动画。

我想有几种方法可以做到这一点。要么制作一个图像列表并在列表上循环,要么制作一个包含精灵上图片坐标的字典 sheet。

我选择了第二个选项,现在我决定要检查哪个更快。所以我以一种非常简单的方式对它们进行了编程,只是为了快速检查它们的性能。

结果彼此非常接近(使用词典时为 0.41,不使用时约为 0.40)。然后我决定尝试如果我使用第一个选项会发生什么,除了我会在一个单独的文件中调用 pygame.image.load 并将动画帧存储为全局变量,然后导入到主文件中。

我虽然会很慢,因为我在某处读到 python 导入非常慢......但令人惊讶的是我得到了 0.021 秒的结果!

这是对性能的巨大改变,这可能对我的游戏至关重要,所以我想知道是否有人碰巧知道为什么这种方法快得多,是否只是因为某些 x 或 y 为什么它在这种情况下更快,而在另一种情况下会非常慢。

这是类似于我游戏当前状态的代码:

    import pygame
import time
start_time = time.time()
playerSpriteSize = 192
img = (0, 0)
class SpriteSheet():
    def __init__(self, filename):
        self.sheet = pygame.image.load(filename).convert()

    def get_image(self, coords, size, flip=False):
        surf = pygame.Surface(size).convert()
        surf.blit(self.sheet, (0, 0), (coords[0], coords[1], size[0], size[1]))
        pygame.transform.flip(surf, False, True)
        surf.set_colorkey((0, 0, 0))
        surf = pygame.transform.flip(surf, flip, False)
        return surf

pygame.init()

displaySurface = pygame.display.set_mode((400, 400))
x = SpriteSheet("playerAnimation.png")


animationList = {0: (0, playerSpriteSize*0), 1: (0, playerSpriteSize*1), 2: (0, playerSpriteSize*2),
                 3: (0, playerSpriteSize*3), 4: (0, playerSpriteSize*4), 5: (0, playerSpriteSize*5),
                 6: (0, playerSpriteSize*6)}
rounda = 0
for i in animationList:
    a1 = x.get_image(animationList[rounda], (playerSpriteSize, playerSpriteSize))
    displaySurface.blit(a1, (30, 30))
    rounda += 1
    pygame.display.update()


print("--- %s seconds ---" %(time.time() - start_time))

现在这是使用在主游戏循环之前加载的图像的代码:

import pygame
import time
start_time = time.time()
imageSize = 192
img = (0, 0)
class SpriteSheet():
    def __init__(self, filename):
        self.sheet = pygame.image.load(filename).convert()

    def get_image(self, coords, size, flip=False):
        surf = pygame.Surface(size).convert()
        surf.blit(self.sheet, (0, 0), (coords[0], coords[1], size[0], size[1]))
        pygame.transform.flip(surf, False, True)
        surf.set_colorkey((0, 0, 0))
        surf = pygame.transform.flip(surf, flip, False)
        return surf

pygame.init()

displaySurface = pygame.display.set_mode((400, 400))
x = SpriteSheet("playerAnimation.png")

a1 = x.get_image((0, 0), (imageSize, imageSize))
a2 = x.get_image((0, 192), (imageSize, imageSize))
a3 = x.get_image((0, 384), (imageSize, imageSize))
a4 = x.get_image((0, 576), (imageSize, imageSize))
a5 = x.get_image((0, 768), (imageSize, imageSize))
a6 = x.get_image((0, 960), (imageSize, imageSize))
a7 = x.get_image((0, 1152), (imageSize, imageSize))

animationList = [a1, a2, a3, a4, a5, a6, a7]

for i in animationList:
    displaySurface.blit(i, (30, 30))
    pygame.display.update()

print("--- %s seconds ---" %(time.time() - start_time))

这是在大约 0.021 秒内运行的代码(分为 2 个文件): 文件 1(主文件)

import pygame
import time
from mainDifferentExtern import a1, a2, a3, a4, a5, a6, a7


start_time = time.time()
imageSize = 192
img = (0, 0)
class SpriteSheet():
    def __init__(self, filename):
        self.sheet = pygame.image.load(filename).convert()

    def get_image(self, coords, size, flip=False):
        surf = pygame.Surface(size).convert()
        surf.blit(self.sheet, (0, 0), (coords[0], coords[1], size[0], size[1]))
        pygame.transform.flip(surf, False, True)
        surf.set_colorkey((0, 0, 0))
        surf = pygame.transform.flip(surf, flip, False)
        return surf

pygame.init()

displaySurface = pygame.display.set_mode((400, 400))
x = SpriteSheet("playerAnimation.png")

animationList = [a1, a2, a3, a4, a5, a6, a7]

for i in animationList:
    displaySurface.blit(i, (30, 30))
    pygame.display.update()

print("--- %s seconds ---" %(time.time() - start_time))

及其从中导入的文件:

import pygame
imageSize = 192


class SpriteSheet():
    def __init__(self, filename):
        self.sheet = pygame.image.load(filename).convert()

    def get_image(self, coords, size, flip=False):
        surf = pygame.Surface(size).convert()
        surf.blit(self.sheet, (0, 0), (coords[0], coords[1], size[0], size[1]))
        pygame.transform.flip(surf, False, True)
        surf.set_colorkey((0, 0, 0))
        surf = pygame.transform.flip(surf, flip, False)
        return surf

pygame.init()
pygame.display.set_mode((1, 1))
x = SpriteSheet("playerAnimation.png")

a1 = x.get_image((0, 0), (imageSize, imageSize))
a2 = x.get_image((0, 192), (imageSize, imageSize))
a3 = x.get_image((0, 384), (imageSize, imageSize))
a4 = x.get_image((0, 576), (imageSize, imageSize))
a5 = x.get_image((0, 768), (imageSize, imageSize))
a6 = x.get_image((0, 960), (imageSize, imageSize))
a7 = x.get_image((0, 1152), (imageSize, imageSize))

编辑:根据 match 的建议,我改变了测量时间的方法,而是使用 python 分析(使用 cProfile),但我得到的结果大致相同,除了时间前两种方法出来的时间有点长

对于单独的文件,所有 getImage 调用都在首次导入文件时发生。您没有为导入计时,因此您忽略了该文件所做的一切的成本。

我认为这里的差异是由于 python 为任何导入的模块编译字节码。这反过来将加快导入的 loading/execution。您可以在原始代码旁边的目录中看到这些 .pyc 文件。

我怀疑如果您执行以下操作,您的结果将再次平衡:

rm *.pyc
PYTHONDONTWRITEBYTECODE=1 python mygame.py

另请记住,0.02 秒的机会完全在任何系统的正常 'drift' 范围内 - 这就是为什么像 timeit 运行 这样的工具可以使用数千个相同的代码次并对结果进行平均。

关于这一点 - 考虑一下您在代码中可能出现的缓慢操作有多少次 - 如果您只加载一次图像,并且通过不同的方式执行的性能增益为 0.02 秒 - 而游戏预计 运行 几分钟或几小时,那么这看起来像是过早的优化。