Lua性能优化和思考

9-27 459 views

往往我们进行性能优化, 首先去搜索别人成果,然后想当然去应用, 优秀点的测试一下, 然后改完收工.

这里吐槽一句, 搜索优化类文章大多直接贴结论或者罗列测试数据,看见能有理有据深入浅出娓娓道来的真是快乐无比.(一个趟过的坑)

如果想进行知识累计, 最好知其然知其所以然, 浅尝辄止不但会因为一则莫名其妙的新”规则” 扼杀了兴趣, 而且所谓优化还可能是错误的 .


这对于Lua一些常用优化, 对比源码来看会清楚许多, 如:用local, 提前填充table元素, insert效率, 字符串拼接…和table.concat选择, pairs和ipairs选择……

使用local保存全局变量的引用

Lua使用的是基于寄存器的虚拟机(使用一个栈来提供寄存器),所有的local变量都会保存在寄存器里,因此访问local变量的速度会快于全局变量。在Lua的源码中可以看到对局部变量的最大数量有200的限制:

/*
@@ LUAI_MAXVARS is the maximum number of local variables per function
@* (must be smaller than 250).
*/
#define LUAI_MAXVARS            200

table.insert,还是t[#t+1] = xxx

/* ltablib.c */

static int tinsert (lua_State *L) {
  int e = aux_getn(L, 1) + 1;  /* first empty element */
  int pos;  /* where to insert new element */
  switch (lua_gettop(L)) {
    case 2: {  /* called with only 2 arguments */
      pos = e;  /* insert new element at the end */
      break;
    }
    case 3: {
      int i;
      pos = luaL_checkint(L, 2);  /* 2nd argument is the position */
      if (pos > e) e = pos;  /* grow array if necessary */
      for (i = e; i > pos; i--) {  /* move up elements */
        lua_rawgeti(L, 1, i-1);
        lua_rawseti(L, 1, i);  /* t[i] = t[i-1] */
      }
      break;
    }
    default: {
      return luaL_error(L, "wrong number of arguments to " LUA_QL("insert"));
    }
  }
  luaL_setn(L, 1, e);  /* new size */
  lua_rawseti(L, 1, pos);  /* t[pos] = v */
  return 0;
}

在向其末尾插入数据时,每次插入数据都会调用aux_getn方法(会调用luaH_getn),用于获取table的长度。事实上#这个运算符(OP_LEN)也是调用的luaH_getn。也就是说其实table.insert的内部会有一次类似于t[#t+1] = xxx这样的操作。以下是luaH_getn的实现:

/*
** Try to find a boundary in table `t'. A `boundary' is an integer index
** such that t[i] is non-nil and t[i+1] is nil (and 0 if t[1] is nil).
*/
int luaH_getn (Table *t) {
  unsigned int j = t->sizearray;
  if (j > 0 && ttisnil(&t->array[j - 1])) {
    /* there is a boundary in the array part: (binary) search for it */
    unsigned int i = 0;
    while (j - i > 1) {
      unsigned int m = (i+j)/2;
      if (ttisnil(&t->array[m - 1])) j = m;
      else i = m;
    }
    return i;
  }
  /* else must find a boundary in hash part */
  else if (t->node == dummynode)  /* hash part is empty? */
    return j;  /* that is easy... */
  else return unbound_search(t, j);
}

首先是使用二分查找来获取array部分的边界(array部分长度总是2的倍数,没有数据的部分会被nil填充),否则如果没有hash部分(array部分为空或者array部分刚好填满),直接返回array长度,再否则就调用unbound_search(查找hash部分)。整体来看这个获取长度的开销还是不小的,因此缓存下来长度,并且在插入数据时直接以n+1为key,是一种很高效的做法。


本文持续更新…

参考 :
https://aillieo.cn/post/2018-09-13-lua-notes-03/

https://www.cnblogs.com/zwywilliam/

Lua常用堆栈操作

要擅于去看lua文档,源码, 部分翻译来自云风, ps C#也开源了 :) 正数表示相对于栈底的位置,栈底是1 ,负数表示相对于栈顶的位置,栈顶是-1; 入栈...

阅读全文

欢迎留言