Jump to content
  • Advertisement
Sign in to follow this  
RCL

AngelScript performance

This topic is 5158 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi I've tested AngelScript performance against Lua. My synthetic test consists of pretty long loop (to make differences noticeable) with calls to various functions (both script ones and exported from C++), and some (rather simple) math calculations. This is what we need the in-game script for - mostly for calling exported functions, all the time-consuming tasks are done from within C++ code. And sorry to say, AngelScript is nearly 2.35 times slower than Lua. I really like AngelScript (because of its syntax and comfort at exporting functions - I hate Luabind and ToLua :)), so I decided to investigate the problem. The most time-consuming (according to profiler) function is asCContext::ExecuteNext() (no surprise here). Changing 'bcSize[ BC_* ]' to enumerated constants ('Sizeof_BC_*') when incrementing programCounter, and using temporary variables instead of class members helped me to get rid of some dereferencing and made the function more compact in memory. However, the performance gain was insufficient (AngelScript remained 2.10 times slower than Lua). I would say that BC_CALL opcode is not a 'light-weight' one, making code with a lot of function calls slow. Perhaps some further 'low-level' optimization (like making opcodes to be ints, not bytes, because ints ARE faster) could help (a bit), but I don't think that problem can be solved by this. I believe that the main reason why AngelScript is slower is that Lua bytecode consists of less opcodes, which are of higher level than those of AngelScript (for example, Lua has special opcodes for 'FOR' iterations, OP_FORLOOP/OP_TFORLOOP). It is a hard decision to choose between Lua and AngelScript. Performance IS important for games, so it would be really great if you managed to optimize AngelScript to make it at least on par with Lua. P.S. And yes, it would be also great if you supported [at least single :)] inheritance when registering object types, too :-)

Share this post


Link to post
Share on other sites
Advertisement
Quote:
Original post by RCL
P.S. And yes, it would be also great if you supported [at least single :)] inheritance when registering object types, too :-)
I have a feeling that this is easier said than done ;)

Share this post


Link to post
Share on other sites
Hi RCL

This is very interesting.

What does Your angelscript code look like?
Did you also stresstest object creation and destruction?
Could we download Your test setup somewhere, or could you post here some of Your functions so we have a more accurate picture?

Regards
Tom

Share this post


Link to post
Share on other sites
Hi Tom,

Quote:
Original post by zola
What does Your angelscript code look like?
Did you also stresstest object creation and destruction?
Could we download Your test setup somewhere, or could you post here some of Your functions so we have a more accurate picture?


No, I did not test object creation and destruction.
Here's my code (yes, I know it is meaningless, but the main point here was to test a) performance of function calls b) performance of math)

Angelscript version
===== 8< =====

// Synthetic test, AngelScript version

float N;

void func5()
{
N += Average( N, N );
}

void func4()
{
N += 2 * Average( N + 1, N + 2 );
}

void func3()
{
N *= 2.1 * N;
}

void func2()
{
N /= 3.5;
}

void Recursion( int nRec )
{
if ( nRec >= 1 )
Recursion( nRec - 1 );

if ( nRec == 5 )
func5();
else if ( nRec == 4 )
func4();
else if ( nRec == 3 )
func3();
else if ( nRec == 2 )
func2();
else
N *= 1.5;
}

int main()
{
N = 0;
float i = 0;

for ( i = 0; i < 1000000; i += 0.25 )
{
Average( i, i );
Recursion( 5 );

if ( N > 100 ) N = 0;
}

Log( "N = "+bstrFormat( N ) );
return 0;
}


===== >8 =====

Lua version
===== 8< =====

-- synthetic test, LUA version

function func5()
n = n + zfx.average( n, n )
end

function func4()
n = n + 2 * zfx.average( n+1, n+2 )
end

function func3()
n = 2.1 * n
end

function func2()
n = n / 3.5
end

function recursion( rec )
if rec >= 1 then
recursion( rec - 1 )
end

if rec == 5 then func5()
else if rec==4 then func4()
else if rec==3 then func3()
else if rec==2 then func2()
else n = n * 1.5
end
end
end
end
end

n = 0
i = 0

for i = 0, 1000000, 0.25 do
zfx.average( i, i + 1 )
recursion( 5 )
if n > 100 then n = 0 end
end

zfx.log( string.format( "N = %f", n ) )

===== >8 =====

both Average() and average() are simple functions returning (arg1 + arg2) / 2.

Share this post


Link to post
Share on other sites
Very interesting indeed. I didn't make AngelScript to compete in performance with Lua, or any other scripting language. But I am surprised to see that AS is over 2 times slower than Lua. I would have thought the typeless variables of Lua would give it a rather large overhead.

It's also interesting to see that changing bcSize[] to enumerated constants improved the performance so much. What variables are you using as local variables instead of member variables?

