美文网首页
lua string 源码浅析

lua string 源码浅析

作者: fooboo | 来源:发表于2020-01-15 21:45 被阅读0次

是这样的,早之前是准备分析下这块的,因为从C/C++转到写lua,虽然上手很容易但是用好不容易,我见过好些业务代码写的其实一般般,不会过多考虑到更深层次的问题。当然,用lua肯定是出于某些业务方面的考虑,大部分不需要考虑底层实现。比如招写C++同学的很难,不小心就线上宕机不可控,再然后线上的热更新不大好支持,需要热重启,或者出现死循环这种怎么跳出来等等。

选择某种框架和开发语言,有当时的业务背景,所以怎么合适怎么来,没有必要感觉这种技术很low什么的。很多项目最后都没机会上线,所以这里分清主次。另外,框架这种设计因素较多这里不再说明,自己知道就行。

这里总结下这个月的工作情况。这一月份开始事情就不多了,很少新的需求,大部分时间是优化新项目用到的基础代码,没错,一七年底准备新项目我也在优化,虽然效果还不错,但并不满意,有几个古老的实现不能动。新项目这里我优化的模块跟具体业务需求没什么关系,毕竟现在还没有到这个地步,只是把基础的高频次使用的实现再次review。月初优化过相关的实现,服务器这边性能提高15-22%,客户端提升大概8%,后来增加某种技巧,使得很大程度上减小开销,这块后来消耗为零。新项目增加了碰撞检测功能,是用C++实现的,当然我也发现了内存泄露,C++中的泄露在LUA层是无法统计出来的。验证的话是容易,只要加快复现,因为原代码中每次new的是小对象,修改也容易,只不过让当初写这块的同学改了。优化了战斗中某些接口,因为是高频次调用,优化后比原来快4~5倍,当然和C/C++是无法比的,优化的事情还是要继续做,这种一般借助工具或经验,对于我来说,每写相关的实现和review其他同学写的功能,就知道大概性能如何。

这应该说是我在今年一月份做的比较有意义的两件事情,一个是优化定时器,性能有一定的提升,二是发现新项目的内存泄漏,和多余的性能开销。

lua语言是值得研究和学习的,我也考虑过这个事情,无奈时间安排不过来,只是在工作中用到基本的,没有深入其他一些源码实现。虽然平时主要写需求,但常用的还是字符串和表等这几种数据结构,怎么用和用好,还是要了解下怎么实现的,这里,以使用为出发点来简单介绍下string的实现,可能我分析的不够清晰和深入,有兴趣可以自己看下源码,至于代码怎么写,还是要注意下,先把功能写对,再profile找热点再优化。

对于lua的其他字符串的相关实现这里不再分析,有点杂,等使用到的时候有热点时再分析,当然还是减少那种关于字符串匹配查找替换等的操作。

这里分析的代码直接是lstring中的,有些太细节和全局性的实现跳过,后期有时间再一一分析。
字符串结构:

299 /*
300 ** Header for string value; string bytes follow the end of this structure
301 ** (aligned according to 'UTString'; see next).
302 */
303 typedef struct TString {
304   CommonHeader;
305   lu_byte extra;  /* reserved words for short strings; "has hash" for longs */
306   lu_byte shrlen;  /* length for short strings */
307   unsigned int hash;
308   union {
309     size_t lnglen;  /* length for long strings */
310     struct TString *hnext;  /* linked list for hash table */
311   } u; 
312 } TString;

创建一个新的字符串:

230 /*
231 ** Create or reuse a zero-terminated string, first checking in the
232 ** cache (using the string address as a key). The cache can contain
233 ** only zero-terminated strings, so it is safe to use 'strcmp' to
234 ** check hits.
235 */
236 TString *luaS_new (lua_State *L, const char *str) {
237   unsigned int i = point2uint(str) % STRCACHE_N;  /* hash */
238   int j;
239   TString **p = G(L)->strcache[i];
240   for (j = 0; j < STRCACHE_M; j++) {
241     if (strcmp(str, getstr(p[j])) == 0)  /* hit? */
242       return p[j];  /* that is it */
243   }
244   /* normal route */
245   for (j = STRCACHE_M - 1; j > 0; j--)
246     p[j] = p[j - 1];  /* move out last element */
247   /* new element is first in the list */
248   p[0] = luaS_newlstr(L, str, strlen(str));
249   return p[0];
250 }

