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

直接在 cs 里调用 luaL_error 真的没问题么? #14

Closed
cloudwu opened this Issue Jan 4, 2017 · 92 comments

Comments

Projects
None yet
@cloudwu

cloudwu commented Jan 4, 2017

https://github.com/Tencent/xLua/blob/master/Assets/XLua/Src/StaticLuaCallbacks.cs#L98 类似这种。

这相当于绕过了 mono ,直接 unwind 了 stack frame 。

而 mono 自己 raise exception 需要 unwind stack frame 的时候,可是做了许多额外工作的。

@chexiongsheng

This comment has been minimized.

Show comment
Hide comment
@chexiongsheng

chexiongsheng Jan 4, 2017

Collaborator

没有直接调,这个luaL_error是C#实现:
public static int luaL_error(IntPtr L, string message) //[-0, +1, m]
{
xlua_csharp_str_error(L, message);
return 0;
}

而xlua_csharp_str_error是C实现,只是设置标志位lua_upvalueindex(2)
LUALIB_API int xlua_csharp_str_error(lua_State* L, const char* msg)
{
lua_pushboolean(L, 1);
lua_replace(L, lua_upvalueindex(2));
lua_pushstring(L, msg);
return 1;
}

而C#的函数指针是包装过的,检查到有异常,在C代码那调用真正的lua_error抛异常
static int csharp_function_wrap(lua_State *L) {
lua_CFunction fn = (lua_CFunction)lua_tocfunction(L, lua_upvalueindex(1));
int ret = fn(L);

if (lua_toboolean(L, lua_upvalueindex(2)))
{
    lua_pushboolean(L, 0);
    lua_replace(L, lua_upvalueindex(2));
    return lua_error(L);
}

if (lua_gethook(L)) {
	call_ret_hook(L);
}

return ret;

}

Collaborator

chexiongsheng commented Jan 4, 2017

没有直接调,这个luaL_error是C#实现:
public static int luaL_error(IntPtr L, string message) //[-0, +1, m]
{
xlua_csharp_str_error(L, message);
return 0;
}

而xlua_csharp_str_error是C实现,只是设置标志位lua_upvalueindex(2)
LUALIB_API int xlua_csharp_str_error(lua_State* L, const char* msg)
{
lua_pushboolean(L, 1);
lua_replace(L, lua_upvalueindex(2));
lua_pushstring(L, msg);
return 1;
}

而C#的函数指针是包装过的,检查到有异常,在C代码那调用真正的lua_error抛异常
static int csharp_function_wrap(lua_State *L) {
lua_CFunction fn = (lua_CFunction)lua_tocfunction(L, lua_upvalueindex(1));
int ret = fn(L);

if (lua_toboolean(L, lua_upvalueindex(2)))
{
    lua_pushboolean(L, 0);
    lua_replace(L, lua_upvalueindex(2));
    return lua_error(L);
}

if (lua_gethook(L)) {
	call_ret_hook(L);
}

return ret;

}

@cloudwu

This comment has been minimized.

Show comment
Hide comment
@cloudwu

cloudwu commented Jan 4, 2017

https://github.com/Tencent/xLua/blob/master/Assets/XLua/Src/StaticLuaCallbacks.cs#L201

这里 LuaAPI.lua_pushstring 里间接调用了 lua_error 呢?

@chexiongsheng

This comment has been minimized.

Show comment
Hide comment
@chexiongsheng

chexiongsheng Jan 4, 2017

Collaborator

这只在内存不足的时候抛异常,如果是lua53的话,代表系统内存没了。发生概率小,好像也没特别好的处理方式。你有啥建议呢?

Collaborator

chexiongsheng commented Jan 4, 2017

这只在内存不足的时候抛异常,如果是lua53的话,代表系统内存没了。发生概率小,好像也没特别好的处理方式。你有啥建议呢?

@chexiongsheng

This comment has been minimized.

Show comment
Hide comment
@chexiongsheng

chexiongsheng Jan 4, 2017

Collaborator

或者封装下吧,在C层面catch

Collaborator

chexiongsheng commented Jan 4, 2017

或者封装下吧,在C层面catch

@chexiongsheng

This comment has been minimized.

Show comment
Hide comment
@chexiongsheng

chexiongsheng Jan 4, 2017

Collaborator

谢谢指正

Collaborator

chexiongsheng commented Jan 4, 2017

谢谢指正

@cloudwu

This comment has been minimized.

Show comment
Hide comment
@cloudwu

cloudwu Jan 4, 2017

m 类型如果不考虑的话也说得过去,不过 lua_getfield 是 e 类型的异常必须捕获

