You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

pycairo中每次调用show_text性能逐次下降问题排查

问题:pycairo show_text() 耗时随调用次数递增的异常问题

我使用pycairo 1.23.0渲染至cairo.ImageSurface,再通过pygame显示位图。多数绘图操作耗时稳定,但cairo.Context.show_text()的调用耗时随每次调用持续增加,尤其在与2的幂相关的帧数时耗时异常。怀疑是内存管理或内部数据结构出现异常,附上可运行示例代码。已知这是“toy”字体API,但引入pango等库对该应用来说过于冗余。

绘图耗时图

示例代码:

import pygame
import cairo
import numpy as np
from time import perf_counter


pygame.init()
screen = pygame.display.set_mode((1280, 720), pygame.SCALED, vsync=1)

while True:
    img_surf = cairo.ImageSurface(cairo.FORMAT_ARGB32, 1280, 720)
    ctx = cairo.Context(img_surf)
    ctx.set_source_rgb(.7, .7, .7)
    ctx.paint()

    start = perf_counter()
    ctx.set_source_rgb(.0, .0, .0)
    ctx.set_font_size(30)
    ctx.move_to(20, 50)
    ctx.show_text("Not very fast")
    stop = perf_counter()

    print(f"{stop - start:.07f}")

    img_surf.flush()

    result = np.copy(np.asarray(img_surf.get_data()))
    result = result.reshape((720, 1280, 4))[..., :3][..., ::-1]

    surface = pygame.surfarray.make_surface(np.transpose(result, (1, 0, 2)))
    screen.blit(surface, (0, 0))
    img_surf.finish()

    pygame.display.flip()

    list(pygame.event.get())

问题根源

你的代码存在两个核心问题,直接导致了耗时递增和内存异常:

  • 循环内重复创建Surface与Context:每次循环都新建cairo.ImageSurface和cairo.Context,即便调用了finish(),pycairo的垃圾回收也无法及时清理底层C语言层面的资源,造成内存累积,进而拖慢show_text()的字体缓存效率。
  • 冗余内存拷贝与转换:np.copy(np.asarray(img_surf.get_data()))会创建额外内存副本,加重内存负担;颜色通道和数组转置的操作也存在优化空间,额外消耗了运行时间。

修复方案

1. 复用Surface和Context

将cairo.ImageSurface和cairo.Context的初始化移到循环外,避免重复创建销毁带来的资源开销:

import pygame
import cairo
import numpy as np
from time import perf_counter


pygame.init()
screen = pygame.display.set_mode((1280, 720), pygame.SCALED, vsync=1)

# 仅初始化一次Surface和Context
img_surf = cairo.ImageSurface(cairo.FORMAT_ARGB32, 1280, 720)
ctx = cairo.Context(img_surf)
ctx.set_font_size(30)  # 提前设置字体大小,避免重复操作

while True:
    # 重置画布背景
    ctx.set_source_rgb(.7, .7, .7)
    ctx.paint()

    start = perf_counter()
    ctx.set_source_rgb(.0, .0, .0)
    ctx.move_to(20, 50)
    ctx.show_text("Now it's fast")
    stop = perf_counter()

    print(f"{stop - start:.07f}")

    img_surf.flush()

    # 优化数据转换逻辑
    data = np.asarray(img_surf.get_data()).reshape((720, 1280, 4))
    data = data[..., [2,1,0]]  # 直接转换ARGB到RGB格式,避免冗余拷贝
    surface = pygame.surfarray.make_surface(np.transpose(data, (1, 0, 2)))
    screen.blit(surface, (0, 0))

    pygame.display.flip()

    # 正确处理退出事件
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            exit()

# 程序结束时再清理资源
img_surf.finish()

2. 优化数据转换流程

  • 移除np.copy(),直接使用np.asarray()获取内存视图(img_surf.get_data()返回的是缓冲区,np.asarray()不会产生额外拷贝)
  • 用data[..., [2,1,0]]直接调整颜色通道顺序,比切片反转更高效
  • 保留必要的数组转置,适配cairo与pygame的内存布局差异

3. 完善事件处理

原代码仅清空事件队列,未处理退出事件,修复后添加pygame.QUIT事件的响应逻辑,保证程序能正常退出。

效果验证

修改后,show_text()的耗时会保持稳定,不会再出现随调用次数递增的情况,也不会有2的幂帧数的异常波动。这是因为底层字体缓存和Surface资源被复用,内存泄漏问题得到解决。


内容的提问来源于stack exchange,提问作者Maximilian Schier

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 07:53:25