改善您的操作
考虑使用可伸缩的内存分配器
内存分配器从用户应用程序接收内存请求(通过malloc()),并以应用程序可以使用的内存位置的地址作为响应。当应用程序使用完内存后,可以将该内存返回给分配器(通过free())。鉴于内存在大多数应用程序中扮演着核心角色,因此内存分配器的性能至关重要。
在高度多线程的应用程序中使用时,操作系统和C标准库实现附带的大多数通用分配器可能会遇到许多陷阱:
- 对操作系统的内存块请求超出了要求。对操作系统的每个请求都会导致系统调用,该系统调用要求发出请求的CPU切换到更高的特权级别并以内核模式执行代码。如果分配器向操作系统发出的分配内存的请求超出了要求,则应用程序的性能可能会受到影响。
- 使用并发原语进行互斥。许多通用分配器并不是为在多线程应用程序中使用而设计的,而是为了保护其关键部分而使用互斥体或其他并发原语来强制执行正确性。在高度多线程的应用程序中使用互斥锁(直接或间接)可能会对应用程序的性能产生负面影响。
在大多数GNU / Linux操作系统上使用的通用分配器是ptmalloc2或其变体。尽管ptmalloc2的内存开销较低,但由于要从多个线程发出请求,因此很难扩展。因此,如果分析表明您的场景在呼叫上花费了大量时间malloc()/free()您可以考虑将通用分配器替换为可扩展的替代方案。
Geolib3-MT在内部使用多种技术来处理内存分配,包括明智地使用jemalloc以满足关键路径中对内存的一些请求。
尽可能将自定义操作标记为线程安全
Geolib3-MT旨在通过并行烹饪位置来跨多个核心扩展。一个Op可以通过调用来声明它不是线程安全的FnGeolibCookInterface::setThreading(),在这种情况下,必须用Op操作烹饪位置时获得全局执行锁(GEL)。这样可以防止其他线程不安全的操作对象从烹饪位置进入,因此很可能导致场景遍历时效率低下。在对场景进行概要分析时,首先确定并转换所有线程不安全的操作。
线程不安全操作可以轻松地从Render Log:如果在Op树中检测到线程不安全的Op,则会发出警告:
[WARN plugins.Geolib3-MT.OpTree]:Op(<opName>:<opType>)被标记为ThreadModeGlobalUnsafe-这可能会降低性能。
将自定义操作标记为可折叠
现实世界场景包含以下“操作链”构造的许多实例:
鉴于多种用途,这可能是出于多种原因造成的AttributeSet节点,它们经常用于hotfix场景以确保在渲染时存在给定的启用属性。或者,操作序列可以表示在创建给定节点图期间要采取的一组逻辑步骤。但是,类似的Ops链对于Geolib3-MT既代表潜在的开销,又代表优化机会。 改善您的操作。
场景可以利用Geolib3-MT的能力来优化Op树的拓扑。自定义操作可能表明可以通过致电将其折叠FnGeolibCookInterface::setOpsCollapsible()。此功能需要FnAttribute::StringAttribute作为参数,其值指示折叠的Ops参数传递到的属性的名称。
例如,考虑四个链AttributeSet以上操作。Geolib3-MT通过将每个Op的Op arg收集到一个Op中来折叠此Op链。 GroupAttribute叫batch,其子对象包含链中每个操作的操作参数。
- 批量
- Op1
- Op2
- Op3
- Op4
注意: Op args按自上而下的顺序传递到折叠的Op。
自定义用户Ops可以通过调用来参与Op Chain折叠功能setOpsCollapsible()如上所述。在上面的例子中AttributeSet来电setOpsCollapsible(“batch”)然后测试其是否在批处理模式下运行cook()呼叫。
如果参与Op链折叠系统,Op会确保处理折叠的Ops链的结果必须与顺序处理每个链结的结果相同。其含义主要涉及上游位置或属性的查询,在这些位置或属性中,这些位置或属性可能已由已折叠的链生成或修改。举一个具体的例子,考虑一个双操作链,其中第一个操作设置属性hello=world
and the second Op in the chain prints Hi There!
if hello
is equal to world
. If the second Op uses Interface::getAttr() to query the value of hello
in the collapsed chain the result is empty, as hello has been set on the location’s output but did not exist on the input. 为了解决这个问题,可以将操作重构为调用getOutputAttr()代替。
Cache frequently accessed attributes
While access of an FnAttribute的数据便宜,因此保留或发布FnAttribute对象需要修改参考计数。这通常不是问题,但是这可能会为创建许多临时或短暂生命的操作积累FnAttribute对象,尤其是当许多线程一次执行Op时。如果FnAttribute实例经常使用,请考虑是否可以对其进行缓存以避免此开销。
作为一个具体示例,请考虑以下OpScript代码段:
局部函数generateChildren(count) 对于i = 1,算 本地名称= Interface.GetAttr(“ data.name”)。getValue().. tostring(i) Interface.CreateChild(名称) 结束 结束 |
此函数使用从输入属性派生的名称创建计数子级data.name。如果计数很大,则出于两个原因,在循环内部访问此属性会导致不必要的工作,
- Lua必须为以下对象分配和取消分配对象: data.name为每个迭代赋予属性,从而导致垃圾收集器需要进行更多工作。
- 这种分配和破坏会导致属性的原子引用计数增加和减少,从而可能在线程之间引入停顿。
当在多个线程上执行访问同一组属性的OpScript时,第二个瓶颈适用。为了避免如此频繁地访问引用计数,可以如下编写该代码段:
局部函数generateChildren(count) 本地词干= Interface.GetAttr(“ data.name”)。getValue() 对于i = 1,算 本地名称=干.. tostring(i) Interface.CreateChild(名称) 结束 结束 |
现在data.name只能在循环外部访问一次,因此在循环执行时不会触及引用计数。