标签:stop index 性能 ring 工作 clear www ++ 基本
最近闲来无事,记记unity3D相关的一些知识点吧,也当作笔记存储。转载请标明出处:http://www.cnblogs.com/zblade/
1、unity是如何调用Start/Awake等相关函数的?
在unity中,一个常见的问题是awake, start, update等相关函数的执行顺序,这个就不在这儿赘述了,一个比较深入的问题,是如何调用这些函数的。如果是虚函数的重载,那么我们为什么没有override关键字?我查阅了一下,知乎上有一个相关问题,大概是2个方向的意见。一个是源代码显示,unity的mono支持对函数名进行字符串获取,在获取到这些函数名后,再进行反射调用。其实说的更浅白一点,就是继承自mono的类,unity都会对其中的函数进行引用统计,在每帧调用的时候,对于非空的函数,都会执行一次遍历反射回调。很多人觉得反射对于性能影响比较大,其实可以用缓存的方式,在第一次反射执行后,缓存下来,下一次执行的之后可以直接从缓存中获取,直接执行,所以很多人测试发现反射的性能接近于函数调用,就是这个原理。
2、图集的原理及使用图集的原因?
图集的本质,其实就是一张大图,将各个图集中的小图合并到一张大图,然后还有一份保存各个小图的尺寸、位置、偏移等信息的数据文件,所以一般一个图集会对应2个文件,当然如果把数据文件也打包进去,就会只有一个数据文件。
使用图集的原因,有这么几点:首先,使用图集可以方便的管理图片资源;其次,在每次绘制一张图的时候,都会在GPU阶段送入一张图片进去,这样的一次操作就会触发一次DrawCall,如果有几十上百个图,那么每次在转换的时候,都会触发多次DrawCall,使用图集,可以只触发一次DrawCall,把所有相关的图片都塞入进去,从而大大降低DrawCall;最后使用图集也方便图片资源的加载和卸载。
3、lua相关的一些问题
1)lua中table实现的原理
lua对于table的设计,是基于数组和hash共同兼容的,对于数组,其主要存储连续的同类型数据,hash则通过key-value的方式存储。对于hash和数组,默认大小都是0,然后是1,2,4等等基于2的幂次递增,由于每次递增的时候,都会进行一次rehash,所以性能都消耗在rehash上,所以在创建table的时候,尽量避免这样rehash的操作,比如:
local t1 = {} t["x"] =1, t["y" ] =2 , t["z"] = 3, 这样三次操作,就会触发三次rehash,想想原理即可明白
local t2 = {"x" = 1, "y" = 2, "z" = 3}, 这样只会触发一次rehash,对比节省3次性能。
2)请回答lua中对于key值的查找过程
lua对于key值的查找,首先会去数组和hash中查找对应的key值,如果存在,则返回;如果不存在,则查找该table是否有元表metatable,如果没有,则返回nil;如果有,则查看metatable中是否有__index方法,如果没有,则返回nil;如果有,则执行__index[key]查找,返回对应的值。
3)lua如何执行GC,以及对应的原理和API设置
lua GC的原理,推荐大家阅读一下云风的blog中对于GC的详细过程的阐述,结合源代码,有较为详细的讲解过程:Lua GC的源码剖析
这儿做一个读后笔记记录吧:
(1) Lua GC对象
lua中一共有9种数据类型,分别为nil, boolean, lightuserdata, number, string, table, function, userdata 和 thread。其中, string, table, function, thread会被GC处理,此外还有proto和upvalue需要被GC处理。
(2) Lua 数据定义方式 union + type
//Union of all Lua values
typedef union
{
GCObject* gc;
void* p;
lua_Number n;
int b;
}Value;
#define TValuefields Value value;
int tt
typedef struct lua_TValue
{
TValuefields;
}TValue
所有的GCObject都有一个相同的数据头,CommonHeader,其定义为:
#define CommonHeader GCObject* next; lu_byte tt; lu_byte marked;
这样所有的GCObject都会被同一个单向链表串接起来,每个对象基于tt识别,marked用来标记清除的工作
(3) Lua 对不同类型的清除操作分类
Lua在每次GC清除的的时候,分为多种类型:
对于GCObject,通过若干根节点开始,逐个直接或者间接的将其上的所有节点左上标记,完成标记后,遍历链表,对未被标记的节点执行删除操作;
对于string类型,由于所有的string都放在一张大的hash表中,这样是为了确保整个lua中同一个string不会被创建两份,所以其是被单独管理的,不会被串在GCObject的链表中
对于upvalue类型数据,也是一个特殊处理过程,这是由于GC可能分布扫描,由于upvalue是对已有的对象的间接引用,在创建的时候不属于创建新数据,在mark的过程中需要添加luaC_barrier
对于userdata, 由于userdata都有gc方法,所以会在最后单独处理逐一遍历所有的userdata来执行其中的gc方法,会有一些特殊的处理
(4) Lua执行GC的几个流程
Lua执行GC的几个流程,可以分为5步: GCSpause\ GCSpropagate\GCSsweepstring\GCSsweep\GCSfinalize,从lua5.1 开始就执行分布GC,每次执行可能会有多个状态切换
GCSpause 为GC阶段的启动流程,标记系统的根节点即可
GCSpropagate 这是标记流程,对尚未标记的对象(灰色链表)迭代标记(反复调用propagatemark),否则在atomic函数中执行一次标记
GCSsweepstring 这就是前面提起的对string类型的数据,进行特殊的处理,在这个状态中,每步都会清除string的hash表中的一列
GCSsweep 和上一个状态类是,不过这步操作的对象是GCObject
GCSfinalize 在这儿主要对userdata执行,如果需要调用其gc,则执行gc操作,由于userdata的对象和关联数据不会在之前的清除阶段被清除,所以其实际清除会在下一次的GC清除中执行或者在lua_close中被清除:lua_close的工作就是简单的处理所有userdata的gc元方法,以及释放其所用到的内存。
(5) Lua GC的标记流程
Lua对于所有的GCObject都设置一个颜色,最开始是白色,新建的节点也是白色,然后在标记阶段,可见的节点被设置为黑色,如果某些节点关联其他节点,在没有处理完其关联节点前,都被标记为灰色,对于颜色的标记,其存储在CommonHeader的8位的marked域中,对于白色有两个白色的标记位,采用一种乒乓开关,避免在标记完成后,清理没有完成前,对象间关系发生变化的时候,某些不需要被清理的节点,就可以从一种类型的白色转换到另一种类型的白色中,比如当前删除0型白色,那么转换到1型白色,这样1型白色就会被保护起来不会被删除,反之亦然。具体对于8个位的定义和使用,可以看云风的原文,有一定讲解。
(6) Lua GC的操作
常用的几个API: luaC_fullgc\ luaC_step\luaC_checkGC
luaC_fullgc: 执行一次完整的gc动作,对于可能执行一般的流程,在走完一次流程后,会阻塞状态再次执行一遍gc,对于已经执行的前半程gc,其实不需要做清除操作,只需要做状态回复
luaC_step: 其核心在与调用singlestep函数,通过设置gcstepmul值,可以设置步长,从而影响gcthreshold,其实步进量的设置,是一个经验值
luaC_checkGC: 自动GC的接口,在大部分导致内存增长的api中会调用该方法,自动GC,可能会在某一个周期性中将众多临时对象也mark了,造成系统的峰值内存占用比实际需求大,可以在这种周期性调用中采用gcstep的方法,同时设置较大的data量,使得有限周期做一个完整的gc。
(7) Lua GC的mark操作
对于Lua的mark操作,主要操作的API: markroot\ reallymarkobject\remarkupvals\atomic\iscleared
(8) Lua GC的write barrier操作
主要的API: luaC_barrier\luaC_barriert\luaC_objbarrier\luaC_objbarriert
(9) Lua GC的剩余操作 sweep/finalize
sweep的操作分为 GCSsweepstring 和GCSseep, 贴2个源码:
case GCSsweepstring: {
lu_mem old = g->totalbytes;
sweepwholelist(L, &g->strt.hash[g->sweepstrgc++]);
if (g->sweepstrgc >= g->strt.size) /* nothing more to sweep? */
g->gcstate = GCSsweep; /* end sweep-string phase */
lua_assert(old >= g->totalbytes);
g->estimate -= old - g->totalbytes;
return GCSWEEPCOST;
}
case GCSsweep: {
lu_mem old = g->totalbytes;
g->sweepgc = sweeplist(L, g->sweepgc, GCSWEEPMAX);
if (*g->sweepgc == NULL) { /* nothing more to sweep? */
checkSizes(L);
g->gcstate = GCSfinalize; /* end sweep phase */
}
lua_assert(old >= g->totalbytes);
g->estimate -= old - g->totalbytes;
return GCSWEEPMAX*GCSWEEPCOST;
}
对于seeplist,其源代码为:
static GCObject **sweeplist (lua_State *L, GCObject **p, lu_mem count) {
GCObject *curr;
global_State *g = G(L);
int deadmask = otherwhite(g);
while ((curr = *p) != NULL && count-- > 0) {
if (curr->gch.tt == LUA_TTHREAD) /* sweep open upvalues of each thread */
sweepwholelist(L, &gco2th(curr)->openupval);
if ((curr->gch.marked ^ WHITEBITS) & deadmask) { /* not dead? */
lua_assert(!isdead(g, curr) || testbit(curr->gch.marked, FIXEDBIT));
makewhite(g, curr); /* make it white (for next cycle) */
p = &curr->gch.next;
}
else { /* must erase `curr‘ */
lua_assert(isdead(g, curr) || deadmask == bitmask(SFIXEDBIT));
*p = curr->gch.next;
if (curr == g->rootgc) /* is the first element of the list? */
g->rootgc = curr->gch.next; /* adjust first */
freeobj(L, curr);
}
}
return p;
}
基本看源代码就可以理解,对于dead的freeobj,没有dead的则执行makewhite,最后一个流程就是GCS finalize,通过GCTM函数执行,每次调用一个需要回收的userdata的gc元方法:
static void GCTM (lua_State *L) {
global_State *g = G(L);
GCObject *o = g->tmudata->gch.next; /* get first element */
Udata *udata = rawgco2u(o);
const TValue *tm;
/* remove udata from `tmudata‘ */
if (o == g->tmudata) /* last element? */
g->tmudata = NULL;
else
g->tmudata->gch.next = udata->uv.next;
udata->uv.next = g->mainthread->next; /* return it to `root‘ list */
g->mainthread->next = o;
makewhite(g, o);
tm = fasttm(L, udata->uv.metatable, TM_GC);
if (tm != NULL) {
lu_byte oldah = L->allowhook;
lu_mem oldt = g->GCthreshold;
L->allowhook = 0; /* stop debug hooks during GC tag method */
g->GCthreshold = 2*g->totalbytes; /* avoid GC steps */
setobj2s(L, L->top, tm);
setuvalue(L, L->top+1, udata);
L->top += 2;
luaD_call(L, L->top - 2, 0);
L->allowhook = oldah; /* restore hooks */
g->GCthreshold = oldt; /* restore threshold */
}
}
在回收的时候,设置较大的GCthreshold来避免GC的重入
4、unity中协程的理解
协程的本质是一个分部执行函数,在unity的mainThread中执行,unity在每帧的更新中,都会执行各个协程调用,分别在FixedUpdate和LateUpdate之后的一些协程调用上,其本质就是一个迭代器,当遇到条件不满足的时候会被挂起,条件满足的时候,会被唤醒来继续执行。举个在其他地方看到的例子吧,这样便于讲解过程:
void Start() { StartCoroutine(Test1()); } IEnumerator Test1() { LogWrapper.Error("a1"); yield return Test2(); LogWrapper.Error("a2"); } IEnumerator Test2() { LogWrapper.Error("b1"); yield return null; LogWrapper.Error("b2"); }
会输出什么的顺序:a1, b1, b2, a2
执行的顺序是先输出a1,然后执行Test2,输出b1,这时候遇到yield return null ,被挂起,在下一帧,被唤醒,继续执行,输出b2, 接着执行输出a2
如果对这个过程理解了,那么协程基本就没问题了
5、unity中meta文件的作用
有两个作用,第一是包含了当前资源(代码或者prefab, 图片等)在当前工程中唯一的guid, unity获取资源是依据guid来获取的,所以每个资源都会附带生成一份meta文件;
第二个,包含了当前资源的导入信息,比如图片资源,会包含一下bump等相关的信息
未完待续,持续更新ing
标签:stop index 性能 ring 工作 clear www ++ 基本
原文地址:https://www.cnblogs.com/zblade/p/8760029.html