Moving the loop into ExecuteNext() ought to raise the performance a little as well, also removing the test (status == tsActive) from every loop iteration could increase the performance sligthly.

Having a specific bytecode for loop iterations might be possible, I'll investigate this. That would save a few bytecodes for each loop.

I think I'll write another test framework for testing the performance of AngelScript so that I can measure the difference with each release.

Inheritance for object types will probably not happen, because I can't guarantee that the method pointers are the same for the base class and the derived one. But I'll investigate this further. Maybe I'll find a solution for a future version.

Share this post


Link to post
Share on other sites
Hi doho,

Quote:
Original post by doho
Quote:
Original post by RCL
P.S. And yes, it would be also great if you supported [at least single :)] inheritance when registering object types, too :-)
I have a feeling that this is easier said than done ;)


Everything is :)

Share this post


Link to post
Share on other sites
Hi WitchLord,

Quote:
Original post by WitchLord
It's also interesting to see that changing bcSize[] to enumerated constants improved the performance so much.


Well,
1) we got rid from reading from the memory
2) code got tighter
( move reg1, [mem] \ add reg2, reg1 replaced by just add reg2, const )

Could be that my system is bound by memory speed =)

Quote:
Original post by WitchLord
What variables are you using as local variables instead of member variables?


I reorganized your switch...case in ExecuteNext(), replacing programCounter with local variable. It did improve the performance (a bit :)), however, when I tried to do the same trick with stackPointer, I got performance loss.

Quote:
Original post by WitchLord
Having a specific bytecode for loop iterations might be possible, I'll investigate this. That would save a few bytecodes for each loop.


Maybe you could also store more pre-computed data for each BC_CALL, or make several versions of BC_CALL opcode to avoid redundant 'if's and function calls every time.

Quote:
Original post by WitchLord
Inheritance for object types will probably not happen, because I can't guarantee that the method pointers are the same for the base class and the derived one. But I'll investigate this further. Maybe I'll find a solution for a future version.


It's a pity. Having entity hierarchy represented in the script by not related to each other types sux (problems with type casting etc etc).

Cheers,
RCL

Share this post


Link to post
Share on other sites
Really, I beleive that object inheritance could be made outside the code of AngelScript.

First you have to create an asIScriptEngine descendant.

