For direct access use https://forums.oldunreal.com
It's been quite a while since oldunreal had an overhaul, but we are moving to another server which require some updates and changes. The biggest change is the migration of our old reliable YaBB forum to phpBB. This system expects you to login with your username and old password known from YaBB.
If you experience any problems there is also the usual "password forgotten" function. Don't forget to clear your browser cache!
If you have any further concerns feel free to contact me: Smirftsch@oldunreal.com

Hanhikers guide to UnrealScript optimization

The section related to UnrealScript and modding. This board is for coders to discuss and exchange experiences or ask questions.
Post Reply
User avatar
han
Global Moderator
Posts: 686
Joined: Wed Dec 10, 2014 12:38 am

Hanhikers guide to UnrealScript optimization

Post by han »

For quite a long time I'm quite disappointed about how slow the 'game' part in Deus Ex runs and today I started to look into more details. Working on my decompiler was certainly great for getting into the UnrealScript execution details. My idea is to focus on smaller changes which won't sacrificy neither code maintainability nor flexibility and can be adopted as a general pattern for writing UnrealScript code.

Currently I can't back my findings with benchmarks and they are mostly based on common sense (in other words: you shouldn't read that kind of guide). As some general words: don't waste your time optimizing code which rarely executes and focus on higher level code optimizations rather than small scale changes and benchmark!


Don't use unnecessary casts/class checks (I).
DO:

Code: Select all

function Stuff( Actor Other )
{
  var Inventory OtherInventory;

  OtherInventory = Inventory(Other);
  if ( bool(OtherInventory) )
  {
     // [...]
  }
}
DON'T:

Code: Select all

function Stuff( Actor Other )
{
  var Inventory OtherInventory;

  if ( Other.IsA('Inventory') )
  {
     OtherInventory = Inventory(Other);

     // [...]
  }
}
IsA() and dynamic casting both perform a class check, so one of these checks can be removed.


Don't use unnecessary casts/class checks (II).
DO:

Code: Select all

function Stuff( Actor Other )
{
  if ( Other.bIsPawn )
  {
     // [...]
  }
}
DON'T:

Code: Select all

function Stuff( Actor Other )
{
  if ( Other.IsA('Pawn') )
  {
     // [...]
  }
}
Some classes already have some fast to check bool set whether they are of a specific class. This pattern should even be used if you still would need to cast to that class, but most of the time your code will be called with objects of another class, and thus saving the class check most of time.

You can also start to add more of these sort of variables to your classes, to use it in more places. If you need to access variables of that particular class, you could also consider storing them in the baseclass. This will also make it easier to write 3rd party code which is not of that particular class. In case of function calls, you could also add some stub function in the baseclass which gets overriden, though this would introduce another function resolve, so this is more useful to keep code flexible by not relying on a specific class.


Make functions final which are certainly never going to be overriden.
DO:

Code: Select all

final function SetBorderStyle( EDrawStyle NewBorderStyle )
{
  BorderStyle = NewBorderStyle;
}
DON'T:

Code: Select all

function SetBorderStyle( EDrawStyle NewBorderStyle )
{
  BorderStyle = NewBorderStyle;
}
In the first case a reference to the Function is stored in callee code, while in the second place only the name of function is stored in callee code, which needs to be resolved during execution.


Don't use functions which just call Super.
DO: DON'T:

Code: Select all

simulated event RenderOverlays( Canvas Canvas )
{
  Super.RenderOverlays( Canvas );
}
Though obvious, I put it here as I noticed this more than a couple of times in IONs code.


Finish your math.
DO:

Code: Select all

function float CalculateSomething( float f )
{
  // 1.00392156863 = 256.0/255.0.
  return f*1.00392156863;
  //
  // 0x0000: EX_Return
  // 0x0001:   EX_Native (iNative=171,Function=Core.Object.Multiply_FloatFloat,OperatorType=1)
  // 0x0002:     EX_LocalVariable (Property=ScriptRaysTestPackage.ScriptRaysTestFunctions.CalculateSomething.F)
  // 0x0007:     EX_FloatConst (FloatConst=1.003922)
  //
}
DON'T:

Code: Select all

