logo
Main

Forums

Downloads

Unreal-Netiquette

Donate for Oldunreal:
Donate

borderline

Links to our wiki:
Wiki

Walkthrough

Links

Tutorials

Unreal Reference

Usermaps

borderline

Contact us:
Submit News
Page Index Toggle Pages: 1 Send TopicPrint
Normal Topic Hanhikers guide to UnrealScript optimization (Read 1754 times)
han
Global Moderator
Unreal Rendering Guru
Developer Team
*****
Offline


Oldunreal member

Posts: 564
Location: Germany
Joined: Dec 10th, 2014
Gender: Male
Hanhikers guide to UnrealScript optimization
Jul 2nd, 2017 at 3:08pm
Print Post  
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:
Code
Select All
 


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 <EX_Return><EX_Nothing>) 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 Edit: Oct 16th, 2017 at 5:54am by han »  

HX on Mod DB. Revision on Steam.
Back to top
 
IP Logged
 
Bleeder91[NL]
Betatester
Offline


Personal Text:

Posts: 994
Location: Location, Location, Location.
Joined: Oct 4th, 2009
Gender: Male
Re: Hanhikers guide to UnrealScript optimization
Reply #1 - Jul 16th, 2017 at 7:39am
Print Post  
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).
  
Back to top
 
IP Logged
 
han
Global Moderator
Unreal Rendering Guru
Developer Team
*****
Offline


Oldunreal member

Posts: 564
Location: Germany
Joined: Dec 10th, 2014
Gender: Male
Re: Hanhikers guide to UnrealScript optimization
Reply #2 - Jul 16th, 2017 at 7:34pm
Print Post  
Bleeder91[NL] wrote on Jul 16th, 2017 at 7:39am:
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.
  

HX on Mod DB. Revision on Steam.
Back to top
 
IP Logged
 
.:..:
Oldunreal MasterPoster
Developer Team
*
Offline



Posts: 1460
Location: Finland
Joined: Aug 16th, 2005
Gender: Male
Re: Hanhikers guide to UnrealScript optimization
Reply #3 - Jul 16th, 2017 at 9:29pm
Print Post  
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)).
  

Shivaxi wrote on Jul 25th, 2013 at 12:50pm:
...and now im stuck trying to fix everything you broke for the next 227 release xD Tongue

(ಠ_ಠ)
Back to top
IP Logged
 
han
Global Moderator
Unreal Rendering Guru
Developer Team
*****
Offline


Oldunreal member

Posts: 564
Location: Germany
Joined: Dec 10th, 2014
Gender: Male
Re: Hanhikers guide to UnrealScript optimization
Reply #4 - Jul 17th, 2017 at 1:41pm
Print Post  
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!="".
  

HX on Mod DB. Revision on Steam.
Back to top
 
IP Logged
 
Bleeder91[NL]
Betatester
Offline


Personal Text:

Posts: 994
Location: Location, Location, Location.
Joined: Oct 4th, 2009
Gender: Male
Re: Hanhikers guide to UnrealScript optimization
Reply #5 - Oct 15th, 2017 at 11:13am
Print Post  
Might just be me, but all my calls of bool(str) result in False no matter what the content is?
  
Back to top
 
IP Logged
 
han
Global Moderator
Unreal Rendering Guru
Developer Team
*****
Offline


Oldunreal member

Posts: 564
Location: Germany
Joined: Dec 10th, 2014
Gender: Male
Re: Hanhikers guide to UnrealScript optimization
Reply #6 - Oct 16th, 2017 at 5:55am
Print Post  
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.
Back to top
 
IP Logged
 
han
Global Moderator
Unreal Rendering Guru
Developer Team
*****
Offline


Oldunreal member

Posts: 564
Location: Germany
Joined: Dec 10th, 2014
Gender: Male
Re: Hanhikers guide to UnrealScript optimization
Reply #7 - May 30th, 2019 at 12:37am
Print Post  
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.
  

HX on Mod DB. Revision on Steam.
Back to top
 
IP Logged
 
Masterkent
Developer Team
Offline



Posts: 1283
Location: Russia
Joined: Apr 5th, 2013
Gender: Male
Re: Hanhikers guide to UnrealScript optimization
Reply #8 - May 30th, 2019 at 1:16pm
Print Post  
han wrote on May 30th, 2019 at 12:37am:
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 Edit: May 31st, 2019 at 6:28am by Masterkent »  
Back to top
 
IP Logged
 
Page Index Toggle Pages: 1
Send TopicPrint
Bookmarks: del.icio.us Digg Facebook Google Google+ Linked in reddit StumbleUpon Twitter Yahoo