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

如何优化Racket/GUI程序以达到60FPS运行帧率?

Racket GUI 画布性能优化建议

针对你这个随机方格网格的Racket程序,以下几个优化点可以帮你把FPS提升到60以上:

1. 复用画笔与画刷,避免重复设置

原代码在每次绘制方格时都重新设置画笔和画刷,这会产生大量冗余的DC调用,是主要性能瓶颈之一。提前创建好黑白两种样式的画笔和画刷,绘制时直接切换:

; 预定义画笔和画刷,避免重复创建
(define black-pen (new pen% [color "black"] [width 1] [style 'solid]))
(define black-brush (new brush% [color "black"] [style 'solid]))
(define white-pen (new pen% [color "white"] [width 1] [style 'solid]))
(define white-brush (new brush% [color "white"] [style 'solid]))

然后在on-paint里直接调用预定义的对象即可,无需重复初始化。

2. 改用位图批量绘制,替代逐格矩形绘制

逐次调用draw-rectangle的开销很高,我们可以预先创建一个与画布同尺寸的位图,直接在位图上完成所有方格绘制,最后一次性把整个位图画到画布上,这能大幅降低DC调用的次数:

修改rect-canvas%的定义,新增位图相关的成员:

(define rect-canvas%
  (class canvas%
    (inherit get-dc)
    (super-new)
    ; 创建与画布同尺寸的位图和对应的DC
    (define bmp (make-object bitmap% (* *scale* *width*) (* *scale* *height*)))
    (define bmp-dc (make-object bitmap-dc% bmp))
    ; 初始化位图DC的画笔
    (send bmp-dc set-pen black-pen)

    (define/override (on-char event)
      ; 原按键逻辑不变
      (begin
             (fprintf (current-error-port)
                      "Received key event: ~a, quitting\n" event)
             (set! *end-time* (current-inexact-milliseconds))
             (println (format "FPS: ~a"
                              (/ (* 1000 *counted-frames*)
                                 (- *end-time* *start-time*))))
             (exit)))

    (define/override (on-paint)
      (set! *counted-frames* (+ 1 *counted-frames*))
      (let ((dc (get-dc)))
        ; 先清空位图
        (send bmp-dc clear)
        ; 批量绘制方格到位图DC
        (for* ([x (in-range *width*)] [y (in-range *height*)])
          (send bmp-dc set-brush (if (get-element x y) black-brush white-brush))
          (send bmp-dc draw-rectangle (* x *scale*) (* y *scale*) *scale* *scale*))
        ; 一次性把位图绘制到画布
        (send dc draw-bitmap bmp 0 0))))

3. 优化计时器间隔与刷新逻辑

原计时器设置1ms间隔,系统无法做到这么高频的触发,反而会导致线程阻塞。目标60FPS的话,把计时器间隔设为16ms(1000/60≈16),同时把refresh-now改成refresh,让GUI系统自行处理刷新时机,减少强制刷新的开销:

(define *timer*
  (new timer%
       [notify-callback
        (lambda ()
          (update-grid!)
          (send *canvas* refresh)
          (flush-output))]))

; 在run函数里启动计时器
(send *timer* start 16)

4. 优化网格更新逻辑

原update-grid!里逐个修改vector元素,可以改用加密随机字节生成批量布尔值,效率更高:

(require racket/bytes)
(define (update-grid!)
  (let ([bytes (crypto-random-bytes (* *height* *width*))])
    (set! *grid* (for/vector ([b (in-bytes bytes)])
                   (even? b)))))

完整优化后的代码

#lang racket

(require racket/gui)
(require racket/random)
(require racket/bytes)

(define *width* 64)
(define *height* 32)
(define *scale* 10)
(define *counted-frames* 0)
(define *start-time* 0)
(define *end-time* 0)

; 预定义画笔和画刷,避免重复创建
(define black-pen (new pen% [color "black"] [width 1] [style 'solid]))
(define black-brush (new brush% [color "black"] [style 'solid]))
(define white-pen (new pen% [color "white"] [width 1] [style 'solid]))
(define white-brush (new brush% [color "white"] [style 'solid]))

(define *timer*
  (new timer%
       [notify-callback
        (lambda ()
          (update-grid!)
          (send *canvas* refresh)
          (flush-output))]))

(define *grid*
  (for/vector ([i (in-range (* *height* *width*))])
    #f))

; 优化网格更新:用加密随机字节生成,效率更高
(define (update-grid!)
  (let ([bytes (crypto-random-bytes (* *height* *width*))])
    (set! *grid* (for/vector ([b (in-bytes bytes)])
                   (even? b)))))

(define (get-element x y)
  (vector-ref *grid* (+ (* y *width*) x)))

(define *frame*
  (new frame% [label "Checkboard Grid"]
       [width (* *scale* *width*)]
       [height (* *scale* *height*)]))

(define rect-canvas%
  (class canvas%
    (inherit get-dc)
    (super-new)
    ; 创建位图和对应DC,用于批量绘制
    (define bmp (make-object bitmap% (* *scale* *width*) (* *scale* *height*)))
    (define bmp-dc (make-object bitmap-dc% bmp))
    ; 初始化位图DC的画笔
    (send bmp-dc set-pen black-pen)

    (define/override (on-char event)
      (begin
             (fprintf (current-error-port)
                      "Received key event: ~a, quitting\n" event)
             (set! *end-time* (current-inexact-milliseconds))
             (println (format "FPS: ~a"
                              (/ (* 1000 *counted-frames*)
                                 (- *end-time* *start-time*))))
             (exit)))

    (define/override (on-paint)
      (set! *counted-frames* (+ 1 *counted-frames*))
      (let ((dc (get-dc)))
        (send bmp-dc clear)
        ; 批量绘制方格到位图
        (for* ([x (in-range *width*)] [y (in-range *height*)])
          (send bmp-dc set-brush (if (get-element x y) black-brush white-brush))
          (send bmp-dc draw-rectangle (* x *scale*) (* y *scale*) *scale* *scale*))
        ; 一次性绘制位图到画布
        (send dc draw-bitmap bmp 0 0))))))

(define *canvas* (new rect-canvas% [parent *frame*]))

(define (run)
  (set! *start-time* (current-inexact-milliseconds))
  (send *frame* show #t)
  ; 60FPS对应的间隔约16ms
  (send *timer* start 16))

(run)

这些优化后,在M1 MacBook上应该能轻松达到60FPS以上,核心思路是减少DC的重复调用、改用批量绘制、优化更新逻辑和合理设置计时器间隔。

内容的提问来源于stack exchange,提问作者Unsatisfied Zebra

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 19:53:12