function float CalculateSomething( float f )
{
  return f*256.0/255.0;
  //
  // 0x0000: EX_Return
  // 0x0001:   EX_Native (iNative=172,Function=Core.Object.Divide_FloatFloat,OperatorType=1)
  // 0x0002:     EX_Native (iNative=171,Function=Core.Object.Multiply_FloatFloat,OperatorType=1)
  // 0x0003:       EX_LocalVariable (Property=ScriptRaysTestPackage.ScriptRaysTestFunctions.CalculateSomething2.F)
  // 0x0008:       EX_FloatConst (FloatConst=256.000000)
  // 0x000E:     EX_FloatConst (FloatConst=255.000000)
  //
}
The UnrealScript compiler won't do the math for you like other compilers would do, so the math operations which always yield the same result will still be done at runtime.


Use cast to bool instead of checking againts 0/0.0/""/None/vect(0,0,0)/rot(0,0,0).
DO:

Code: Select all

function UseBoolCast2( int i, float f, string Str, Object Obj )
{
  if ( bool(i) );
  if ( bool(f) );
  //if ( bool(Str) ); // See dots post below.
  if ( bool(Obj) ); 
  //
  // 0x0000: EX_JumpIfNot (JumpOffset=0x0009)
  // 0x0003:   EX_IntToBool
  // 0x0004:     EX_LocalVariable (Property=ScriptRaysTestPackage.ScriptRaysTestFunctions.UseBoolCast2.i)
  // 0x0009: EX_JumpIfNot (JumpOffset=0x0012)
  // 0x000C:   EX_FloatToBool
  // 0x000D:     EX_LocalVariable (Property=ScriptRaysTestPackage.ScriptRaysTestFunctions.UseBoolCast2.F)
  // 0x0012: EX_JumpIfNot (JumpOffset=0x001B)
  // 0x0015:   EX_StringToBool
  // 0x0016:     EX_LocalVariable (Property=ScriptRaysTestPackage.ScriptRaysTestFunctions.UseBoolCast2.Str)
  // 0x001B: EX_JumpIfNot (JumpOffset=0x0024)
  // 0x001E:   EX_ObjectToBool
  // 0x001F:     EX_LocalVariable (Property=ScriptRaysTestPackage.ScriptRaysTestFunctions.UseBoolCast2.Obj)
  //
}
DON'T:

Code: Select all

function UseBoolCast( int i, float f, string Str, Object Obj )
{
  if ( i!=0 );
  if ( f!=0.0 );
  //if ( Str!="" ); // See dots post below.
  if ( Obj!=None );
  //
  // 0x0000: EX_JumpIfNot (JumpOffset=0x000B)
  // 0x0003:   EX_Native (iNative=155,Function=Core.Object.NotEqual_IntInt,OperatorType=1)
  // 0x0004:     EX_LocalVariable (Property=ScriptRaysTestPackage.ScriptRaysTestFunctions.UseBoolCast.i)
  // 0x0009:     EX_IntZero
  // 0x000B: EX_JumpIfNot (JumpOffset=0x001A)
  // 0x000E:   EX_Native (iNative=181,Function=Core.Object.NotEqual_FloatFloat,OperatorType=1)
  // 0x000F:     EX_LocalVariable (Property=ScriptRaysTestPackage.ScriptRaysTestFunctions.UseBoolCast.F)
  // 0x0014:     EX_FloatConst (FloatConst=0.000000)
  // 0x001A: EX_JumpIfNot (JumpOffset=0x0026)
  // 0x001D:   EX_Native (iNative=123,Function=Core.Object.NotEqual_StrStr,OperatorType=1)
  // 0x001E:     EX_LocalVariable (Property=ScriptRaysTestPackage.ScriptRaysTestFunctions.UseBoolCast.Str)
  // 0x0023:     EX_StringConst (StringConst="")
  // 0x0026: EX_JumpIfNot (JumpOffset=0x0031)
  // 0x0029:   EX_Native (iNative=119,Function=Core.Object.NotEqual_ObjectObject,OperatorType=1)
  // 0x002A:     EX_LocalVariable (Property=ScriptRaysTestPackage.ScriptRaysTestFunctions.UseBoolCast.Obj)
  // 0x002F:     EX_NoObject
  //
}
You can check whether a variable is 'zero' by casting it to a bool for the basic types and objects. This way you skip execution and fetching of the constants.


Use correct constant types.
DO:

Code: Select all

function float UseCorrectConstantTypes2( float f )
{
  return f*2.0;
  //
  // 0x0000: EX_Return
  // 0x0001:   EX_Native (iNative=171,Function=Core.Object.Multiply_FloatFloat,OperatorType=1)
  // 0x0002:     EX_LocalVariable (Property=ScriptRaysTestPackage.ScriptRaysTestFunctions.UseCorrectConstantTypes2.F)
  // 0x0007:     EX_FloatConst (FloatConst=2.000000)
  //
}
DON'T:

