提升OpScript性能
了解Lua垃圾收集器
在Lua中,可以在堆栈或堆上分配内存。通过Lua的垃圾回收器回收在堆上分配的对象或内存。因此,当垃圾收集器正在运行时,您的Lua代码无法前进,减少垃圾收集器必须运行的频率或垃圾收集器必须收集的垃圾数量有助于提高OpScript的性能。
以下Lua构造虽然不错,但会导致创建一个新对象,随后必须在垃圾回收期间对其进行清理。您应该特别注意这些构造在循环中使用。
- String_one..string_two -字符串连接或任何字符串创建函数都可能导致创建新对象。由于Lua字符串是唯一的,因此它首先检查以确定该字符串是否已在其他位置创建。
- { … } -每次执行表构造函数时,都会创建一个新表。
- function() ... end -执行函数语句会创建一个闭包。如果在n次迭代的循环中执行此操作,则将创建n个闭包。
注意: 有关更多信息,请参见lua-users.org。
避免循环中使用“ ..”
作为了解Lua垃圾收集器的一个具体示例,在此建议中,我们探讨了字符串连接运算符“ ..”的用法,该字符串因其方便的简写符号而常用于OpScripts。
例如,请考虑以下用于构建L系统描述的代码:
local结果=“” for我= 1,#inputStr do local char = inputStr:sub(i,i) local ruleStr = rulesTable [char] if ruleStr then 结果= result..ruleStr else 结果= result..char end end return结果 |
result
被紧密地附加到。尽管很方便,但这会导致大量的字符串分配和较差的性能。仅在循环完成后,才可以重写前面的代码以处理所有串联,如下所示:
本地buf = {} for我= 1,#inputStr do local char = inputStr:sub(i,i) 本地ruleStr = rulesTable [char] if ruleStr then buf [#buf + 1] = ruleStr else buf [#buf + 1] =字符 end end 返回table.concat(buf) |
将上述示例作为大型示例的一部分运行Katana场景文件将此功能的场景处理时间减少了约2.5倍,如下表所示:
字符串方法 |
时间 |
“ ..”运算符 |
7.011秒 |
Table.concat() |
2.681秒 |
考虑创建自定义C ++ Ops来替换昂贵的OpScripts
虽然Lua和OpScript API非常方便和通用,但是占场景遍历时间很大一部分的OpScript可能更适合作为自定义C ++ Ops。
作为一个具体示例,请考虑以下OpScript,它将目标位置放置在统一框中的随机位置,方向和比例。
本地哈希= ExpressionMath.stablehash(Interface.GetOutputName())) math.randomseed(哈希)
local昏暗= 10 local s = 0.15
local sx = s *(1 + 0.5 * math.random()) local sy = s *(1 + 0.5 * math.random()) local sz = s *(1 + 0.5 * math.random())
local tx = 2 * dim *(math.random()-0.5) local ty =昏暗* math.random()+ sy local tz = 2 * dim *(math.random()-0.5)
local斧= math.random() local ay = math.random() local az = math.random() local轴= Imath.V3d(ax,ay,az):normalize() local角度= 360 * math.random()
local翻译= Imath.V3d(tx,ty,tz) local旋转= Imath.V4d(角度,axis.x,axis.y,axis.z) local标度= Imath.V3d(sx,sy,sz)
Interface.SetAttr(“ xform.group0.translate”, DoubleAttribute(translate:toTable(),3)) Interface.SetAttr(“ xform.group0.rotate”, DoubleAttribute(rotate:toTable(),4)) Interface.SetAttr(“ xform.group0.scale”, DoubleAttribute(scale:toTable(),3)) |
(这样的OpScript当然是人为设计的,但是可以想象用定义良好的分布代替随机性来放置几何图形。)
该OpScript可以重写为以下C ++ Op:
#include <FnAttribute / FnAttribute.h> #include <FnGeolib / op / FnGeolibOp.h> #include <FnGeolibServices / FnExpressionMath.h> #include <FnGeolibServices / FnGeolibCookInterfaceUtilsService.h> #include <FnPluginSystem / FnPlugin.h>
#include <cstdlib>
inline double getRandom() { return (doublerand()/( double )RAND_MAX; } struct DistributeGeometryOp: public铸造厂::武士刀:: GeolibOp { static void设置(Foundry :: Katana :: GeolibSetupInterface&interface) { interface.setThreading(Foundry :: Katana :: GeolibSetupInterface :: ThreadModeConcurrent); } static void cook(Foundry :: Katana :: GeolibCookInterface&interface) { srand(FnGeolibServices :: FnExpressionMath :: stablehash(interface.getOutputName())); double昏暗= 10.0; double s = 0.15f; double sx = s *(1.0 + 0.5f * getRandom()); double sy = s *(1.0 + 0.5f * getRandom()); double sz = s *(1.0 + 0.5f * getRandom());
double tx = 2.0f * dim *(getRandom()-0.5f); double ty = dim * getRandom()+ sy; double tz = 2.0f * dim *(getRandom()-0.5f);
double斧= getRandom(); double ay = getRandom(); double az = getRandom();
double axisLen = sqrt(ax * ax + ay * ay + az * az); 斧/ = axisLen; ay / = axisLen; az / = axisLen;
double角度= 360.0 * getRandom();
double translation [] = {tx,ty,tz}; double旋转[] = {角度,斧头,ay,az}; double scale [] = {sx,sy,sz}; interface.setAttr( “ Xform.group0.translate”, FnAttribute :: DoubleAttribute(translate,3,3)); interface.setAttr( “ Xform.group0.rotate”, FnAttribute :: DoubleAttribute(rotate,4,4)); interface.setAttr( “ Xform.group0.scale”, FnAttribute :: DoubleAttribute(scale,3,3)); } }; DEFINE_GEOLIBOP_PLUGIN(DistributeGeometryOp); void registerPlugins() { REGISTER_PLUGIN(DistributeGeometryOp,“ DistributeGeometryOp”,0,1); } |
根据不同的随机分布,这些操作会产生相同的场景。但是,C ++版本的执行速度大约是其3.2倍:当用于放置10,000个几何图元时,Ops使用以下资源:
运维 |
CPU时间 |
使用的内存 |
OpScript |
0.989秒 |
54.24 MiB |
C ++作业 |
0.310秒 |
53.67 MiB |
在这种情况下,内存使用情况相似。但是,如果OpScript正在分配和释放许多小对象,则转换为C ++还可节省在Lua垃圾回收上花费的时间。