OpScriptパフォーマンスの改善

Lua Garbage Collectorを理解する

Luaでは、メモリはスタックまたはヒープに割り当てることができます。ヒープに割り当てられたオブジェクトまたはメモリは、Luaのガベージコレクターによって回収されます。したがって、ガベージコレクターが実行されている場合、Luaコードは前方に進むことができないため、ガベージコレクターを実行する頻度を減らすか、ガベージコレクターが収集するガベージの量を減らすと、OpScriptsのパフォーマンスが向上します。

次のLuaコンストラクトは、悪くはありませんが、ガベージコレクション中に後でクリーンアップする必要がある新しいオブジェクトを作成します。これらのコンストラクトがループ内で使用される場合、特に注意する必要があります。

  • String_one..string_two -文字列の連結または任意の文字列作成関数により、新しいオブジェクトが作成される可能性があります。Luaの文字列は一意であるため、最初に文字列が他の場所で作成されているかどうかを確認します。
  • { … } -テーブルコンストラクターが実行されるたびに、新しいテーブルが作成されます。
  • function() ... end -関数ステートメントを実行すると、クロージャーが作成されます。これをn回の繰り返しのループ内で実行すると、n個のクロージャーが作成されます。

注意:  詳細についてはlua-users.org

ループ内の「..」を避ける

Luaガベージコレクターの理解の具体例として、この推奨事項では、便利な略記法によりOpScriptsで一般的に使用される文字列連結演算子「..」の使用を検討します。

例として、L-System記述を作成するために使用される次のコードを考えてください。

local結果= ""

for i = 1、#inputStr do

local char = inputStr:sub(i、i)

local ruleStr = rulesTable [char]

if ruleStr then

結果=結果..ruleStr

else

結果= result..char

end

end

return結果

resultタイトループで追加されます。便利ではありますが、これにより、文字列が大量に割り当てられ、パフォーマンスが低下します。上記のコードは、以下に示すようにループが完了した後にのみ、すべての連結を処理するように書き直すことができます。

ローカルbuf = {}

for i = 1、#inputStr do

local char = inputStr:sub(i、i)

ローカルruleStr = rulesTable [char]

if ruleStr then

buf [#buf + 1] = ruleStr

else

buf [#buf + 1] = char

end

end

return table.concat(buf)

大規模の一部として上記の例を実行するKatanaシーンファイルは、次の表に示すように、この関数のシーン処理時間を約2.5倍短縮しました。

文字列メソッド

時間

「..」演算子

7.011秒

Table.concat()

2.681s

高価なOpScriptを置き換えるカスタムC ++ Opの作成を検討してください

LuaとOpScript APIは非常に便利で多用途ですが、シーンのトラバース時間の大部分を占めるOpScriptsは、カスタムC ++ Opsとして適しています。

具体的な例として、次のOpScriptを考えてみましょう。このOpScriptは、ターゲットボックスの位置、向き、およびスケールを一様なボックス内でランダムに配置します。

ローカルハッシュ= ExpressionMath.stablehash(Interface.GetOutputName()))

math.randomseed(hash)

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 = dim * math.random()+ sy

local tz = 2 * dim *(math.random()-0.5)

local ax = math.random()

local ay = math.random()

local az = math.random()

local axis = Imath.V3d(ax、ay、az):normalize()

local角度= 360 * math.random()

local translate = Imath.V3d(tx、ty、tz)

local rotate = Imath.V4d(angle、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()

{

returndouble)rand()/( double )RAND_MAX;

}

struct DistributeGeometryOp: public Foundry :: Katana :: GeolibOp

{

static void setup(Foundry :: Katana :: GeolibSetupInterface&interface)

{

interface.setThreading(Foundry :: Katana :: GeolibSetupInterface :: ThreadModeConcurrent);

}

static void cook(Foundry :: Katana :: GeolibCookInterface&interface)

{

srand(FnGeolibServices :: FnExpressionMath :: stablehash(interface.getOutputName()));

double dim = 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 ax = getRandom();

double ay = getRandom();

double az = getRandom();

double axisLen = sqrt(ax * ax + ay * ay + az * az);

ax / = axisLen;

ay / = axisLen;

az / = axisLen;

double角度= 360.0 * getRandom();

double translate [] = {tx、ty、tz};

double rotate [] = {angle、ax、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);

}

異なるランダム分布まで、これらのOpsは同じシーンを生成します。ただし、C ++バージョンは約3.2倍の速度で実行されます。10,000個のジオメトリックプリミティブの配置に使用される場合、Opsは次のリソースを使用します。

Op

CPU時間

使用メモリ

OpScript

0.989秒

54.24 MiB

C ++ Op

0.310秒

53.67 MiB

この場合、メモリ使用量は似ています。ただし、OpScriptが多数の小さなオブジェクトを割り当てて解放している場合、C ++に変換すると、Luaガベージコレクションに費やす時間も節約されます。