Code: Select all

function float UseCorrectConstantTypes( float f )
{
  return f*2;
  //
  // 0x0000: EX_Return
  // 0x0001:   EX_Native (iNative=171,Function=Core.Object.Multiply_FloatFloat,OperatorType=1)
  // 0x0002:     EX_LocalVariable (Property=ScriptRaysTestPackage.ScriptRaysTestFunctions.UseCorrectConstantTypes.F)
  // 0x0007:     EX_IntToFloat
  // 0x0008:       EX_IntConstByte (IntConst=2)
  //
}
One example of this is using an integer constant which gets casted to float variable at runtime instead of having it already stored as a floating point variable which gets directly used. (Note: For "2*f" the 2 ends up as a floating point constant too in this particular case).


Don't use 'conveniance' functions to wrap your Natives..
DO:

Code: Select all

native final function SetViewportLocation( Vector NewLocation, optional bool bEnable );
native final function SetViewportLocationXYZ( float X, float Y, float Z, optional bool bEnable );
DON'T:

Code: Select all

native final function SetViewportLocation( Vector NewLocation, optional bool bEnable );

final function SetViewportLocationXYZ( float X, float Y, float Z, bool bEnable )
{
  local Vector Temp;
  Temp.X = X;
  Temp.Y = Y;
  Temp.Z = Z;
  SetViewportLocation( Temp, bEnable );
}
Nuff said.


Don't define empty functions (if they just return 0).
DO:

Code: Select all

event bool ComputerInputFinished( String inputKey, String inputValue );
DON'T:

Code: Select all

event bool ComputerInputFinished( String inputKey, String inputValue )
{
      return False;
}
The first one will not set FUNC_Defined and initially I assumed that a bit of code execution would be skipped, but this doesn't seem to be the case. However in case you use a return value which coincidence with the zero allocation (false, None, 0, 0.0, first enum entry, vect(0,0,0), rot(0,0,0), etc.) this will produce only the default empty ) bytecode and save the dublicated zeroing of the return value data. In case there is no return value this yields the exact same bytecode and execution.

In any case this is certainly a neglectable optimization and I only put it here because it looks cleaner to me and it could be used with a future (small) optimizations of bytecode execution especially in respect to RPCs. (I briefly talked with Smirftsch about it).


tl;dr
Don't use unnecessary cast/class checks and function resolves to get the biggest savings.


To be continued...
Last edited by han on Mon Oct 16, 2017 5:54 am, edited 1 time in total.
HX on Mod DB. Revision on Steam. Löffels on Patreon.
User avatar
Bleeder91[NL]
OldUnreal Member
Posts: 1062
Joined: Sun Oct 04, 2009 7:22 pm

Re: Hanhikers guide to UnrealScript optimization

Post by Bleeder91[NL] »

I'll keep this in my bookmarks so I can look over my code from time to time. Thanks!

Side note: I think you reversed the Do's and Don'ts on Use cast to bool instead of checking againts 0/0.0/""/None/vect(0,0,0)/rot(0,0,0).
Image
User avatar
han
Global Moderator
Posts: 686
Joined: Wed Dec 10, 2014 12:38 am

Re: Hanhikers guide to UnrealScript optimization

Post by han »

Side note: I think you reversed the Do's and Don'ts on Use cast to bool instead of checking againts 0/0.0/""/None/vect(0,0,0)/rot(0,0,0).
I did. And for constant types too.
Last edited by han on Sun Jul 16, 2017 7:40 pm, edited 1 time in total.
HX on Mod DB. Revision on Steam. Löffels on Patreon.
User avatar
.:..:
OldUnreal Member
Posts: 1634
Joined: Tue Aug 16, 2005 4:35 am

Re: Hanhikers guide to UnrealScript optimization

Post by .:..: »

Hmm in addition, bool(str) is not same as str!="", because bool("0") or bool("false") will also result in false.

So for a foolproof method you could use length, bool(Len(str)).
Last edited by .:..: on Sun Jul 16, 2017 9:31 pm, edited 1 time in total.
1823223D2A33224B0 wrote:...and now im stuck trying to fix everything you broke for the next 227 release xD :P
(ಠ_ಠ)
User avatar
han
Global Moderator
Posts: 686
Joined: Wed Dec 10, 2014 12:38 am

Re: Hanhikers guide to UnrealScript optimization