以上会先从53*2的二维数组cache中查找是否已存在相同的string,有的话直接返回并不创建新的。否则创建新的并cache:

213 /*
214 ** new string (with explicit length)
215 */
216 TString *luaS_newlstr (lua_State *L, const char *str, size_t l) {
217   if (l <= LUAI_MAXSHORTLEN)  /* short string? */
218     return internshrstr(L, str, l);
219   else {
220     TString *ts;
221     if (l >= (MAX_SIZE - sizeof(TString))/sizeof(char))
222       luaM_toobig(L);
223     ts = luaS_createlngstrobj(L, l);
224     memcpy(getstr(ts), str, l * sizeof(char));
225     return ts;
226   }
227 }

对于长度小于等于40的string来说,可能是直接通过hash查找并复用,并不会直接创建:

525 static TString *
526 internshrstr (lua_State *L, const char *str, size_t l) {
527   TString *ts;
528   global_State *g = G(L);
529   unsigned int h = luaS_hash(str, l, g->seed);                                                              
530   unsigned int h0;
531   // lookup global state of this L first
532   ts = queryshrstr (L, str, l, h);
533   if (ts)
534     return ts;
535   // lookup SSM again
536   h0 = luaS_hash(str, l, 0);
537   ts = query_string(NULL, h0, str, l);
538   if (ts)
539     return ts;
540   // If SSM.n greate than 0, add it to SSM
541   if (SSM.n > 0) {
542     ATOM_DEC(&SSM.n);
543     return add_string(h0, str, l);
544   } 
545   // Else add it to global state (local)
546   return addshrstr (L, str, l, h);
547 }

174 /*
175 ** checks whether short string exists and reuses it or creates a new one                                    
176 */
177 static TString *queryshrstr (lua_State *L, const char *str, size_t l, unsigned int h) {
178   TString *ts;
179   global_State *g = G(L);
180   TString **list = &g->strt.hash[lmod(h, g->strt.size)];
181   lua_assert(str != NULL);  /* otherwise 'memcmp'/'memcpy' are undefined */
182   for (ts = *list; ts != NULL; ts = ts->u.hnext) {
183     if (l == ts->shrlen &&
184         (memcmp(str, getstr(ts), l * sizeof(char)) == 0)) {
185       /* found! */
186       if (isdead(g, ts))  /* dead (but not collected yet)? */
187         changewhite(ts);  /* resurrect it */
188       return ts;
189     }
190   }
191   return NULL;
192 }

以上查找会查两遍,这里query_string会加锁rwlock_rlock这些,接着add_string:

504 static TString *
505 add_string(unsigned int h, const char *str, lu_byte l) {
506         struct shrmap * s = &SSM;
507         TString * tmp = new_string(h, str, l);
508         rwlock_rlock(&s->lock);
509                 struct TString *ts = shrstr_exist(s, h, str, l);
510                 if (ts) {
511                         // string is create by other thread, so free tmp
512                         free(tmp);
513                 } else {
514                         struct shrmap_slot *slot = &s->readwrite[h & s->mask];
515                         ts = tmp;
516                         ts->u.hnext = slot->str;
517                         slot->str = ts;
518                         rwlock_wunlock(&slot->lock);
519                         ATOM_INC(&SSM.total);
520                 }
521         rwlock_runlock(&s->lock);
522         return ts;
523 }

若都没的话则创建addshrstr:

194 static TString *addshrstr (lua_State *L, const char *str, size_t l, unsigned int h) {
195   TString *ts;
196   global_State *g = G(L);
197   TString **list = &g->strt.hash[lmod(h, g->strt.size)];
198   if (g->strt.nuse >= g->strt.size && g->strt.size <= MAX_INT/2) {
199     luaS_resize(L, g->strt.size * 2);
200     list = &g->strt.hash[lmod(h, g->strt.size)];  /* recompute with new size */
201   }
202   ts = createstrobj(L, l, LUA_TSHRSTR, h);
203   memcpy(getstr(ts), str, l * sizeof(char));
204   ts->shrlen = cast_byte(l);
205   ts->u.hnext = *list;
206   *list = ts;
207   g->strt.nuse++;
208   return ts;
209 }

