Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

优化lua方法的调用方式 #210

Open
mybios opened this issue Apr 15, 2019 · 12 comments
Open

优化lua方法的调用方式 #210

mybios opened this issue Apr 15, 2019 · 12 comments

Comments

@mybios
Copy link
Contributor

mybios commented Apr 15, 2019

现在生成的代码调用方式分几种:
1、静态方法
2、成员方法
3、属性方法
4、运算符方法
调用方式又分为:虚方法/属性、抽象方法/属性、接口实现方法、new覆盖方法等等

而这些调用,都免不了要通过lua的table hash查找的方式来找到具体的成员,这个过程是可以优化的。

我这边使用过这种方法来优化DLL中导出的所有方法调用:
1、使用一张全局表,来通过顺序数字下标来保存所有需要被调用的方法function
例如: GlobalMethodCache = {UnityEngine.GameObject.AddComponent , UnityEngine.GameObject.GetComponent}
2、在meta.xml中,通过Template对这些方法调用,统一改成下标索引调用,即:
local gameObject;
gameObject:AddComponent(xxx)
gameObject:GetComponent(xxx)
优化成
local GlobalMethodCache;
local gameObject;
GlobalMethodCache[1](gameObject, xxx)
GlobalMethodCache[2](gameObject, xxx)
3、通过这种方式,对于没有被new覆盖的函数,没有虚函数抽象函数可以通过这种方法能避免元表找方法等额外消耗,全部优化成一个upvalue获取(GlobalMethodCache)以及一个数组下表索引,从而达到最大性能优化,同时额外的好处是还做了代码混淆。
4、对于DLL的方法调用,例如Vector3等频繁调用,可以有大概1/3的性能提升
5、对于CSharp.lua生成的代码,假如所有代码,都通过这种方式优化,相信可以得到可观的提高。

@yanghuan
Copy link
Owner

#31 有人提到过,里面有详细的描述。

这种处理方式就算会实现,优先级也很低,可能会加入额外的编译参数来支持。
目前而言短期我应该不会处理。
它牺牲了大量的代码可读性,获得的性能收益可能也就20%-50%,我觉得不一定值得

@mybios
Copy link
Contributor Author

mybios commented Apr 16, 2019

准备编译成lua的C#源码:

// 矩形的中心与矩形外一点的连线与矩形的交点
public static Vector3 GetIntersectPointWithScreenRect(Vector2 localRectPos, Vector2 border)
{
    Vector2 ret = localRectPos;
    Vector2 center = Vector2.zero;
    Vector2 dir = localRectPos;

    float clampBorderWidth = Screen.width * 0.5f - border.x;
    float clampBorderHeight = Screen.height * 0.5f - border.y;

    if (Mathf.Abs(dir.x) < float.Epsilon || Mathf.Abs(dir.y) < float.Epsilon)
    {
        ret.x = Mathf.Clamp(ret.x, -clampBorderWidth, clampBorderWidth);
        ret.y = Mathf.Clamp(ret.y, -clampBorderHeight, clampBorderHeight);
        return ret;
    }

    float tanRect = clampBorderHeight / clampBorderWidth;


    float ptTan = Mathf.Abs(dir.y / dir.x);

    if (ptTan < tanRect)
    {
        ret = dir * clampBorderWidth / Mathf.Abs(dir.x);
    }
    else
    {
        ret = dir * clampBorderHeight / Mathf.Abs(dir.y);
    }

    return ret;

}

@mybios
Copy link
Contributor Author

mybios commented Apr 16, 2019

优化前的编译后lua代码