Post by han »

Indeed, StringToBool really handles "True"/"False" the localized versions of these, and integer values. Missed that.

bool(Len(str)) shouldn't be an advantage anymore over using Str!="".
Last edited by han on Mon Jul 17, 2017 1:46 pm, edited 1 time in total.
HX on Mod DB. Revision on Steam. Löffels on Patreon.
User avatar
Bleeder91[NL]
OldUnreal Member
Posts: 1062
Joined: Sun Oct 04, 2009 7:22 pm

Re: Hanhikers guide to UnrealScript optimization

Post by Bleeder91[NL] »

Might just be me, but all my calls of bool(str) result in False no matter what the content is?
Image
User avatar
han
Global Moderator
Posts: 686
Joined: Wed Dec 10, 2014 12:38 am

Re: Hanhikers guide to UnrealScript optimization

Post by han »

Check the two post above yours.

Actually put the reference to those posts on objects not strings in the first post. Corrected that now.
HX on Mod DB. Revision on Steam. Löffels on Patreon.
User avatar
han
Global Moderator
Posts: 686
Joined: Wed Dec 10, 2014 12:38 am

Re: Hanhikers guide to UnrealScript optimization

Post by han »

Group the replication statements for your variables
DO:

Code: Select all

replication
{
  reliable if ( Role==ROLE_Authority )
    Candy, Bar;
}
DON'T:

Code: Select all

replication
{
  reliable if ( Role==ROLE_Authority )
    Candy;

  reliable if ( Role==ROLE_Authority )
    Bar;
}
Each (un)reliable entry creates it's own replication condition index. Each replication index is at most evaluated once and cached. So by using a single replication statement, you avoid needless double execution of the replication condition.
Last edited by han on Thu May 30, 2019 12:38 am, edited 1 time in total.
HX on Mod DB. Revision on Steam. Löffels on Patreon.
User avatar
Masterkent
OldUnreal Member
Posts: 1469
Joined: Fri Apr 05, 2013 12:41 pm

Re: Hanhikers guide to UnrealScript optimization

Post by Masterkent »

Each (un)reliable entry creates it's own replication condition index. Each replication index is at most evaluated once and cached. So by using a single replication statement, you avoid needless double execution of the replication condition.
Two questions:

1) How did you figure that out and for what versions of the UScript compiler?

According to my tests, 227j merges lexically identical conditions, so the two above variants should be handled in the same way (a single check will be used in both cases).

For the following test code

Code: Select all

var string RV1, RV2, RV3, RV4, RV5, RV6, RV7, RV8, RV9, RV10;

replication
{
      reliable if (Role == ROLE_Authority && LogRepSubexpression(true))
            RV1;
      reliable if (Role == ROLE_Authority && LogRepSubexpression(false))
            RV2;
      reliable if (Role == ROLE_Authority && LogRepSubexpression(false))
            RV3;
      reliable if ((Role == ROLE_Authority) && (LogRepSubexpression(false)))
            RV4;
      reliable if (Role == ROLE_Authority && LogRepSubexpression(bool(0)))
            RV5,
            RV6,
            RV7,
            RV8,
            RV9,
            RV10;
}

event BeginPlay()
{
      RV1 = "1";
      RV2 = "2";
      RV3 = "3";
      RV4 = "4";
      RV5 = "5";
      RV6 = "6";
      RV7 = "7";
      RV8 = "8";
      RV9 = "9";
      RV10 = "10";
}

function bool LogRepSubexpression(bool b)
{
      Log("*** Replication Subexpression ***" @ b);
      return true;
}
I get only 3 log entries:

from LogRepSubexpression(true),
from LogRepSubexpression(false),
and from LogRepSubexpression(bool(0)).

Replacing bool(0) with false results in creating only two log entries (for calls with true and false).

Obviously, the compiler is not smart enough to be able to determine that bool(0) and false are equivalent conditions, but when the same sequence of tokens is used in two or more full condition expressions, it groups the corresponding variables without problems. Besides, redundant parentheses don't seem to affect determining the equivalency between expressions.

2) If actors of the given class cannot be owned by a player, would

Code: Select all

reliable if (true)
be functionally equivalent to

Code: Select all

reliable if (Role == ROLE_Authority)
?

Anyway, I doubt that such optimizations can give any noticeable benefits, since replication statements aren't evaluated very often.
Last edited by Masterkent on Fri May 31, 2019 6:28 am, edited 1 time in total.
Post Reply

Return to “UScript Board”