AngelScript performance

Started by
13 comments, last by WitchLord 19 years, 7 months ago
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 :-)
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 ;)
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
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 versionfloat 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 versionfunction func5()    n = n + zfx.average( n, n )endfunction func4()    n = n + 2 * zfx.average( n+1, n+2 )endfunction func3()    n = 2.1 * nendfunction func2()    n = n / 3.5endfunction 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        endendn = 0i = 0for i = 0, 1000000, 0.25 do    zfx.average( i, i + 1 )     recursion( 5 )    if n > 100 then n = 0 endendzfx.log( string.format( "N = %f", n ) )

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

both Average() and average() are simple functions returning (arg1 + arg2) / 2.
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.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

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 :)
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
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.
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
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 ?

This topic is closed to new replies.

Advertisement