LUA_API int lua_getglobal (lua_State *L, const char *name) {

btw, m 类型异常不光是 oom ,还有可能因为 __gc 里的 error 触发。

cloudwu commented Jan 4, 2017

m 类型如果不考虑的话也说得过去,不过 lua_getfield 是 e 类型的异常必须捕获

LUA_API int lua_getglobal (lua_State *L, const char *name) {

btw, m 类型异常不光是 oom ,还有可能因为 __gc 里的 error 触发。

@chexiongsheng

This comment has been minimized.

Show comment
Hide comment
@chexiongsheng

chexiongsheng Jan 4, 2017

Collaborator

lua_getfield, lua_setfield都处理了,e类型都统一处理过,如果发现遗漏,不吝指教。
m类型我再统一处理下。

Collaborator

chexiongsheng commented Jan 4, 2017

lua_getfield, lua_setfield都处理了,e类型都统一处理过,如果发现遗漏,不吝指教。
m类型我再统一处理下。

@cloudwu

This comment has been minimized.

Show comment
Hide comment
@cloudwu

cloudwu Jan 4, 2017

若从 c# 调用 lua 函数,一个完备的方案是在 c# 里把函数及所有参数 marshal 成一个 c 结构,这个过程不应调用任何 lua api;在 c 函数里将这个 c 结构 unmarshal 到 lua state 中;这个 unmarshal 过程用 lua_pcall 调用。

cloudwu commented Jan 4, 2017

若从 c# 调用 lua 函数,一个完备的方案是在 c# 里把函数及所有参数 marshal 成一个 c 结构,这个过程不应调用任何 lua api;在 c 函数里将这个 c 结构 unmarshal 到 lua state 中;这个 unmarshal 过程用 lua_pcall 调用。

@chexiongsheng

This comment has been minimized.

Show comment
Hide comment
@chexiongsheng

chexiongsheng Jan 4, 2017

Collaborator

lua_getglobal和lua_setglobal是有遗漏,不过用的地方不多。
你说的那种方式性能不太理想。

Collaborator

chexiongsheng commented Jan 4, 2017

lua_getglobal和lua_setglobal是有遗漏,不过用的地方不多。
你说的那种方式性能不太理想。

@cloudwu

This comment has been minimized.

Show comment
Hide comment
@cloudwu

cloudwu Jan 4, 2017

正确性高于性能,且未必有性能问题。性能应该从实现方法上推敲去改善,而不应利用不完备的设计来提升。

cloudwu commented Jan 4, 2017

正确性高于性能,且未必有性能问题。性能应该从实现方法上推敲去改善,而不应利用不完备的设计来提升。

@cloudwu

This comment has been minimized.

Show comment
Hide comment
@cloudwu

cloudwu Jan 4, 2017

另外,上面提到的方案更简洁,跨越两种语言的 api 更少。

通常 正确性第一,其次是简洁性,它可以为正确性提供更多保障;最后才应该考虑性能。甚至性能好坏只应该是一种副产品,而不应喧宾夺主。

cloudwu commented Jan 4, 2017

另外,上面提到的方案更简洁,跨越两种语言的 api 更少。

通常 正确性第一,其次是简洁性,它可以为正确性提供更多保障;最后才应该考虑性能。甚至性能好坏只应该是一种副产品,而不应喧宾夺主。

@chexiongsheng

This comment has been minimized.

Show comment
Hide comment
@chexiongsheng

chexiongsheng Jan 4, 2017

Collaborator

推敲下,应该性能也没有大问题,可以后续验证下。
但api应该没怎么减少,pushxxx和toxxx在lua调C#还是会用到的。
至于正确性,现有方案也不见得不能保证:把所有会抛lua异常的api都封装好,在C#层面处理好异常返回。

Collaborator

chexiongsheng commented Jan 4, 2017

推敲下,应该性能也没有大问题,可以后续验证下。
但api应该没怎么减少,pushxxx和toxxx在lua调C#还是会用到的。
至于正确性,现有方案也不见得不能保证:把所有会抛lua异常的api都封装好,在C#层面处理好异常返回。

@chexiongsheng

This comment has been minimized.

Show comment
Hide comment
@chexiongsheng

chexiongsheng Jan 4, 2017

Collaborator

况且lua调用C#,这些api的封装也势在必行
除非lua调C#也改为marshal,unmarshal,那这样直接改为lua和C#之间用RPC来通讯好了。

Collaborator

chexiongsheng commented Jan 4, 2017

况且lua调用C#,这些api的封装也势在必行
除非lua调C#也改为marshal,unmarshal,那这样直接改为lua和C#之间用RPC来通讯好了。

@cloudwu

This comment has been minimized.

Show comment
Hide comment
@cloudwu

cloudwu Jan 4, 2017

反向也是一样的,完备性上讲, lua 调用的 c# 函数同样要处理 c# raise 的 exception .

cloudwu commented Jan 4, 2017

反向也是一样的,完备性上讲, lua 调用的 c# 函数同样要处理 c# raise 的 exception .

@cloudwu

This comment has been minimized.

Show comment
Hide comment
@cloudwu

cloudwu Jan 4, 2017

ffi 交互 api 最终只需要有两个,从一种语言中传递一个双方都能解析的 c struct 让对方解析它做对应的操作。

cloudwu commented Jan 4, 2017

ffi 交互 api 最终只需要有两个,从一种语言中传递一个双方都能解析的 c struct 让对方解析它做对应的操作。

@chexiongsheng

This comment has been minimized.

Show comment
Hide comment
@chexiongsheng

chexiongsheng Jan 4, 2017

Collaborator

c#异常已经处理,就开篇那种方式。
c#调用lua还是先保留现有实现,会统一对所有产生异常的lua api封装为用返回值来表示异常,之前把e类型大多都处理了,现在会处理掉m类型。
marshal,unmarshal方案目前能想到的问题是对方的对象引用持有:可能lua测执行的过程中,c#的对象已经释放,反之亦然。目前lua对c#的引用管理是通过userdata的gc加上c#的对象池,c#对lua的引用通过C#构造,析构的lua_ref和lua_unref。
性能还是会有区别的,il2cpp的pinvoke其实只是普通c函数调用,而相比之下,marshal,unmarshal至少多了打解包开销。

Collaborator

chexiongsheng commented Jan 4, 2017

c#异常已经处理,就开篇那种方式。
c#调用lua还是先保留现有实现,会统一对所有产生异常的lua api封装为用返回值来表示异常,之前把e类型大多都处理了,现在会处理掉m类型。
marshal,unmarshal方案目前能想到的问题是对方的对象引用持有:可能lua测执行的过程中,c#的对象已经释放,反之亦然。目前lua对c#的引用管理是通过userdata的gc加上c#的对象池,c#对lua的引用通过C#构造,析构的lua_ref和lua_unref。
性能还是会有区别的,il2cpp的pinvoke其实只是普通c函数调用,而相比之下,marshal,unmarshal至少多了打解包开销。

@michaelpengcn

This comment has been minimized.

Show comment
Hide comment
@michaelpengcn

michaelpengcn Jan 4, 2017

看到云风大神在这里讨论,我顿时对这个xlua感兴趣了

michaelpengcn commented Jan 4, 2017

看到云风大神在这里讨论,我顿时对这个xlua感兴趣了

@caiguangwen1

This comment has been minimized.

Show comment
Hide comment
@caiguangwen1

caiguangwen1 Jan 4, 2017

云大在给这个项目涨粉。。。

caiguangwen1 commented Jan 4, 2017

云大在给这个项目涨粉。。。

@cloudwu

This comment has been minimized.

Show comment
Hide comment
@cloudwu

cloudwu Jan 4, 2017

marshal 不一定要编码成字节流,它只是不同语言间数据交换的一种方法。本质上,调用 lua_pushstring 等也是在做 marshaling ,只不过是对单个对象做而已。以上提出方案是建议把单次交互的所有需要交换数据一起做 marshaling ,这其实是提供给具体优化更大的余地。

跨语言对象持有的生命期问题是一个独立问题,应该和跨语言调用分解后独立处理。

userdata 的 __gc 方法是一个很重量的东西,而且不可控因素很多。比如在 xLua 目前的实现中,lua 中的 __gc 回饶回 c# 中的 LuaGC 方法,这又涉及跨越不同语言信息交换,这是在做 FFI 时应尽量减少的行为。另外 __gc 发生的时机不可控,复杂的 __gc 方法会影响正常业务流程的时间开销估计。

把这个问题正交分离单独解决的方法是:

在做 marshaling 的期间,所有 C# 对象进入 marshaling 流程后都放到一个用于保持生命期的集合(处于 mono 虚拟机)中,并传递 id (可从集合中索引)到 lua 。

lua 在做 unmarshal 的时候,把 id 关联到一个 table ,table 中可记录更多 C# 对象的类型信息。这个关联关系放在 lua vm 的全局弱表中即可。lua 业务层引用这个 table 相当于引用 C# 对象。传递回 C# 只需要编码 id。

lua 在走完一轮 gc 后,遍历出弱表中哪些 id 对应的 table 消失,可以统计出哪些 C# 对象不再被引用。把不再引用的 id 交还给 mono ,然后 mono 里给出一个 api 根据需要清理它们。这个过程只有交换 id 的过程是涉及语言间数据交换的。

C# 引用 lua 对象的处理方法是一致的,只是换一种语言实现。

cloudwu commented Jan 4, 2017

marshal 不一定要编码成字节流,它只是不同语言间数据交换的一种方法。本质上,调用 lua_pushstring 等也是在做 marshaling ,只不过是对单个对象做而已。以上提出方案是建议把单次交互的所有需要交换数据一起做 marshaling ,这其实是提供给具体优化更大的余地。

跨语言对象持有的生命期问题是一个独立问题,应该和跨语言调用分解后独立处理。

userdata 的 __gc 方法是一个很重量的东西,而且不可控因素很多。比如在 xLua 目前的实现中,lua 中的 __gc 回饶回 c# 中的 LuaGC 方法,这又涉及跨越不同语言信息交换,这是在做 FFI 时应尽量减少的行为。另外 __gc 发生的时机不可控,复杂的 __gc 方法会影响正常业务流程的时间开销估计。

把这个问题正交分离单独解决的方法是:

在做 marshaling 的期间,所有 C# 对象进入 marshaling 流程后都放到一个用于保持生命期的集合(处于 mono 虚拟机)中,并传递 id (可从集合中索引)到 lua 。

lua 在做 unmarshal 的时候,把 id 关联到一个 table ,table 中可记录更多 C# 对象的类型信息。这个关联关系放在 lua vm 的全局弱表中即可。lua 业务层引用这个 table 相当于引用 C# 对象。传递回 C# 只需要编码 id。

lua 在走完一轮 gc 后,遍历出弱表中哪些 id 对应的 table 消失,可以统计出哪些 C# 对象不再被引用。把不再引用的 id 交还给 mono ,然后 mono 里给出一个 api 根据需要清理它们。这个过程只有交换 id 的过程是涉及语言间数据交换的。

C# 引用 lua 对象的处理方法是一致的,只是换一种语言实现。

@chexiongsheng

This comment has been minimized.

Show comment
Hide comment
@chexiongsheng

chexiongsheng Jan 4, 2017

Collaborator

“这其实是提供给具体优化更大的余地。”

如果mono的pinvoke开销不好评估,如果il2cpp下,直接push的方式,仅仅相当于你在pcall里头做的事情的一部分。
以静态int add(int a, int b)为例,目前方式:
lua_pushnumber(L, a);
lua_pushnumber(L, b)

marshal/unmarshal方案是这样的
1、先marshal
marshal(buff, a) %
marshal(buff, b) %

2、在pcall里头unmarshal
unmarshal(buff, &a) %
unmarshal(buff, &b) %
lua_pushnumber(L, a); *
lua_pushnumber(L, b) *

你说的优化只是优化%标识的语句,总不能优化没吧?
而现在方案开销只对应*号的语句。
另外,还没考虑指示调用哪个lua函数的marshal/unmarshal呢。

ps一下,il2cpp是大势所趋,android下也是。

Collaborator

chexiongsheng commented Jan 4, 2017

“这其实是提供给具体优化更大的余地。”

如果mono的pinvoke开销不好评估,如果il2cpp下,直接push的方式,仅仅相当于你在pcall里头做的事情的一部分。
以静态int add(int a, int b)为例,目前方式:
lua_pushnumber(L, a);
lua_pushnumber(L, b)

marshal/unmarshal方案是这样的
1、先marshal
marshal(buff, a) %
marshal(buff, b) %

2、在pcall里头unmarshal
unmarshal(buff, &a) %
unmarshal(buff, &b) %
lua_pushnumber(L, a); *
lua_pushnumber(L, b) *

你说的优化只是优化%标识的语句,总不能优化没吧?
而现在方案开销只对应*号的语句。
另外,还没考虑指示调用哪个lua函数的marshal/unmarshal呢。

ps一下,il2cpp是大势所趋,android下也是。

@dualface

This comment has been minimized.

Show comment
Hide comment
@dualface

dualface Jan 4, 2017

在 mono 对象被 lua 引用时就标记为不可 gc,我认为会有问题:

  • lua 没有做 gc 前,被 lua 引用的 mono 对象也不会被 gc
  • lua 自动 gc 的时机只和 lua vm 的状态有关,和 mono 的内存消耗无关

我个人认为比较合理的做法是:

  • mono 对象和 lua 的 gc 不要关联
  • mono 对象 gc 时,将 lua vm 中的对应值赋值为 null 即可

dualface commented Jan 4, 2017

在 mono 对象被 lua 引用时就标记为不可 gc,我认为会有问题:

  • lua 没有做 gc 前,被 lua 引用的 mono 对象也不会被 gc
  • lua 自动 gc 的时机只和 lua vm 的状态有关,和 mono 的内存消耗无关

我个人认为比较合理的做法是:

  • mono 对象和 lua 的 gc 不要关联
  • mono 对象 gc 时,将 lua vm 中的对应值赋值为 null 即可
@cloudwu

This comment has been minimized.

Show comment
Hide comment
@cloudwu

cloudwu Jan 4, 2017

mono 和 lua 是两个非常独立的模块,首先应该考虑设计层面的模块间减少耦合度,提高内聚性,再考虑实现细节的优化。设计做简单了,实现优化甚至是可有可无的东西。

提高内聚性后,交互变成批量处理的东西,在理论上存在更大的优化空间。比如聚合要处理的批量数据后(把要传递的参数聚合在一起),对 cache 更友好。

lua_pushstring 这种要保证异常处理的完备,每次调用都是要检查的,而聚合在一起后,是通过 lua 自己的 error 机制统一处理例外情况,而不需要逐个做判断。

总的来说,在设计层面考虑问题,不要陷入不必要的优化细节考虑上。

cloudwu commented Jan 4, 2017

mono 和 lua 是两个非常独立的模块,首先应该考虑设计层面的模块间减少耦合度,提高内聚性,再考虑实现细节的优化。设计做简单了,实现优化甚至是可有可无的东西。

提高内聚性后,交互变成批量处理的东西,在理论上存在更大的优化空间。比如聚合要处理的批量数据后(把要传递的参数聚合在一起),对 cache 更友好。

lua_pushstring 这种要保证异常处理的完备,每次调用都是要检查的,而聚合在一起后,是通过 lua 自己的 error 机制统一处理例外情况,而不需要逐个做判断。

总的来说,在设计层面考虑问题,不要陷入不必要的优化细节考虑上。

@cloudwu

This comment has been minimized.

Show comment
Hide comment
@cloudwu

cloudwu Jan 4, 2017

lua gc 有它自身的完备性,不应该用各种使用约定来约束对象生命期。lua gc 本身也是完全可控的,lua 也提供了足够丰富的控制手段。

cloudwu commented Jan 4, 2017

lua gc 有它自身的完备性,不应该用各种使用约定来约束对象生命期。lua gc 本身也是完全可控的,lua 也提供了足够丰富的控制手段。

@cloudwu

This comment has been minimized.

Show comment
Hide comment
@cloudwu

cloudwu Jan 4, 2017

FFI 本身就是非常重量的事情,Lua 和 C 之间的交互也属于 FFI 。lua mailling list 很多年前作者就强调过,如果你需要从 lua 中调用 C ,那么要做的就必须是件重量的事情。运行开销的很大比例若花在跨语言交互上本身就是有问题的。

所以 FFI 的设计关键是要简单,如果只是想提高交互效率,lua 和 C 交互的 api 本身就有很大的改进空间。扩展个 opcode 会比用标准 api 去交互高效的多。

cloudwu commented Jan 4, 2017

FFI 本身就是非常重量的事情,Lua 和 C 之间的交互也属于 FFI 。lua mailling list 很多年前作者就强调过,如果你需要从 lua 中调用 C ,那么要做的就必须是件重量的事情。运行开销的很大比例若花在跨语言交互上本身就是有问题的。

所以 FFI 的设计关键是要简单,如果只是想提高交互效率,lua 和 C 交互的 api 本身就有很大的改进空间。扩展个 opcode 会比用标准 api 去交互高效的多。

@liyonghelpme

This comment has been minimized.

Show comment
Hide comment
@liyonghelpme

liyonghelpme Jan 5, 2017

控制一帧里面跨语言交互的次数,然后重点优化频繁调用的改成批处理,这样既简单也和谐友爱

liyonghelpme commented Jan 5, 2017

控制一帧里面跨语言交互的次数,然后重点优化频繁调用的改成批处理,这样既简单也和谐友爱

@chexiongsheng

This comment has been minimized.

Show comment
Hide comment
@chexiongsheng

chexiongsheng Jan 5, 2017

Collaborator

一个个问题来分析
一、调用方式
正确性:marshal方案固然有保障,但全面封装异常也可以,如果全面有保证的话。
性能:
marshal方案多了marshal/unmarshal(包括调用及返回,有两次marshal/unmarshal),但好处是只有一个pcall
全面封装异常会对每个m类型的压栈/出栈api会有一次pcall开销,像lua_pushnumber这类不需要。
光推敲,很难得出哪个更好,但相信也不会有什么质的差别。
复杂度:
我十分赞同你说的设计要简单的观点。但我没能看出引入一套考虑了函数(名或者引用)以及各种类型参数的marshal/unmarshal方案会比对几个m类型api封装要简单。
二、对象生命周期管理
感觉用弱表来跟踪C#对象引用是个好想法,可以验证下。但也不一定要把对象映射到lua table,一个空lua table 就有80字节左右,用userdata更合适,不挂__gc就可以了。弱表跟踪userdata同样适用。
两个互无感知的GC间的对象引用是个麻烦事,比如环引用就无解。更彻底的做法是C#实现lua,对的,就是你们之前的UniLua,只是性能不知道能做到什么程度,感觉il2cpp下有可能性能问题能解决。
xLua v1的时候有集成UniLua作为WebPlayer的兼容,不过由于一直没有WebPlayer的项目,在V2重构时去掉了。

ffi确实重,项目主要的性能瓶颈往往在这。

推热补丁其实一个目的是希望尽量降低这影响
1、lua只出现在打补丁的时候,正常逻辑下不涉及;
2、约束了使用场景:如果是Stateless方式,热更的是一般是纯逻辑,不涉及状态(当然要涉及也可以),那对象生命周期管理的负担有望减轻;而Stateful方式的状态管理也是自动化的,一个对象对应一个lua table,如果你设置了的话,对象析构系统自动化解除关联,避免了代码不严谨导致的环引用。

Collaborator

chexiongsheng commented Jan 5, 2017

一个个问题来分析
一、调用方式
正确性:marshal方案固然有保障,但全面封装异常也可以,如果全面有保证的话。
性能:
marshal方案多了marshal/unmarshal(包括调用及返回,有两次marshal/unmarshal),但好处是只有一个pcall
全面封装异常会对每个m类型的压栈/出栈api会有一次pcall开销,像lua_pushnumber这类不需要。
光推敲,很难得出哪个更好,但相信也不会有什么质的差别。
复杂度:
我十分赞同你说的设计要简单的观点。但我没能看出引入一套考虑了函数(名或者引用)以及各种类型参数的marshal/unmarshal方案会比对几个m类型api封装要简单。
二、对象生命周期管理
感觉用弱表来跟踪C#对象引用是个好想法,可以验证下。但也不一定要把对象映射到lua table,一个空lua table 就有80字节左右,用userdata更合适,不挂__gc就可以了。弱表跟踪userdata同样适用。
两个互无感知的GC间的对象引用是个麻烦事,比如环引用就无解。更彻底的做法是C#实现lua,对的,就是你们之前的UniLua,只是性能不知道能做到什么程度,感觉il2cpp下有可能性能问题能解决。
xLua v1的时候有集成UniLua作为WebPlayer的兼容,不过由于一直没有WebPlayer的项目,在V2重构时去掉了。

ffi确实重,项目主要的性能瓶颈往往在这。

推热补丁其实一个目的是希望尽量降低这影响
1、lua只出现在打补丁的时候,正常逻辑下不涉及;
2、约束了使用场景:如果是Stateless方式,热更的是一般是纯逻辑,不涉及状态(当然要涉及也可以),那对象生命周期管理的负担有望减轻;而Stateful方式的状态管理也是自动化的,一个对象对应一个lua table,如果你设置了的话,对象析构系统自动化解除关联,避免了代码不严谨导致的环引用。

@cloudwu

This comment has been minimized.

Show comment
Hide comment
@cloudwu

cloudwu Jan 5, 2017

简单指的是尽量引入最少的东西。

比如,只用 table 能解决的问题,就不要引入 userdata ;如果能只向 mono 导入 2,3 个 C API 解决的问题,就不要入导入十几或几十个 C API 。

这些都是耦合度层面的设计考虑,把类型处理、生命期管理放在模块内部是从提高内聚性方面考虑的。和 pinvoke/pcall 的代价到底有多大无关。如果初期开发原型,直接把数据都打包成 json 就好了,C# 和 lua 都有成熟的 json 库,然后慢慢再来改进。可以这样分步来完成实现,就是低耦合高内聚的体现。

从使用角度上讲,userdata 只能携带 C 层面的数据,往往在具体实现时要考虑绑定一些 lua 层面的对象。比如、封装的 C# 对象需要有类型名字方便调试。这些名字是以 string 对象的形式呈现的,如果把 string 放在 userdata 的 C 内存里,会增加跨语言的耦合度,通常是在构造跨语言引用对象时,放在 lua vm 中的。如果采用 userdata ,就需要额外设置它的 uservalue ;这样就不如直接使用一个 table 。

而且 table 对 lua 代码本身有更大的可操控性,方便直接用 lua 写更多逻辑。

总的来说,就是莫要执着在性能上。性能只是正确设计的副产品。

ps. mono 对象和 lua 对象相互 1:1 对应关系,是不会成环的。用不着用 C# 去实现 lua 或是用 lua 实现 C# 。UniLua 的开发动机是需要在 web 浏览器里使用 u3d 的标准控件来跑 lua 代码;失去这个需求后,我们再也没用过它。

cloudwu commented Jan 5, 2017

简单指的是尽量引入最少的东西。

比如,只用 table 能解决的问题,就不要引入 userdata ;如果能只向 mono 导入 2,3 个 C API 解决的问题,就不要入导入十几或几十个 C API 。

这些都是耦合度层面的设计考虑,把类型处理、生命期管理放在模块内部是从提高内聚性方面考虑的。和 pinvoke/pcall 的代价到底有多大无关。如果初期开发原型,直接把数据都打包成 json 就好了,C# 和 lua 都有成熟的 json 库,然后慢慢再来改进。可以这样分步来完成实现,就是低耦合高内聚的体现。

从使用角度上讲,userdata 只能携带 C 层面的数据,往往在具体实现时要考虑绑定一些 lua 层面的对象。比如、封装的 C# 对象需要有类型名字方便调试。这些名字是以 string 对象的形式呈现的,如果把 string 放在 userdata 的 C 内存里,会增加跨语言的耦合度,通常是在构造跨语言引用对象时,放在 lua vm 中的。如果采用 userdata ,就需要额外设置它的 uservalue ;这样就不如直接使用一个 table 。

而且 table 对 lua 代码本身有更大的可操控性,方便直接用 lua 写更多逻辑。

总的来说,就是莫要执着在性能上。性能只是正确设计的副产品。

ps. mono 对象和 lua 对象相互 1:1 对应关系,是不会成环的。用不着用 C# 去实现 lua 或是用 lua 实现 C# 。UniLua 的开发动机是需要在 web 浏览器里使用 u3d 的标准控件来跑 lua 代码;失去这个需求后,我们再也没用过它。

@cloudwu

This comment has been minimized.

Show comment
Hide comment
@cloudwu

cloudwu Jan 5, 2017

另外,lua 的 function 是 first class 的;作为一个完整的 ffi 方案,怎么把一个 lua 里的 object , 包括function / table / thread 的引用传递给 c# 是首要考虑的事情。

cloudwu commented Jan 5, 2017

另外,lua 的 function 是 first class 的;作为一个完整的 ffi 方案,怎么把一个 lua 里的 object , 包括function / table / thread 的引用传递给 c# 是首要考虑的事情。

@cloudwu

This comment has been minimized.

Show comment
Hide comment
@cloudwu

cloudwu Jan 5, 2017

我下午随便写了一段代码演示怎么把 C# 里一组变量一次 marshal 到 C 库里,只需要 C 里面实现一个函数作一次 p/invoke 就够了。这里的 marshal 过程是没有太多开销的。

https://github.com/cloudwu/csbridge

C# 不是很熟,应该还有优化余地。

在这个基础上改一下,就可以进一步去调到 lua vm 里去了。有空我也写个 xxlua 用用。

cloudwu commented Jan 5, 2017

我下午随便写了一段代码演示怎么把 C# 里一组变量一次 marshal 到 C 库里,只需要 C 里面实现一个函数作一次 p/invoke 就够了。这里的 marshal 过程是没有太多开销的。

https://github.com/cloudwu/csbridge

C# 不是很熟,应该还有优化余地。

在这个基础上改一下,就可以进一步去调到 lua vm 里去了。有空我也写个 xxlua 用用。

@dualface

This comment has been minimized.

Show comment
Hide comment
@dualface

dualface Jan 5, 2017

总的来说还是同意云大的看法,设计上先保持清晰,才能有更大的优化空间。也看了一下 csbridge 的实现,这种方式是很清晰简洁,后期优化空间也大,相比直接 push 参数到 stack,应该没多大区别。

不过目前的实现只适合简单的值类型,对于 function/table/thread 等类型,处理起来就麻烦多了。

举个例子:

lua 传入一个 function 给 mono,mono 会在后续调用这个 lua funciton。

lua function 虽然在 lua 里是 first class,但在 c 里并不能简单的转换为一个值,更不能直接转为一个 mono value。

我的做法是:

定义一个全局 lua table,每当有 lua function 要传入时。就生成一个 lua function id,然后把 id 和 lua function 关联起来。也就是 lua table[id] = lua function。

id 是一个 integer,自然就可以很方便的传递给 c/mono 了。

当 mono 需要 call 这个 lua function 时,给出 id 和参数就行了。 bridge(假设 bridge 负责 mono/lua 间的交互)根据 id 查找 lua function,push 到 stack 里,然后再 push 参数并 pcall。

不过这种做法要注意的就是当 mono 不再需要之前传入的 lua function 时,得记得从 lua table 里删除 id,不然 lua function 会无法 gc。

不知道有没有更好的解决方案?

dualface commented Jan 5, 2017

总的来说还是同意云大的看法,设计上先保持清晰,才能有更大的优化空间。也看了一下 csbridge 的实现,这种方式是很清晰简洁,后期优化空间也大,相比直接 push 参数到 stack,应该没多大区别。

不过目前的实现只适合简单的值类型,对于 function/table/thread 等类型,处理起来就麻烦多了。

举个例子:

lua 传入一个 function 给 mono,mono 会在后续调用这个 lua funciton。

lua function 虽然在 lua 里是 first class,但在 c 里并不能简单的转换为一个值,更不能直接转为一个 mono value。

我的做法是:

定义一个全局 lua table,每当有 lua function 要传入时。就生成一个 lua function id,然后把 id 和 lua function 关联起来。也就是 lua table[id] = lua function。

id 是一个 integer,自然就可以很方便的传递给 c/mono 了。

当 mono 需要 call 这个 lua function 时,给出 id 和参数就行了。 bridge(假设 bridge 负责 mono/lua 间的交互)根据 id 查找 lua function,push 到 stack 里,然后再 push 参数并 pcall。

不过这种做法要注意的就是当 mono 不再需要之前传入的 lua function 时,得记得从 lua table 里删除 id,不然 lua function 会无法 gc。

不知道有没有更好的解决方案?

@cloudwu

This comment has been minimized.

Show comment
Hide comment
@cloudwu

cloudwu Jan 5, 2017

lua 对象(包括 funtion)传给 C# 引用是必须解决的问题,而且设计后面函数调用的设计,这个今天来不及做了,明天我把它补上。

csbridge 第一版实现(见最早的 commit),同事看过提意见说 string 放在那个结构里会导致 marshal 的时候复制,增加 p/invoke 的成本;所以我后来的版本增加了一个 api 把有 string 和没有 string 的情况分开处理了。

这里还存在一个优化点,可以把一部分常用的 string 同步到 lua vm 中(通常是短字符串)。然后在第一次调用后,以后全部只传 string id ,不用在 p/invoke 的时候再装箱拆箱。有空我会把这个优化加上。

cloudwu commented Jan 5, 2017

lua 对象(包括 funtion)传给 C# 引用是必须解决的问题,而且设计后面函数调用的设计,这个今天来不及做了,明天我把它补上。

csbridge 第一版实现(见最早的 commit),同事看过提意见说 string 放在那个结构里会导致 marshal 的时候复制,增加 p/invoke 的成本;所以我后来的版本增加了一个 api 把有 string 和没有 string 的情况分开处理了。

这里还存在一个优化点,可以把一部分常用的 string 同步到 lua vm 中(通常是短字符串)。然后在第一次调用后,以后全部只传 string id ,不用在 p/invoke 的时候再装箱拆箱。有空我会把这个优化加上。

@chexiongsheng

This comment has been minimized.

Show comment
Hide comment
@chexiongsheng

chexiongsheng Jan 5, 2017

Collaborator

@cloudwu 这是一段循环引用的测试

using UnityEngine;
using System.Collections;
using XLua;

public class CRTest
{
public LuaTable Tbl;
public CRTest(LuaTable tbl)
{
tbl.Set("csobj", this);
Tbl = tbl;
}
}

public class CircularReference : MonoBehaviour {
LuaEnv luaenv = new LuaEnv();

// Use this for initialization
void Start () {

}

// Update is called once per frame
void Update () {
    for (int i = 0; i < 1000; i++)
    {
        CRTest crt = new CRTest(luaenv.NewTable());
        //crt.Tbl = null; //打开注释之后内存就不泄漏,或者这句换成crt.Tbl.Dispose();也可以
    }

    System.GC.Collect();
    System.GC.WaitForPendingFinalizers();
    luaenv.FullGc();
    luaenv.Tick();
}

}

Collaborator

chexiongsheng commented Jan 5, 2017

@cloudwu 这是一段循环引用的测试

using UnityEngine;
using System.Collections;
using XLua;

public class CRTest
{
public LuaTable Tbl;
public CRTest(LuaTable tbl)
{
tbl.Set("csobj", this);
Tbl = tbl;
}
}

public class CircularReference : MonoBehaviour {
LuaEnv luaenv = new LuaEnv();

// Use this for initialization
void Start () {

}

// Update is called once per frame
void Update () {
    for (int i = 0; i < 1000; i++)
    {
        CRTest crt = new CRTest(luaenv.NewTable());
        //crt.Tbl = null; //打开注释之后内存就不泄漏,或者这句换成crt.Tbl.Dispose();也可以
    }

    System.GC.Collect();
    System.GC.WaitForPendingFinalizers();
    luaenv.FullGc();
    luaenv.Tick();
}

}

@chexiongsheng

This comment has been minimized.

Show comment
Hide comment
@chexiongsheng

chexiongsheng Jan 5, 2017

Collaborator

lua gc是Mark & Sweep,mono的sgen也是。
如果对象都在一个gc里头,循环引用是没问题的。
但分别在两个不同的gc管理器,问题就来了:
CRTest 对象被lua里头的一个table引用了,它没法释放,而lua的table被CRTest 对象引用了,也没法释放。Mark & Sweep是在一个图里头标识出从root出发不可达的部分,如果这个图能够囊括所有对象,循环引用是不会造成问题的,但问题是对于lua或者mono,这个图都不全。

Collaborator

chexiongsheng commented Jan 5, 2017

lua gc是Mark & Sweep,mono的sgen也是。
如果对象都在一个gc里头,循环引用是没问题的。
但分别在两个不同的gc管理器,问题就来了:
CRTest 对象被lua里头的一个table引用了,它没法释放,而lua的table被CRTest 对象引用了,也没法释放。Mark & Sweep是在一个图里头标识出从root出发不可达的部分,如果这个图能够囊括所有对象,循环引用是不会造成问题的,但问题是对于lua或者mono,这个图都不全。

@cloudwu

This comment has been minimized.

Show comment
Hide comment
@cloudwu

cloudwu Jan 5, 2017

这是一个设计错误,正确设计生命期管理是没有这个问题的。今天我没空写代码,明天会补充到 csbridge 里。

核心问题是:一个 A 虚拟机里的对象,把引用传递到 B 虚拟机里,该怎么作对?ps. @dualface 提到到 function 引用的问题是这个的子问题, mono 需要引用 lua 函数,lua 其实也需要引用 mono 函数。所谓 lua 调用 mono 里的 C# 方法本质上就是发送了一个 mono 函数类和参数,申请 mono 运行它;和 mono 调用 lua 的过程是一致的。

由于 mono 和 lua 在这个问题上是对称的,只是拥有的语言特性有点不同,所以下面我只讨论一边,即,怎么把一个 mono 对象的引用传递给 lua 。

mono 中应该有一个集合 T 。同时 Lua 中有两个集合,一个弱表集合 C ,一个 id 集合 L 。

当一个 mono 对象被创建出来,第一次需要传递给 lua 时,应该为它申请一个新的 id ,id:object 放入 T ,id:weak 放入 C 。之后将 id 传递给 lua ,随后 lua 应该把 id 放在 C 和 L ,生成一个 lua object (内含 id )给 lua 业务引用。

当 lua 的业务不再引用某个 id 后,这个 id 会在 C 中消失,定期比较 C 和 L 可以找到曾经传递给 lua 却不再引用的 id 集,把这个集交给 mono 。

mono 收到 lua 传递过来的不再引用的 id 集后,把这些 id 从 T 中删除,等待 mono gc 去回收。


反过来不写了,mono 可以用 weak reference 来模拟弱表。

cloudwu commented Jan 5, 2017

这是一个设计错误,正确设计生命期管理是没有这个问题的。今天我没空写代码,明天会补充到 csbridge 里。

核心问题是:一个 A 虚拟机里的对象,把引用传递到 B 虚拟机里,该怎么作对?ps. @dualface 提到到 function 引用的问题是这个的子问题, mono 需要引用 lua 函数,lua 其实也需要引用 mono 函数。所谓 lua 调用 mono 里的 C# 方法本质上就是发送了一个 mono 函数类和参数,申请 mono 运行它;和 mono 调用 lua 的过程是一致的。

由于 mono 和 lua 在这个问题上是对称的,只是拥有的语言特性有点不同,所以下面我只讨论一边,即,怎么把一个 mono 对象的引用传递给 lua 。

mono 中应该有一个集合 T 。同时 Lua 中有两个集合,一个弱表集合 C ,一个 id 集合 L 。

当一个 mono 对象被创建出来,第一次需要传递给 lua 时,应该为它申请一个新的 id ,id:object 放入 T ,id:weak 放入 C 。之后将 id 传递给 lua ,随后 lua 应该把 id 放在 C 和 L ,生成一个 lua object (内含 id )给 lua 业务引用。

当 lua 的业务不再引用某个 id 后,这个 id 会在 C 中消失,定期比较 C 和 L 可以找到曾经传递给 lua 却不再引用的 id 集,把这个集交给 mono 。

mono 收到 lua 传递过来的不再引用的 id 集后,把这些 id 从 T 中删除,等待 mono gc 去回收。


反过来不写了,mono 可以用 weak reference 来模拟弱表。

@chexiongsheng

This comment has been minimized.

Show comment
Hide comment
@chexiongsheng

chexiongsheng Jan 6, 2017

Collaborator

@cloudwu 其实目前xLua的对象引用实现和你说的是类似的。
C#有个ObjectPool的类,就是对应你说的T集合,保存的是id:object映射,在lua测有个弱表,对应你的C,lua object在xLua是用userdata。回收思路不一样,xLua是gc回调,而你这里是定期扫描弱表,但在对象引用这块达成的效果是一致的:T的释放依赖于lua测不再引用那id。反向也是类似,思路都是引用对方对象时,在对方那增加个引用,当且仅当本方没有使用时,释放增加的那个引用。
但还是会出现环的问题,你可以在你的模型推导一下,当两个对象分别属于不同gc管理,又互相引用的时候,会释放么?哪个会先释放呢?
我觉得根因不是出在实现上的问题,而是没法满足mark&swap算法的一个必要条件:能遍历整个网络。它虽然也能遍历T集合,但T集合实际上是另一个gc系统建立的虚引用,它无法进一步检查这些虚引用的来源。
我想到的解决思路,如果还是mark&swap算法,那这算法囊括的对象得是所有的.net和lua对象。

Collaborator

chexiongsheng commented Jan 6, 2017

@cloudwu 其实目前xLua的对象引用实现和你说的是类似的。
C#有个ObjectPool的类,就是对应你说的T集合,保存的是id:object映射,在lua测有个弱表,对应你的C,lua object在xLua是用userdata。回收思路不一样,xLua是gc回调,而你这里是定期扫描弱表,但在对象引用这块达成的效果是一致的:T的释放依赖于lua测不再引用那id。反向也是类似,思路都是引用对方对象时,在对方那增加个引用,当且仅当本方没有使用时,释放增加的那个引用。
但还是会出现环的问题,你可以在你的模型推导一下,当两个对象分别属于不同gc管理,又互相引用的时候,会释放么?哪个会先释放呢?
我觉得根因不是出在实现上的问题,而是没法满足mark&swap算法的一个必要条件:能遍历整个网络。它虽然也能遍历T集合,但T集合实际上是另一个gc系统建立的虚引用,它无法进一步检查这些虚引用的来源。
我想到的解决思路,如果还是mark&swap算法,那这算法囊括的对象得是所有的.net和lua对象。

@cloudwu

This comment has been minimized.

Show comment
Hide comment
@cloudwu

cloudwu Jan 6, 2017

对于 A B 个对象分处两个虚拟机,又相互引用的情况,可以这样处理:

A 发现所在自己所在虚拟机中已经没有引用,仅被 B 的虚拟机引用时,通知 B 告知自己这边已经无引用。但并不把自己实体删除。由于 A 自身已经没有引用,所以只有 B 重新把引用传回来时才会重建(利用实体)。

我今天正在依此实现,看晚上能不能做完。

cloudwu commented Jan 6, 2017

对于 A B 个对象分处两个虚拟机,又相互引用的情况,可以这样处理:

A 发现所在自己所在虚拟机中已经没有引用,仅被 B 的虚拟机引用时,通知 B 告知自己这边已经无引用。但并不把自己实体删除。由于 A 自身已经没有引用,所以只有 B 重新把引用传回来时才会重建(利用实体)。

我今天正在依此实现,看晚上能不能做完。

@chexiongsheng

This comment has been minimized.

Show comment
Hide comment
@chexiongsheng

chexiongsheng Jan 6, 2017

Collaborator

这也很难啊,拿mono gc来说
T集合对于mono gc来说就普通对象而已,它怎么知道这是来自别的虚拟机的引用保持机制呢?除非你修改mono gc,对T集合特殊处理,发现某某类名,就认为这是来自unmanager的引用。

Collaborator

chexiongsheng commented Jan 6, 2017

这也很难啊,拿mono gc来说
T集合对于mono gc来说就普通对象而已,它怎么知道这是来自别的虚拟机的引用保持机制呢?除非你修改mono gc,对T集合特殊处理,发现某某类名,就认为这是来自unmanager的引用。

@cloudwu

This comment has been minimized.

Show comment
Hide comment
@cloudwu

cloudwu Jan 6, 2017

lua 侧是完全没有问题的,做起来非常简单。因为 lua 有 ephemeron table 。在 ephemeron table 的 value __gc 中复活即可。

C# 不熟,我需要找找有没有类似机制。

cloudwu commented Jan 6, 2017

lua 侧是完全没有问题的,做起来非常简单。因为 lua 有 ephemeron table 。在 ephemeron table 的 value __gc 中复活即可。

C# 不熟,我需要找找有没有类似机制。

@cloudwu

This comment has been minimized.

Show comment
Hide comment
@cloudwu

cloudwu Jan 6, 2017

C# 侧不用解决,因为只要一边能处理对,循环就被打破了。

cloudwu commented Jan 6, 2017

C# 侧不用解决,因为只要一边能处理对,循环就被打破了。

@cloudwu

This comment has been minimized.

Show comment
Hide comment
@cloudwu

cloudwu Jan 6, 2017

我来讲讲循环引用是怎么解开的:

如果语言具有一种能力,当一个对象没有引用了后,可以感知到这点,但是又允许不删除这个对象,暂时保留起来。那么,解决这个问题就非常简单。

比如,在 mono 一边有个对象 A 它引用了 lua 中的对象 B 而 B 也反向引用回 A,那么本质上, mono 中有两个对象 A 和 B' ,lua 中有 B 和 A' 。这里 A' 是 A 的代理, B' 是 B 的代理。

如果 mono 发现它自身已经不再引用 A 了,无论是否 lua 中是否还存在 A' ,它都可以把 A 转移到一个第三方位置(或者 A 内部有一个计数器 +1 )。这时 A 是没有被删除的,如果 lua 把 A' 发送回来,就将 A 状态复原即可。

随后,lua 如果发现自己已经不再引用 A' 了,也通知这个第三方(或再将 A 的内部计数器 +1 )。一旦第三方收到两次 A ,那么 A 就可以真的删除了。


lua 是非常容易做到这点的,如果不想侵入对象本身的实现,可以加一个壳利用 ephemeron table 来实现。

当 lua 中 B 创建过 B' 传递给 mono 时,为 B 创建一个壳对象 { B } ,放置 B -> { B } 到 ephemeron table 中。一旦 B 在自身中没有引用,就在 { B } 的 __gc 方法中把 B 复活,放在一个独立表(第三方)内。

只要 B' 在 mono 中也确认被删除,才真的移除;否则,一旦发现 mono 重新通过 B' 访问 B ,就在 ephemeron table 中重新构造引用,并在独立表中删除 B ,等待重复上面的过程。


C# 我不太熟悉,不知道有没有方法做这样的复活处理,比如为传递去 lua 的 C# 对象都创建一个壳,在壳对象的 finalizer 中是否能做同样的事情我不太确定。

不过没有也没太大关系,等吃完饭我写写,如何通过 lua 的单边特性把循环引用解开。

cloudwu commented Jan 6, 2017

我来讲讲循环引用是怎么解开的:

如果语言具有一种能力,当一个对象没有引用了后,可以感知到这点,但是又允许不删除这个对象,暂时保留起来。那么,解决这个问题就非常简单。

比如,在 mono 一边有个对象 A 它引用了 lua 中的对象 B 而 B 也反向引用回 A,那么本质上, mono 中有两个对象 A 和 B' ,lua 中有 B 和 A' 。这里 A' 是 A 的代理, B' 是 B 的代理。

如果 mono 发现它自身已经不再引用 A 了,无论是否 lua 中是否还存在 A' ,它都可以把 A 转移到一个第三方位置(或者 A 内部有一个计数器 +1 )。这时 A 是没有被删除的,如果 lua 把 A' 发送回来,就将 A 状态复原即可。

随后,lua 如果发现自己已经不再引用 A' 了,也通知这个第三方(或再将 A 的内部计数器 +1 )。一旦第三方收到两次 A ,那么 A 就可以真的删除了。


lua 是非常容易做到这点的,如果不想侵入对象本身的实现,可以加一个壳利用 ephemeron table 来实现。

当 lua 中 B 创建过 B' 传递给 mono 时,为 B 创建一个壳对象 { B } ,放置 B -> { B } 到 ephemeron table 中。一旦 B 在自身中没有引用,就在 { B } 的 __gc 方法中把 B 复活,放在一个独立表(第三方)内。

只要 B' 在 mono 中也确认被删除,才真的移除;否则,一旦发现 mono 重新通过 B' 访问 B ,就在 ephemeron table 中重新构造引用,并在独立表中删除 B ,等待重复上面的过程。


C# 我不太熟悉,不知道有没有方法做这样的复活处理,比如为传递去 lua 的 C# 对象都创建一个壳,在壳对象的 finalizer 中是否能做同样的事情我不太确定。

不过没有也没太大关系,等吃完饭我写写,如何通过 lua 的单边特性把循环引用解开。

@cloudwu

This comment has been minimized.

Show comment
Hide comment
@cloudwu

cloudwu Jan 7, 2017

上面说的只是方案,具体实现有无数种优化方法。只要实现我说的流程就可以。关键是找到外部引用,内部无引用集。不用 __gc 复活去找也没问题,自己写百来行代码遍历 lua vm 去找也挺容易。

若假设释放循环引用并不是特别需要立刻处理的事情,可以直接把 lua 中监测外部引用的弱表做成强表。也就是放出去的外部引用无论自己内部是否还有引用都不管它。然后等合适的时机(比如 loading 等),主动调用检测循环。也就是把强表改弱,强制跑完一轮 gc ,找到唯一外部引用,然后再跑后续流程。

只要你能正确理解方法,那么实现上可以去优化的地方多的是。

脑子里想不明白,更应该实现出来看看哪里出了问题。

cloudwu commented Jan 7, 2017

上面说的只是方案,具体实现有无数种优化方法。只要实现我说的流程就可以。关键是找到外部引用,内部无引用集。不用 __gc 复活去找也没问题,自己写百来行代码遍历 lua vm 去找也挺容易。

若假设释放循环引用并不是特别需要立刻处理的事情,可以直接把 lua 中监测外部引用的弱表做成强表。也就是放出去的外部引用无论自己内部是否还有引用都不管它。然后等合适的时机(比如 loading 等),主动调用检测循环。也就是把强表改弱,强制跑完一轮 gc ,找到唯一外部引用,然后再跑后续流程。

只要你能正确理解方法,那么实现上可以去优化的地方多的是。

脑子里想不明白,更应该实现出来看看哪里出了问题。

@chexiongsheng

This comment has been minimized.

Show comment
Hide comment
@chexiongsheng

chexiongsheng Jan 7, 2017

Collaborator

可能我表达的得不好吧,我不是说一个gc流程加那些对象的移动会有多耗,而是我们本来的目的就是为了解决循环引用。而由于同进同出的规则存在,循环引用相关对象会被临时访问代理的二次访问牵连而重新移回cache,那你的这算法最精妙的部分就失效了。我之所以强调那些临时访问代理多,不是为了说移动的代价大,而是想说二次访问的几率变大了。

Collaborator

chexiongsheng commented Jan 7, 2017

可能我表达的得不好吧,我不是说一个gc流程加那些对象的移动会有多耗,而是我们本来的目的就是为了解决循环引用。而由于同进同出的规则存在,循环引用相关对象会被临时访问代理的二次访问牵连而重新移回cache,那你的这算法最精妙的部分就失效了。我之所以强调那些临时访问代理多,不是为了说移动的代价大,而是想说二次访问的几率变大了。

@cloudwu

This comment has been minimized.

Show comment
Hide comment
@cloudwu

cloudwu Jan 7, 2017

同进同出是指一个 gc 循环期间,所有不被内部引用,只被外部引用的对象,不是指历史上所有的这种对象。这些对象是潜在的有内部联系的。且如果这批对象里只有 mono 代理,是可以直接清除的。

只有夹入了 lua 对象,才会导致一批进坟场。

如果夹入的 lua 临时对象真的是临时的,mono 用完也不要了,那么 mono 之后会通知这个 lua 对象删除,之后,这批对象间的联系就会被打破。就是完成了循环解除。

如果夹入的 lua 临时对象是持久的,那么本就不应该删除。如果 mono 随后访问这个对象,将永久从坟场复活,不会反复。如果想加强这种情况的优化,可以不立刻解除 mono 访问过的 lua 对象,不立刻放到弱表里,或只根据需要把 cache 属性变弱。这样可以在 gc 循环结束时,最少的筛选出这种 lua 临时对象无关的东西。

唯一出反复进出可能是:lua 这边属于临时对象,lua 自己不再引用,mono 一直持有,就是不删,但是也从来不访问这个对象。

cloudwu commented Jan 7, 2017

同进同出是指一个 gc 循环期间,所有不被内部引用,只被外部引用的对象,不是指历史上所有的这种对象。这些对象是潜在的有内部联系的。且如果这批对象里只有 mono 代理,是可以直接清除的。

只有夹入了 lua 对象,才会导致一批进坟场。

如果夹入的 lua 临时对象真的是临时的,mono 用完也不要了,那么 mono 之后会通知这个 lua 对象删除,之后,这批对象间的联系就会被打破。就是完成了循环解除。

如果夹入的 lua 临时对象是持久的,那么本就不应该删除。如果 mono 随后访问这个对象,将永久从坟场复活,不会反复。如果想加强这种情况的优化,可以不立刻解除 mono 访问过的 lua 对象,不立刻放到弱表里,或只根据需要把 cache 属性变弱。这样可以在 gc 循环结束时,最少的筛选出这种 lua 临时对象无关的东西。

唯一出反复进出可能是:lua 这边属于临时对象,lua 自己不再引用,mono 一直持有,就是不删,但是也从来不访问这个对象。

@cloudwu

This comment has been minimized.

Show comment
Hide comment
@cloudwu

cloudwu Jan 7, 2017

今天花了一天时间,搭了个架子,实现了 C# 对 Lua 的调用。主要把本贴最前面的想法用代码说明。

https://github.com/cloudwu/csbridge

完成的部分包括:可以获取 Lua 虚拟机里的全局函数;可以传递 C# 的 class 到 Lua 中被 Lua 持有,可以调用 Lua 对象。调用结果可以返回到 C# ,返回结果可以是任意类型。

尚没有完成 Lua 对 C# 的调用,和 C# 长期持有 Lua 对象的处理。

cloudwu commented Jan 7, 2017

今天花了一天时间,搭了个架子,实现了 C# 对 Lua 的调用。主要把本贴最前面的想法用代码说明。

https://github.com/cloudwu/csbridge

完成的部分包括:可以获取 Lua 虚拟机里的全局函数;可以传递 C# 的 class 到 Lua 中被 Lua 持有,可以调用 Lua 对象。调用结果可以返回到 C# ,返回结果可以是任意类型。

尚没有完成 Lua 对 C# 的调用,和 C# 长期持有 Lua 对象的处理。

@chexiongsheng

This comment has been minimized.

Show comment
Hide comment
@chexiongsheng

chexiongsheng Jan 7, 2017

Collaborator

"如果 mono 随后访问这个对象,将永久从坟场复活,不会反复。"
不太明白,如果lua所有对象都不持有这对象,gc过后不是还会进坟场么?
另外,感觉你那个在loading时大搞一次的想法靠谱,我验证下

Collaborator

chexiongsheng commented Jan 7, 2017

"如果 mono 随后访问这个对象,将永久从坟场复活,不会反复。"
不太明白,如果lua所有对象都不持有这对象,gc过后不是还会进坟场么?
另外,感觉你那个在loading时大搞一次的想法靠谱,我验证下

@cloudwu

This comment has been minimized.

Show comment
Hide comment
@cloudwu

cloudwu Jan 7, 2017

mono 每次调用,访问过的对象,都会在坟场外制造强引用,这个强引用保持多久要按需要去设计。通常会优化成访问过的对象保持一段时间再去掉强引用。

ps. csbridge 中 Lua 对 C# 的调用也完成了。基础框架算基本完成,需要配合一些代码生成工具去生成高效的供 Lua 调用的 C# 代码,或者不考虑效率用反射封装也行。

cloudwu commented Jan 7, 2017

mono 每次调用,访问过的对象,都会在坟场外制造强引用,这个强引用保持多久要按需要去设计。通常会优化成访问过的对象保持一段时间再去掉强引用。

ps. csbridge 中 Lua 对 C# 的调用也完成了。基础框架算基本完成,需要配合一些代码生成工具去生成高效的供 Lua 调用的 C# 代码,或者不考虑效率用反射封装也行。

@chexiongsheng

This comment has been minimized.

Show comment
Hide comment
@chexiongsheng

chexiongsheng Jan 8, 2017

Collaborator

这不是又要加一坨代码了。。我感觉目前为止,这方案已经复杂到不太适合放在平时的运行期了,循环引用这类不是很紧急的东西,由业务在合适的地方(比如切场景的loading之类)主动去调用解环比较合适。

Collaborator

chexiongsheng commented Jan 8, 2017

这不是又要加一坨代码了。。我感觉目前为止,这方案已经复杂到不太适合放在平时的运行期了,循环引用这类不是很紧急的东西,由业务在合适的地方(比如切场景的loading之类)主动去调用解环比较合适。

@cloudwu

This comment has been minimized.

Show comment
Hide comment
@cloudwu

cloudwu Jan 8, 2017

不用加一坨,我的实现中大约加了 3 行 lua 代码完成延迟解引用。就是另外给一个强表,访问时在弱表和强表各加一次引用。之后定期把强表清掉。

ps. 最要紧的是,这些管理引用关系(以及其它一些)逻辑,一定一定要用 lua 自己去写,而不要在 C 代码利用 lua c api 写上一大坨。

cloudwu commented Jan 8, 2017

不用加一坨,我的实现中大约加了 3 行 lua 代码完成延迟解引用。就是另外给一个强表,访问时在弱表和强表各加一次引用。之后定期把强表清掉。

ps. 最要紧的是,这些管理引用关系(以及其它一些)逻辑,一定一定要用 lua 自己去写,而不要在 C 代码利用 lua c api 写上一大坨。

@chexiongsheng

This comment has been minimized.

Show comment
Hide comment
@chexiongsheng

chexiongsheng Jan 9, 2017

Collaborator

ps一下,你前面说的“每个用 u3d 开发的公司都会顺手搞个 某lua ,反正 C# 不受待见,都想用 lua 做开发”
我先举个反面例子:我前同事公司的三个项目,第一个项目是C#+Lua,做完这项目他们表示完全受不了Lua。第二个项目只用C#,结果后悔了,后悔的原因是没法热更,一些小bug(小主要是代码改动很小,但对用户影响还是挺大的)只能干等大版本,现在第三个项目开发中,准备用xLua的hotfix特性。
你云大的公司,程序员都是你面试(或者你面试过的人面试的),或者冲着你云大过去的,那当然“C# 不受待见,都想用 lua 做开发”。
我个人用lua也有一段时间了,从11年底开始到现在,12年初就搞过类似xLua的东西,当时只是在那家小公司里头内部用,没放出去。那时前后台框架都是我主导开发,lua全栈开发。说这些只是想说明我个人对lua是有深厚的感情的,至少不会黑它。我个人的看法是C#也是很好的语言,如果综合语言设计,语言实现,库,社区,工具链等来看,比lua要好。

Collaborator

chexiongsheng commented Jan 9, 2017

ps一下,你前面说的“每个用 u3d 开发的公司都会顺手搞个 某lua ,反正 C# 不受待见,都想用 lua 做开发”
我先举个反面例子:我前同事公司的三个项目,第一个项目是C#+Lua,做完这项目他们表示完全受不了Lua。第二个项目只用C#,结果后悔了,后悔的原因是没法热更,一些小bug(小主要是代码改动很小,但对用户影响还是挺大的)只能干等大版本,现在第三个项目开发中,准备用xLua的hotfix特性。
你云大的公司,程序员都是你面试(或者你面试过的人面试的),或者冲着你云大过去的,那当然“C# 不受待见,都想用 lua 做开发”。
我个人用lua也有一段时间了,从11年底开始到现在,12年初就搞过类似xLua的东西,当时只是在那家小公司里头内部用,没放出去。那时前后台框架都是我主导开发,lua全栈开发。说这些只是想说明我个人对lua是有深厚的感情的,至少不会黑它。我个人的看法是C#也是很好的语言,如果综合语言设计,语言实现,库,社区,工具链等来看,比lua要好。

@cloudwu

This comment has been minimized.

Show comment
Hide comment
@cloudwu

cloudwu Jan 9, 2017

设计框架的人喜欢 C# 很正常,对于固定需求,静态语言更有优势。但多变的业务逻辑还是动态性强的语言更好。纯 C# 当然比 C# + Lua 做项目好,准确说是纯用一种语言肯定比混合语言开发好。

如果单论语言设计, C# 和 Java 在一个层次上,比 C++ 好一点,和 Lua 的 “语言设计" 这点还是有很大差距的。

cloudwu commented Jan 9, 2017

设计框架的人喜欢 C# 很正常,对于固定需求,静态语言更有优势。但多变的业务逻辑还是动态性强的语言更好。纯 C# 当然比 C# + Lua 做项目好,准确说是纯用一种语言肯定比混合语言开发好。

如果单论语言设计, C# 和 Java 在一个层次上,比 C++ 好一点,和 Lua 的 “语言设计" 这点还是有很大差距的。

@cloudwu

This comment has been minimized.

Show comment
Hide comment
@cloudwu

cloudwu Jan 9, 2017

如果只是想更新代码,我觉得更好的方案是再嵌一个 C# , 统一用一种语言,而不是再引入一门新语言。

比方说,可以试试 http://www.paxcompiler.com/paxscriptnet/about.htm

cloudwu commented Jan 9, 2017

如果只是想更新代码,我觉得更好的方案是再嵌一个 C# , 统一用一种语言,而不是再引入一门新语言。

比方说,可以试试 http://www.paxcompiler.com/paxscriptnet/about.htm

@chexiongsheng

This comment has been minimized.

Show comment
Hide comment
@chexiongsheng

chexiongsheng Jan 9, 2017

Collaborator

@cloudwu 首先只谈语言设计,不谈语言实现,库,社区,工具链,有点耍流氓。
况且,就是“语言设计”,我也没觉得C#差了,lua的设计可能更少错误,个人觉得也是因为做得少,少做少错。你前面也强调了,你对C#不熟。。。

Collaborator

chexiongsheng commented Jan 9, 2017

@cloudwu 首先只谈语言设计,不谈语言实现,库,社区,工具链,有点耍流氓。
况且,就是“语言设计”,我也没觉得C#差了,lua的设计可能更少错误,个人觉得也是因为做得少,少做少错。你前面也强调了,你对C#不熟。。。

@jinqi166

This comment has been minimized.

Show comment
Hide comment
@jinqi166

jinqi166 Jan 9, 2017

到此为止吧,在讨论下去就跟本帖无关了,只是大家看待问题的触发点和角度不同而已,我觉得这个issue可以close掉了

jinqi166 commented Jan 9, 2017

到此为止吧,在讨论下去就跟本帖无关了,只是大家看待问题的触发点和角度不同而已,我觉得这个issue可以close掉了

@cloudwu

This comment has been minimized.

Show comment
Hide comment
@cloudwu

cloudwu Jan 9, 2017

实现好不好就看代码是否清晰,好不好理解。按这个标准, lua 的官方实现和 mono 的实现哪个好呢?

做的少,没有多余的东西,已有的东西又是完备的,足够解决问题。这就是做的最好的地方了。设计的足够少已经体现了语言设计的功力。

而 C# 做了那么多,结果基础的 coroutine (yield 和异常不兼容)和 closure (不能引用外部 ref out 变量) 都不够完备 …… 不是高下立分么?

cloudwu commented Jan 9, 2017

实现好不好就看代码是否清晰,好不好理解。按这个标准, lua 的官方实现和 mono 的实现哪个好呢?

做的少,没有多余的东西,已有的东西又是完备的,足够解决问题。这就是做的最好的地方了。设计的足够少已经体现了语言设计的功力。

而 C# 做了那么多,结果基础的 coroutine (yield 和异常不兼容)和 closure (不能引用外部 ref out 变量) 都不够完备 …… 不是高下立分么?

@chexiongsheng

This comment has been minimized.

Show comment
Hide comment
@chexiongsheng

chexiongsheng Jan 9, 2017

Collaborator

你说的那个coroutine不是C#的,这是Unity用generator模拟出来的货。。c#在异步并发的解决方案是await。

ref,out在lua没有对应物,不好比较。

Collaborator

chexiongsheng commented Jan 9, 2017

你说的那个coroutine不是C#的,这是Unity用generator模拟出来的货。。c#在异步并发的解决方案是await。

ref,out在lua没有对应物,不好比较。

@chexiongsheng

This comment has been minimized.

Show comment
Hide comment
@chexiongsheng

chexiongsheng Jan 9, 2017

Collaborator

还有,mono也代表不了.net

Collaborator

chexiongsheng commented Jan 9, 2017

还有,mono也代表不了.net

@chexiongsheng

This comment has been minimized.

Show comment
Hide comment
@chexiongsheng

chexiongsheng Jan 9, 2017

Collaborator

"代码是否清晰,好不好理解",如果要拿mono代表.net话,是不是也可以拿luajit来代表一下lua呢?

Collaborator

chexiongsheng commented Jan 9, 2017

"代码是否清晰,好不好理解",如果要拿mono代表.net话,是不是也可以拿luajit来代表一下lua呢?

@dwing4g

This comment has been minimized.

Show comment
Hide comment
@dwing4g

dwing4g Jan 9, 2017

C#的流行真是拜Unity所赐啊, 游戏引擎基本都是C/C++写的, 接入Lua语言本来是很自然方便的事, 基本没有自研引擎的公司能想到开放C#语言来二次开发.
结果Unity一出, 既不想暴露C++接口, 又想要运行效率, 结果接个Mono来支持C#开发, 搞得与Lua之间的互操作极其复杂, 真希望Unity开发者以后直接暴露C接口就天下太平了(或许从il2cpp中能找到接口?)...
话说, 新版Unity的mono已经支持sgen了?

dwing4g commented Jan 9, 2017

C#的流行真是拜Unity所赐啊, 游戏引擎基本都是C/C++写的, 接入Lua语言本来是很自然方便的事, 基本没有自研引擎的公司能想到开放C#语言来二次开发.
结果Unity一出, 既不想暴露C++接口, 又想要运行效率, 结果接个Mono来支持C#开发, 搞得与Lua之间的互操作极其复杂, 真希望Unity开发者以后直接暴露C接口就天下太平了(或许从il2cpp中能找到接口?)...
话说, 新版Unity的mono已经支持sgen了?

@cloudwu

This comment has been minimized.

Show comment
Hide comment
@cloudwu

cloudwu Jan 9, 2017

luajit 和 lua 特性都不同,是不同的语言,或者说,是一种 lua 的方言。luajit 的 coroutine 也是不完备的,靠给给系统打补丁完成。其它许多语言特性也遗留了 lua 一个古老的版本中的设计冗余,比如把全局变量特殊看待,引入函数环境这些多余的东西,而这些 Lua 本身已经自己修正了。

找 mono 代表 .net 的实现质量,真的是因为微软家的不开源啊。

ref out 在 lua 中没有对应物不正说明 C# 加入了一些可以不要,实现了却和其它语言特性放在一起不完备的东西?

coroutine 和并发是两个层面的东西,coroutine 指的语言是否能把执行序列作为 first class 来看待。在这点上 C# 做的并不完备。

cloudwu commented Jan 9, 2017

luajit 和 lua 特性都不同,是不同的语言,或者说,是一种 lua 的方言。luajit 的 coroutine 也是不完备的,靠给给系统打补丁完成。其它许多语言特性也遗留了 lua 一个古老的版本中的设计冗余,比如把全局变量特殊看待,引入函数环境这些多余的东西,而这些 Lua 本身已经自己修正了。

找 mono 代表 .net 的实现质量,真的是因为微软家的不开源啊。

ref out 在 lua 中没有对应物不正说明 C# 加入了一些可以不要,实现了却和其它语言特性放在一起不完备的东西?

coroutine 和并发是两个层面的东西,coroutine 指的语言是否能把执行序列作为 first class 来看待。在这点上 C# 做的并不完备。

@chexiongsheng

This comment has been minimized.

Show comment
Hide comment
@chexiongsheng

chexiongsheng Jan 9, 2017

Collaborator

感觉ref,out一个是实现多返回,另外一个是性能考虑吧。我猜的,作者才知道。
不能因为某语言没有某个特性也是完备的,就否掉别的语言那个特性吧?Brainfuck也是图灵完备的,语言元素更少,那是不是可以完全把其它所有语言的大多数特性都否掉了?
lua的设计也没有完全正交,比如它的各种语法糖。
c# await返回的task也是first class的,就一个状态机,本质上和coroutine的stack没区别。

Collaborator

chexiongsheng commented Jan 9, 2017

感觉ref,out一个是实现多返回,另外一个是性能考虑吧。我猜的,作者才知道。
不能因为某语言没有某个特性也是完备的,就否掉别的语言那个特性吧?Brainfuck也是图灵完备的,语言元素更少,那是不是可以完全把其它所有语言的大多数特性都否掉了?
lua的设计也没有完全正交,比如它的各种语法糖。
c# await返回的task也是first class的,就一个状态机,本质上和coroutine的stack没区别。

@dwing4g

This comment has been minimized.

Show comment
Hide comment
@dwing4g

dwing4g Jan 9, 2017

ref out用在struct类型的参数, 可以省很多开销. 当然主要还是为了解决多返回值的问题, 毕竟C系语言都不习惯返回多个值, 所以参考了指针参数的方式.
C#最大的优点是开发效率,运行效率,除错效率都非常好; Lua最大的优点是简单,轻量级; 各有无法替代的优势, 也是对手的劣势, 适用环境区别很大, 就不要争了, 所谓一些语言细节都是次要的, 没有十全十美的语言, 对少量缺陷要容忍. 要不是Unity的掺合, 平时没见过比较C#和Lua的.

dwing4g commented Jan 9, 2017

ref out用在struct类型的参数, 可以省很多开销. 当然主要还是为了解决多返回值的问题, 毕竟C系语言都不习惯返回多个值, 所以参考了指针参数的方式.
C#最大的优点是开发效率,运行效率,除错效率都非常好; Lua最大的优点是简单,轻量级; 各有无法替代的优势, 也是对手的劣势, 适用环境区别很大, 就不要争了, 所谓一些语言细节都是次要的, 没有十全十美的语言, 对少量缺陷要容忍. 要不是Unity的掺合, 平时没见过比较C#和Lua的.

@cloudwu

This comment has been minimized.

Show comment
Hide comment
@cloudwu

cloudwu Jan 9, 2017

Brainfuck 没有 coroutine 也没有 closure ,完全不能放在一起讨论。图灵完备只是一个必要条件,不是充分条件。

async/await 解决的只是并行多个执行序分离的问题,并不能直接控制执行序,也就是缺乏 yield 的能力。比如要实现一个迭代器,还是得用 IEnumerable 和 yield ,继而上面提到的和异常系统的兼容性依旧存在。

如果增加一个特性而不考虑和已有的一个特性的兼容性,而另一种语言有相同的两个特性却能解决好。这里谈的不就是设计好坏吗?

cloudwu commented Jan 9, 2017

Brainfuck 没有 coroutine 也没有 closure ,完全不能放在一起讨论。图灵完备只是一个必要条件,不是充分条件。

async/await 解决的只是并行多个执行序分离的问题,并不能直接控制执行序,也就是缺乏 yield 的能力。比如要实现一个迭代器,还是得用 IEnumerable 和 yield ,继而上面提到的和异常系统的兼容性依旧存在。

如果增加一个特性而不考虑和已有的一个特性的兼容性,而另一种语言有相同的两个特性却能解决好。这里谈的不就是设计好坏吗?

@liiir1985

This comment has been minimized.

Show comment
Hide comment
@liiir1985

liiir1985 Jan 9, 2017

用IEnumerable来做coroutine本来就是unity里才有的特有用法,在所有其他.net应用中都是不存在的
我也想不出来在其他环境中的.net为什么会需要用coroutine,而不是直接用async/await
C#在设计的时候就本来不是这么使用的,只是Unity为了实现特定的功能这么用了。早期Unity的开发团队也是可以说对c#非常不熟,定义的接口也是很多不符合.Net规范的
ref关键字最主要是为了值类型的传参效率,ref int 在IL层面= int&,closure取之前栈上的int&并没有任何意义,甚至可能取到野指针
对于在非值类型上用ref关键字…… 绝大多数情况纯粹是使用者理解错误的错误用法

c++/c上配lua无可厚非,也没必要跟c#做比较
c#在ide,调试,语法糖等方面使得他的开发效率是非常高的,如果unity能支持.net 4.6 的profile,加上dynamic关键字,用起来跟lua也没有太大差别
至于开发效率,还有ide这些,个人感觉不同,也没有特别大的对比意义,就像vim/emacs还是VisualStudio好用,不同环境和团队有自己的见解,横向对比没有意义

liiir1985 commented Jan 9, 2017

用IEnumerable来做coroutine本来就是unity里才有的特有用法,在所有其他.net应用中都是不存在的
我也想不出来在其他环境中的.net为什么会需要用coroutine,而不是直接用async/await
C#在设计的时候就本来不是这么使用的,只是Unity为了实现特定的功能这么用了。早期Unity的开发团队也是可以说对c#非常不熟,定义的接口也是很多不符合.Net规范的
ref关键字最主要是为了值类型的传参效率,ref int 在IL层面= int&,closure取之前栈上的int&并没有任何意义,甚至可能取到野指针
对于在非值类型上用ref关键字…… 绝大多数情况纯粹是使用者理解错误的错误用法

c++/c上配lua无可厚非,也没必要跟c#做比较
c#在ide,调试,语法糖等方面使得他的开发效率是非常高的,如果unity能支持.net 4.6 的profile,加上dynamic关键字,用起来跟lua也没有太大差别
至于开发效率,还有ide这些,个人感觉不同,也没有特别大的对比意义,就像vim/emacs还是VisualStudio好用,不同环境和团队有自己的见解,横向对比没有意义

@chexiongsheng

This comment has been minimized.

Show comment
Hide comment
@chexiongsheng

chexiongsheng Jan 9, 2017

Collaborator

Unity不支持async/await是因为它用的mono版本过低

Collaborator

chexiongsheng commented Jan 9, 2017

Unity不支持async/await是因为它用的mono版本过低

@dwing4g

This comment has been minimized.

Show comment
Hide comment
@dwing4g

dwing4g Jan 9, 2017

Unity 5.5大幅更新了mono, 貌似支持了async/await, 但gc貌似尚未支持sgen.

dwing4g commented Jan 9, 2017

Unity 5.5大幅更新了mono, 貌似支持了async/await, 但gc貌似尚未支持sgen.

@jinqi166

This comment has been minimized.

Show comment
Hide comment
@jinqi166

jinqi166 Jan 9, 2017

你的逻辑代码在unity下只能在主线程跑,现在是将来也是,至于渲染上使用多线程你不用关心也不管你的事!开发者大会上有人问多线程渲染是否安全以及用户逻辑是否可以使用多线程,官方答案很坚定:“逻辑部分是线程安全的,因为你只有一个主线程”

jinqi166 commented Jan 9, 2017

你的逻辑代码在unity下只能在主线程跑,现在是将来也是,至于渲染上使用多线程你不用关心也不管你的事!开发者大会上有人问多线程渲染是否安全以及用户逻辑是否可以使用多线程,官方答案很坚定:“逻辑部分是线程安全的,因为你只有一个主线程”

@cloudwu

This comment has been minimized.

Show comment
Hide comment
@cloudwu

cloudwu Jan 9, 2017

coroutine 一个最大的用途就是用来干净的实现迭代器,迭代器的意义就在于两个不同的执行序间有通讯的能力。语言不支持没关系,C++ 就不支持,用 C++ 开发的项目当然可以选别的方法来解决问题。这当然不是必不可少的语言特性。

但如果来讨论语言设计好坏,C# 等于支持了一半,这就很尴尬了。

对于 closure 来说,一切外部值都是引用。比如在 lua 里,你写 local a = 1 ,那么后续有 closure 的话,自然也是引用的 &a ,能不能保证不是野指针是语言在实现这个特性时需要保证的东西。比如 lua 里将一个栈变量经过 closure 封闭后内部变成一个引用对象,就是运行时处理的过程。C# 想做当然也做的出来,选择直接禁止某种用法也没有说有错。只能说语言设计时考虑的不完备。

cloudwu commented Jan 9, 2017

coroutine 一个最大的用途就是用来干净的实现迭代器,迭代器的意义就在于两个不同的执行序间有通讯的能力。语言不支持没关系,C++ 就不支持,用 C++ 开发的项目当然可以选别的方法来解决问题。这当然不是必不可少的语言特性。

但如果来讨论语言设计好坏,C# 等于支持了一半,这就很尴尬了。

对于 closure 来说,一切外部值都是引用。比如在 lua 里,你写 local a = 1 ,那么后续有 closure 的话,自然也是引用的 &a ,能不能保证不是野指针是语言在实现这个特性时需要保证的东西。比如 lua 里将一个栈变量经过 closure 封闭后内部变成一个引用对象,就是运行时处理的过程。C# 想做当然也做的出来,选择直接禁止某种用法也没有说有错。只能说语言设计时考虑的不完备。

@dwing4g

This comment has been minimized.

Show comment
Hide comment
@dwing4g

dwing4g Jan 9, 2017

话说, C#的yield/async/await都不是真正的coroutine, 编译器搞的语法糖而已; Lua的coroutine是真的.

dwing4g commented Jan 9, 2017

话说, C#的yield/async/await都不是真正的coroutine, 编译器搞的语法糖而已; Lua的coroutine是真的.

@liiir1985

This comment has been minimized.

Show comment
Hide comment
@liiir1985

liiir1985 Jan 9, 2017

mono 2.6最坑的不是支不支持async/await,而是他用的Boehm GC,这东西拿给c++用还凑合,给c#用是巨坑无比,毕竟mono2.6已经是快10年前的东西了,希望早日把这个破gc换了,换成generational的

liiir1985 commented Jan 9, 2017

mono 2.6最坑的不是支不支持async/await,而是他用的Boehm GC,这东西拿给c++用还凑合,给c#用是巨坑无比,毕竟mono2.6已经是快10年前的东西了,希望早日把这个破gc换了,换成generational的

@liiir1985

This comment has been minimized.

Show comment
Hide comment
@liiir1985

liiir1985 Jan 9, 2017

关于逻辑代码是否只能在主线程跑,要是你看了Keynote 2016的话就知道,从Unity5.6开始,unity将步入全面多线程的时代,会引入一个C# Jobsystem的东西让开发者多线程跑逻辑,他还提供了一个逻辑层多线程的Demo, 控制20w条鱼的不同轨迹运动

liiir1985 commented Jan 9, 2017

关于逻辑代码是否只能在主线程跑,要是你看了Keynote 2016的话就知道,从Unity5.6开始,unity将步入全面多线程的时代,会引入一个C# Jobsystem的东西让开发者多线程跑逻辑,他还提供了一个逻辑层多线程的Demo, 控制20w条鱼的不同轨迹运动

@dwing4g

This comment has been minimized.

Show comment
Hide comment
@dwing4g

dwing4g Jan 9, 2017

想要支持真正的coroutine, 对底层改动非常大, 除非一开始设计.NET的时候就考虑到有这个东西, 现在再支持恐怕弊大于利了, 所以也就只能用编译器技巧来实现表面差不多的玩意, 这也是没办法的事. Lua这种轻量级的运行环境反而容易实现. 不过话说回来, 这一点对C#来说无可厚非, 有点小缺陷没什么影响的, 完全无法撼动C#的其它优势, 除了影响完美主义者的心理作用.
关于多线程, 必要性真不大, 现在就很好了, 没听说有CPU瓶颈的, 即使遇到大多都有设计问题, 个别特殊情况自己创建线程即可.

dwing4g commented Jan 9, 2017

想要支持真正的coroutine, 对底层改动非常大, 除非一开始设计.NET的时候就考虑到有这个东西, 现在再支持恐怕弊大于利了, 所以也就只能用编译器技巧来实现表面差不多的玩意, 这也是没办法的事. Lua这种轻量级的运行环境反而容易实现. 不过话说回来, 这一点对C#来说无可厚非, 有点小缺陷没什么影响的, 完全无法撼动C#的其它优势, 除了影响完美主义者的心理作用.
关于多线程, 必要性真不大, 现在就很好了, 没听说有CPU瓶颈的, 即使遇到大多都有设计问题, 个别特殊情况自己创建线程即可.

@liiir1985

This comment has been minimized.

Show comment
Hide comment
@liiir1985

liiir1985 Jan 9, 2017

多线程的确不是一个非有不可的东西,对于unity而言,加入逻辑层多线程是因为现在他把渲染多线程了之后,瓶颈的确出现在了CPU层。他的渲染多线程是通过一套自己的JobSystem实现的,CPU层往JobSystem添加Job,然后渲染层多线程并行处理Job列表,然后现在的瓶颈就在排job这层了,所以他退出C#JobSystem只是个顺带的,底层实际上是他实现了Transform等关键组建的线程安全,从而达到可以多线程排Job

liiir1985 commented Jan 9, 2017

多线程的确不是一个非有不可的东西,对于unity而言,加入逻辑层多线程是因为现在他把渲染多线程了之后,瓶颈的确出现在了CPU层。他的渲染多线程是通过一套自己的JobSystem实现的,CPU层往JobSystem添加Job,然后渲染层多线程并行处理Job列表,然后现在的瓶颈就在排job这层了,所以他退出C#JobSystem只是个顺带的,底层实际上是他实现了Transform等关键组建的线程安全,从而达到可以多线程排Job

@cloudwu

This comment has been minimized.

Show comment
Hide comment
@cloudwu

cloudwu Jan 9, 2017

手持设备上就不要去想利用多线程去提高性能了,一个线程能做的事情,安排到几个线程做只会更浪费电。排查热点,想想哪里做的不对需要改进才是正途。

cloudwu commented Jan 9, 2017

手持设备上就不要去想利用多线程去提高性能了,一个线程能做的事情,安排到几个线程做只会更浪费电。排查热点,想想哪里做的不对需要改进才是正途。

@cloudwu cloudwu closed this Jan 9, 2017

@dragengt

This comment has been minimized.

Show comment
Hide comment
@dragengt

dragengt Jan 17, 2017

从知乎过来留名……支持云风大大。

dragengt commented Jan 17, 2017

从知乎过来留名……支持云风大大。

@ElPsyCongree

This comment has been minimized.

Show comment
Hide comment
@ElPsyCongree

ElPsyCongree Aug 14, 2017

c# 所有调用的lua函数都通过pcall并装载lua error错误处理函数,怎么可能漏掉lua error呢

ElPsyCongree commented Aug 14, 2017

c# 所有调用的lua函数都通过pcall并装载lua error错误处理函数,怎么可能漏掉lua error呢

@czjone

This comment has been minimized.

Show comment
Hide comment
@czjone

czjone Jan 18, 2018

看了半天 ,主要是在架构分层上没有明确分层。如果把lua提高到更高的业务层:那就明白云风说的是很有道理的~虽然xlua已很优秀,但是对正确性的要求不是很高,程序不管怎么做,正确性绝对是第一要求。

czjone commented Jan 18, 2018

看了半天 ,主要是在架构分层上没有明确分层。如果把lua提高到更高的业务层:那就明白云风说的是很有道理的~虽然xlua已很优秀,但是对正确性的要求不是很高,程序不管怎么做,正确性绝对是第一要求。

@chexiongsheng

This comment has been minimized.

Show comment
Hide comment
@chexiongsheng

chexiongsheng Jan 18, 2018

Collaborator

@czjone talk is cheap。
你哪里看到没明确分层呢?争论其实是参数打解包的方式而已,那几个push,to之类lua api本质上也是打解包的一种。
哪里看到不正确呢?
拿个真实项目做例子吧:mmorpg,2015年11月开始用xlua,2017年7月上线,项目成员刚刚看了下,前端开发19人,后端11人。上线前ios的__TEXT段就超标了,上线至少一两周内在appstore畅销榜前十,应用lua的范围是除战斗之外均用了lua。规模、装机量,使用程度应该够代表性了吧?
上线至今无任何xlua相关的bug,这样的“正确性”有多少这种规模的库能做到呢?

Collaborator

chexiongsheng commented Jan 18, 2018

@czjone talk is cheap。
你哪里看到没明确分层呢?争论其实是参数打解包的方式而已,那几个push,to之类lua api本质上也是打解包的一种。
哪里看到不正确呢?
拿个真实项目做例子吧:mmorpg,2015年11月开始用xlua,2017年7月上线,项目成员刚刚看了下,前端开发19人,后端11人。上线前ios的__TEXT段就超标了,上线至少一两周内在appstore畅销榜前十,应用lua的范围是除战斗之外均用了lua。规模、装机量,使用程度应该够代表性了吧?
上线至今无任何xlua相关的bug,这样的“正确性”有多少这种规模的库能做到呢?

@ElPsyCongree

This comment has been minimized.

Show comment
Hide comment
@ElPsyCongree

ElPsyCongree Jan 18, 2018

这个问题本身都不需要争论的,如果使用 c/s封层通讯,只会无缘无故的导致 字节copy,以及效率低下的反序列化。抛弃 使用LuaState这个栈进行通讯,本身就得不偿失,而xlua的异常处理本身就不是很大,c#、lua两个都是高级语言,只要好好利用 c#的 try catch和lua的 pcall,发生无法捕捉的异常,简直不可能

ElPsyCongree commented Jan 18, 2018

这个问题本身都不需要争论的,如果使用 c/s封层通讯,只会无缘无故的导致 字节copy,以及效率低下的反序列化。抛弃 使用LuaState这个栈进行通讯,本身就得不偿失,而xlua的异常处理本身就不是很大,c#、lua两个都是高级语言,只要好好利用 c#的 try catch和lua的 pcall,发生无法捕捉的异常,简直不可能

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment