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

p5.js中函数首次调用耗时远高于后续调用的原因探究

瓦片棋盘绘制性能测试:首次调用耗时远高于后续的原因分析

我在测试瓦片棋盘的绘制性能,有两个可选方案:逐块绘制瓦片,或者先把瓦片填充到图像中再绘制静态图。绘制静态图的速度比逐块绘制快很多,但我发现函数首次调用的耗时明显高于后续调用,是后续的4-10倍,想知道这个现象的原因是什么?

测试代码

let img;
let NROWS = 55;
let NCOLS = 29;
const TILE_WIDTH_HALF = 32;
const TILE_HEIGHT_HALF = 16;
const HALF_WIDTH = 950;
const HALF_HEIGHT = 450;
let start, end;
let bg;

function preload() {
  img = loadImage("tile.png");
}

// Populates the background graphics
function populateBackground() {
  bg = createGraphics(HALF_WIDTH*2, HALF_HEIGHT*2);
  for (let row=0; row<NROWS; row++) {
    for (let col=0; col<NCOLS; col++) {
      x = col*TILE_WIDTH_HALF*2 + (TILE_WIDTH_HALF * (row%2)+1)
      y = row*TILE_HEIGHT_HALF;
      bg.image(img, x, y);
    }
  }
}

// draws tile by tile into the canvas
function drawTileByTile() {
  background(100);  
  start = millis();
  for (let row=0; row<NROWS; row++) {
    for (let col=0; col<NCOLS; col++) {
      x = col*TILE_WIDTH_HALF*2 + (TILE_WIDTH_HALF * (row%2)+1) - HALF_WIDTH;
      y = row*TILE_HEIGHT_HALF - HALF_HEIGHT;
      image(img, x, y);
    }
  }
  end = millis();
  return (end-start);  
}

// draws the prepopulated graphics as a single image
function drawAsImage() {
  background(100);
  start = millis();
  image(bg,-HALF_WIDTH,-HALF_HEIGHT);
  end = millis();
  return (end-start);  
}

function setup() 
{
  createCanvas(HALF_WIDTH*2, HALF_HEIGHT*2, WEBGL);
  populateBackground();

  // Draw tile by tile 10 times
  let elapsed_ms = [];
  for (let n=0; n<10; n++) {
    elapsed_ms.push(drawTileByTile().toFixed(3));
  }
  console.log(elapsed_ms);
  
  // Draw as image 10 times
  elapsed_ms = [];
  for (let n=0; n<10; n++) {
    elapsed_ms.push(drawAsImage().toFixed(3));
  }
  console.log(elapsed_ms);

  noLoop();
}

function draw()
{
  
}

测试耗时数据

sketch.js:61 (10) ['114.400', '35.400', '37.000', '31.800', '54.200', '51.900', '33.000', '34.400', '30.200', '31.200']
sketch.js:68 (10) ['1.000', '0.100', '0.100', '0.000', '0.100', '0.000', '0.000', '0.000', '0.000', '0.000']

原因分析

这个现象核心是GPU的资源初始化与缓存机制在起作用,具体可以拆解成几点:

  • 首次调用的资源初始化开销:第一次调用image()绘制时,GPU需要完成一系列一次性的准备工作——比如把图像数据从CPU内存上传到GPU显存、创建纹理对象、配置渲染管线的参数(像着色器、纹理采样方式)。这些操作都需要消耗时间,后续调用时已经完成了初始化,就不用再重复执行。
  • GPU缓存复用:后续绘制时,GPU会把已经加载好的纹理、渲染状态等数据缓存起来,直接复用这些缓存资源就能快速完成绘制,省去了重新处理资源的流程。
  • WebGL上下文预热:因为你用了WEBGL模式创建画布,第一次执行绘制操作时,WebGL上下文本身也需要启动和预热,比如初始化默认着色器、设置视口、配置渲染环境等,这些都会拉高首次调用的耗时,之后上下文处于就绪状态,操作效率就会大幅提升。

另外看你测试里的drawAsImage,首次耗时1ms,后续几乎为0,也完全符合这个逻辑:第一次把预生成的bg纹理上传到GPU,之后直接复用这个纹理绘制,几乎没有额外开销。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 21:32:48