Next, each time you register an object class, you can (i'have already made it for other purpose, see my post about AngelScript Editors) register for your convenience (inheritance for you !).

At the end of the process of registering the whole objects, you then have a tree with all objects and their members, and methods (yes, the same that angelscript already has).

Then the beauty comes ... you just have to develop a function to build the object hierarchy :
bool RegisterObjectHierarchy(char *TypeParent, char *TypeChild)
{
for each member of TypeParent
if TypeChild hasn't got the same member
RegisterObjectProperty(TypeChild, definition of TypeParent member, adresse of TypeParent member)

for each method of TypeParent
if TypeChild hasn't got the same member
RegisterObjectMethod(TypeChild, definition of TypeParent method, adress of TypeParent method, call convention of TypeParent method)

return evrything went ok.
}

Once the inheritance is made, free all of your datas tree !

AbrKen.

Share this post


Link to post
Share on other sites
Quote:
Original post by abrken
Really, I beleive that object inheritance could be made outside the code of AngelScript.


How do I *safely* cast my, say, CActor reference to CEntity (CActor's superclass) reference this way?

Cheers,
RCL

Share this post


Link to post
Share on other sites
I don't know what you're meaning when your saying *safely*

This work for me :

in_pAsEngine->RegisterObjectType("CXmlValue", sizeof(CXmlValue), asOBJ_CLASS);
in_pAsEngine->RegisterObjectBehaviour("CXmlValue", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(CXmlValue_constructor), asCALL_CDECL_OBJLAST);
in_pAsEngine->RegisterObjectBehaviour("CXmlValue", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(CXmlValue_destructor), asCALL_CDECL_OBJLAST);
in_pAsEngine->RegisterGlobalFunction("CXmlValue *CXmlValue_new()", asFUNCTION(CXmlValue_new), asCALL_CDECL);
in_pAsEngine->RegisterGlobalFunction("void CXmlValue_delete(CXmlValue *)", asFUNCTION(CXmlValue_delete), asCALL_CDECL);
in_pAsEngine->RegisterObjectProperty("CXmlValue", "string m_strName", offsetof(CXmlValue, m_strName));
in_pAsEngine->RegisterObjectProperty("CXmlValue", "string m_strValue", offsetof(CXmlValue, m_strValue));
in_pAsEngine->RegisterObjectMethod("CXmlValue", "const string &getName()",asMETHOD(CXmlValue,getName), asCALL_THISCALL);
in_pAsEngine->RegisterObjectMethod("CXmlValue", "const bool getValueAsBool()",asMETHOD(CXmlValue,getValueAsBool), asCALL_THISCALL);
in_pAsEngine->RegisterObjectMethod("CXmlValue", "const bits32 getValueAsDWORD()",asMETHOD(CXmlValue,getValueAsDWORD), asCALL_THISCALL);
in_pAsEngine->RegisterObjectMethod("CXmlValue", "const string &getValueAsString()",asMETHOD(CXmlValue,getValueAsString), asCALL_THISCALL);
in_pAsEngine->RegisterObjectMethod("CXmlValue", "const CString getValueAsCString()",asMETHOD(CXmlValue,getValueAsCString), asCALL_THISCALL);

in_pAsEngine->RegisterObjectType("CXmlParameter", sizeof(CXmlParameter), asOBJ_CLASS);
in_pAsEngine->RegisterObjectBehaviour("CXmlParameter", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(CXmlParameter_constructor), asCALL_CDECL_OBJLAST);
in_pAsEngine->RegisterObjectBehaviour("CXmlParameter", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(CXmlParameter_destructor), asCALL_CDECL_OBJLAST);
in_pAsEngine->RegisterGlobalFunction("CXmlParameter *CXmlParameter_new()", asFUNCTION(CXmlParameter_new), asCALL_CDECL);
in_pAsEngine->RegisterGlobalFunction("void CXmlParameter_delete(CXmlParameter *)", asFUNCTION(CXmlParameter_delete), asCALL_CDECL);
in_pAsEngine->RegisterObjectProperty("CXmlParameter", "string m_strName", offsetof(CXmlValue, m_strName));
in_pAsEngine->RegisterObjectProperty("CXmlParameter", "string m_strValue", offsetof(CXmlValue, m_strValue));
in_pAsEngine->RegisterObjectMethod("CXmlParameter", "const string &getName()",asMETHOD(CXmlValue,getName), asCALL_THISCALL);
in_pAsEngine->RegisterObjectMethod("CXmlParameter", "const bool getValueAsBool()",asMETHOD(CXmlValue,getValueAsBool), asCALL_THISCALL);
in_pAsEngine->RegisterObjectMethod("CXmlParameter", "const bits32 getValueAsDWORD()",asMETHOD(CXmlValue,getValueAsDWORD), asCALL_THISCALL);
in_pAsEngine->RegisterObjectMethod("CXmlParameter", "const string &getValueAsString()",asMETHOD(CXmlValue,getValueAsString), asCALL_THISCALL);
in_pAsEngine->RegisterObjectMethod("CXmlParameter", "const CString getValueAsCString()",asMETHOD(CXmlValue,getValueAsCString), asCALL_THISCALL);


and this is the class definition :


/*!
*****************************************************************************************
* \class CXmlValue : public CObject XmlMemoryTree.h
* \brief Description
*
* detaille
*****************************************************************************************
* \exception Aucune
*
*****************************************************************************************
* \bug Aucun de connu au 01/10/01
*****************************************************************************************/
class CXmlValue : public CObject {
public:
//! Nom de la Valeur
string m_strName,
//! Contenu de la valeur
m_strValue;

CXmlValue();
CXmlValue(const CXmlValue &);
virtual ~CXmlValue();

const string &getName()const{ return m_strName ; };

const bool getValueAsBool()const;
const DWORD getValueAsDWORD()const;
const string &getValueAsString()const;
const CString getValueAsCString()const;
const CTime getValueAsDate(LPCTSTR in_szDateFormat, bool &out_bGoodFmt) const;
const CTime getValueAsTime(LPCTSTR in_szTimeFormat, bool &out_bGoodFmt) const;

void Serialize(CArchive &ar);

CXmlValue &operator =(const CXmlValue&);
bool operator ==(const CXmlValue&in_xmlValue) const {return (m_strName == in_xmlValue.m_strName && m_strValue == in_xmlValue.m_strValue); };
bool operator !=(const CXmlValue&in_xmlValue) const {return (m_strName != in_xmlValue.m_strName || m_strValue != in_xmlValue.m_strValue); };

DECLARE_SERIAL(CXmlValue);
private:
string Uppercase(const string & in_str)const;
};



/*!
*****************************************************************************************
* \class CXmlParameter : public CXmlValue XmlMemoryTree.h
* \brief Description
*
* detaille
*****************************************************************************************
* \exception Aucune
*
*****************************************************************************************
* \bug Aucun de connu au 01/10/01
*****************************************************************************************/
class CXmlParameter : public CXmlValue
{
public:

CXmlParameter();
//CXmlParameter(const CXmlValue &);
//CXmlParameter(const CXmlParameter &);
virtual ~CXmlParameter();

void Serialize(CArchive &ar);

//CXmlParameter &operator =(const CXmlValue&);
//CXmlParameter &operator =(const CXmlParameter&);

DECLARE_SERIAL(CXmlParameter);
};



CXmlParameter is a CXmlValue descandant and it works fine.
I'm working with MSVC6 so peharps it's because of that ?

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!