GetIntersectPointWithScreenRect = function (localRectPos, border)
  local ret = localRectPos:__clone__()
  local center = UnityEngine.Vector2.getzero()
  local dir = localRectPos:__clone__()

  local clampBorderWidth = UnityEngine.Screen.getwidth() * 0.5 - border:getx()
  local clampBorderHeight = UnityEngine.Screen.getheight() * 0.5 - border:gety()

  if UnityEngine.Mathf.Abs(dir:getx()) < 1.40129846432482E-45 --[[Single.Epsilon]] or UnityEngine.Mathf.Abs(dir:gety()) < 1.40129846432482E-45 --[[Single.Epsilon]] then
    ret:setx(UnityEngine.Mathf.Clamp0(ret:getx(), - clampBorderWidth, clampBorderWidth))
    ret:sety(UnityEngine.Mathf.Clamp0(ret:gety(), - clampBorderHeight, clampBorderHeight))
    return UnityEngine.Vector2.op_Implicit1(ret:__clone__())
  end

  local tanRect = clampBorderHeight / clampBorderWidth


  local ptTan = UnityEngine.Mathf.Abs(dir:gety() / dir:getx())

  if ptTan < tanRect then
    ret = UnityEngine.Vector2.op_Division(UnityEngine.Vector2.op_Multiply0(dir, clampBorderWidth), UnityEngine.Mathf.Abs(dir:getx()))
  else
    ret = UnityEngine.Vector2.op_Division(UnityEngine.Vector2.op_Multiply0(dir, clampBorderHeight), UnityEngine.Mathf.Abs(dir:gety()))
  end

  return UnityEngine.Vector2.op_Implicit1(ret:__clone__())
end

@mybios
Copy link
Contributor Author

mybios commented Apr 16, 2019

优化后(保留函数名注释):

GetIntersectPointWithScreenRect = function (localRectPos, border)
  local ret = localRectPos:__clone__()
  local center = __[5342]--[[UnityEngine.Vector2.getzero]]()
  local dir = localRectPos:__clone__()

  local clampBorderWidth = __[1017]--[[UnityEngine.Screen.getwidth]]() * 0.5 - __[5303]--[[UnityEngine.Vector2.getx]](border)
  local clampBorderHeight = __[1018]--[[UnityEngine.Screen.getheight]]() * 0.5 - __[5304]--[[UnityEngine.Vector2.gety]](border)

  if UnityEngine.Mathf.Abs(__[5303]--[[UnityEngine.Vector2.getx]](dir)) < 1.40129846432482E-45 --[[Single.Epsilon]] or UnityEngine.Mathf.Abs(__[5304]--[[UnityEngine.Vector2.gety]](dir)) < 1.40129846432482E-45 --[[Single.Epsilon]] then
    ret:setx(__[5163]--[[UnityEngine.Mathf.Clamp0]](__[5303]--[[UnityEngine.Vector2.getx]](ret), - clampBorderWidth, clampBorderWidth))
    ret:sety(__[5163]--[[UnityEngine.Mathf.Clamp0]](__[5304]--[[UnityEngine.Vector2.gety]](ret), - clampBorderHeight, clampBorderHeight))
    return __[5337]--[[UnityEngine.Vector2.op_Implicit1]](ret:__clone__())
  end

  local tanRect = clampBorderHeight / clampBorderWidth


  local ptTan = UnityEngine.Mathf.Abs(__[5304]--[[UnityEngine.Vector2.gety]](dir) / __[5303]--[[UnityEngine.Vector2.getx]](dir))

  if ptTan < tanRect then
    ret = __[5333]--[[UnityEngine.Vector2.op_Division]](__[5331]--[[UnityEngine.Vector2.op_Multiply0]](dir, clampBorderWidth), UnityEngine.Mathf.Abs(__[5303]--[[UnityEngine.Vector2.getx]](dir)))
  else
    ret = __[5333]--[[UnityEngine.Vector2.op_Division]](__[5331]--[[UnityEngine.Vector2.op_Multiply0]](dir, clampBorderHeight), UnityEngine.Mathf.Abs(__[5304]--[[UnityEngine.Vector2.gety]](dir)))
  end

  return __[5337]--[[UnityEngine.Vector2.op_Implicit1]](ret:__clone__())
end

@mybios
Copy link
Contributor Author

mybios commented Apr 16, 2019

优化后(去掉注释)