130 /*
131 ** creates a new string object
132 */
133 static TString *createstrobj (lua_State *L, size_t l, int tag, unsigned int h) {
134   TString *ts;
135   GCObject *o;
136   size_t totalsize;  /* total size of TString object */
137   totalsize = sizelstring(l);
138   o = luaC_newobj(L, tag, totalsize);
139   ts = gco2ts(o);
140   ts->hash = h;
141   ts->extra = 0;
142   getstr(ts)[l] = '\0';  /* ending 0 */
143   return ts;
144 }

以上是当字符串长度小于等于40时才这样,否则直接创建:

157 TString *luaS_createlngstrobj (lua_State *L, size_t l) {                                                    
158   TString *ts = createstrobj(L, l, LUA_TLNGSTR, LNGSTR_SEED);
159   ts->u.lnglen = l;
160   return ts;
161 }

从createstrobj中并没有看到为str分配空间并拷贝的地方,并且TString结构中无明显的空间,如注释所说“string bytes follow the end of this structure”,这里的实现如弹性数组"struct dummy {size_t len; char value[0];};"这种。

其他的实现诸如:

 205 /*
 206 ** create a new collectable object (with given type and size) and link
 207 ** it to 'allgc' list.
 208 */
 209 GCObject *luaC_newobj (lua_State *L, int tt, size_t sz) {                                                  
 210   global_State *g = G(L);
 211   GCObject *o = cast(GCObject *, luaM_newobject(L, novariant(tt), sz));
 212   o->marked = luaC_white(g);
 213   o->tt = tt;
 214   o->next = g->allgc;
 215   g->allgc = o;
 216   return o;
 217 }
324 /*
325 ** Get the actual string (array of bytes) from a 'TString'.
326 ** (Access to 'extra' ensures that value is really a 'TString'.)
327 */
328 #define getstr(ts)  \                                                                                       
329   check_exp(sizeof((ts)->extra), cast(char *, (ts)) + sizeof(UTString))
 194 void luaC_fix (lua_State *L, GCObject *o) {
 195   global_State *g = G(L);
 196   if (g->allgc != o)
 197           return;  /* if object is not 1st in 'allgc' list, it is in global short string table */
 198   white2gray(o);  /* they will be gray forever */                                                          
 199   g->allgc = o->next;  /* remove object from 'allgc' list */
 200   o->next = g->fixedgc;  /* link it to 'fixedgc' list */
 201   g->fixedgc = o;
 202 }

这里涉及的知识较多,没细分。比如垃圾回收相关的。

总结下,虽然lua中的string使用相对于其他语言来说,更方便,更可控,不需要考虑资源的泄露这些,但如果不了解这些实现,可能用的不够好。之前项目线上出现一个事故,就是因为使用第三方库时,因为提供给这边的接口是const修饰,表示不会修改外部参数,所以我当时在优化的时候,直接把中间的拷贝串给删除。然后直接把lua层的const char* 传递到那边接口,而那边接口居然改了我传的指针内容,导致整个进程,即多个luavm中的该字符都被替换成***,当然接口是否规范是另一回事。

所以个人感觉,还是更偏向于C/C++的使用,当然还是要看自己对这块的认知和熟悉程序,当然在项目中,对于语言工具的使用按照项目来。有些同学只停留在会用,并不会去了解一些实现甚至如何更好的使用,可能成长或许会少一些吧,猜测?

后续再简单分析下lua中的table实现,如何高效使用,以上的这两个结构对lua的回收也是有一定的影响,lua中其他的东西以后有时间再慢慢分析吧,我也是刚有点时间看看,可能有些不对的地方,欢迎指出来讨论。

相关文章

网友评论

      本文标题:lua string 源码浅析

      本文链接:https://www.haomeiwen.com/subject/ljkxzctx.html