GetIntersectPointWithScreenRect = function (localRectPos, border)
  local ret = localRectPos:__clone__()
  local center = __[5342]()
  local dir = localRectPos:__clone__()

  local clampBorderWidth = __[1017]() * 0.5 - __[5303](border)
  local clampBorderHeight = __[1018]() * 0.5 - __[5304](border)

  if UnityEngine.Mathf.Abs(__[5303](dir)) < 1.40129846432482E-45 --[[Single.Epsilon]] or UnityEngine.Mathf.Abs(__[5304](dir)) < 1.40129846432482E-45 --[[Single.Epsilon]] then
    ret:setx(__[5163](__[5303](ret), - clampBorderWidth, clampBorderWidth))
    ret:sety(__[5163](__[5304](ret), - clampBorderHeight, clampBorderHeight))
    return __[5337](ret:__clone__())
  end

  local tanRect = clampBorderHeight / clampBorderWidth


  local ptTan = UnityEngine.Mathf.Abs(__[5304](dir) / __[5303](dir))

  if ptTan < tanRect then
    ret = __[5333](__[5331](dir, clampBorderWidth), UnityEngine.Mathf.Abs(__[5303](dir)))
  else
    ret = __[5333](__[5331](dir, clampBorderHeight), UnityEngine.Mathf.Abs(__[5304](dir)))
  end

  return __[5337](ret:__clone__())
end

@yanghuan
Copy link
Owner

嗯,看起来确实可行
做的激进的话,最终会像js最小化代码一样,几乎不可读和调试,还需要map文件来对应调试信息,js是另一套工具单独处理,做到完善的话,代码量肯定工也不少

TypeScript 也没有做任何影响可读性的优化,CSharp.lua 初衷也是这样的,在不影响可读性的前提下,尽量的优化一些。在处理metadata时,我尝试过将__metadata__里面引用到的类型信息,都放到一张表里面导出到manifest.lua中,最终也是通过数组下标索引这些类型信息,发展确实对可读性影响较大,所以还是放弃了。

我也测试过查表和数组下标访问的效率差异,发现在jit和非jit的情况下,大致都可节约50%左右的时间,一方面确实有较大改善的空间,但是也看的出来lua查表确实也很快,并没有数量级的差异。

很多类似调用方式、函数内敛的优化可能是虚拟机层面更应该考虑的,luajit做的就很不错,如果可能的话就尽量使用luajit。

@mybios
Copy link
Contributor Author

mybios commented Apr 17, 2019

js的混淆,是为了混淆而混淆,lua里这里优化是为了性能而优化。
函数声明部分不需要修改,只需在函数体生成调用的地方统一收集起来,改成全局调用的方式即可。
lua查表的速度,取决于key的字符串大小,取决于表里重复hash的数量
至于jit / luajit,是另一层面的优化了。

@mybios
Copy link
Contributor Author

mybios commented Apr 17, 2019

另外,50%的性能提升,是非常可观的了。值得为此去写一大箩筐代码。

@yanghuan
Copy link
Owner

是的,确实是客观的提升
我预估可能需要全工时至少2周的时间,做完善并解决相关的bug。优化我一直放在次要的位置,相对可读性、功能完善等。我项目使用的luajit, 做的也是2d 卡牌,目前而言性能足够了,所以短期内不会实现。

@mybios
Copy link
Contributor Author

mybios commented Apr 17, 2019

我这边做的是FPS游戏,lua里有很多运算密集型的逻辑,已经不得不把大量逻辑改回在C#运行。但性能问题仍然很严峻,一帧耗时动不动就超过30ms

@yanghuan
Copy link
Owner

你说过目前使用的是lua 5.3, 可以考虑在嵌入个luajit运行主逻辑,跑跑看看,这个优化就算做了,也不确定能起到很大的帮助,而且确实需要一大箩筐的代码,目前精力也有限,欢迎提交PR。

@mybios
Copy link
Contributor Author

mybios commented Apr 17, 2019

嗯嗯,这会也在考虑用luajit来进一步优化。等版本档期不那么紧的时候,深入学下下CSharp.lua的源码。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants