Jump to content

question about code optimization


Recommended Posts

I just ran some new tests and overturned my previous results. This time, I used a quest script instead of an object script attached to an NPC, to eliminate AI, drawing and physics from my tests. Additionally, I used the power of OBSE's User Functions to squeeze 380000 function calls per frame into a script. I also ran my tests in the Oak and Crosier's cellar with all wine bottles removed (physics interactions of these bottles were affecting my framerate and skewing my results). You can download my test plugin from here: https://github.com/XJDHDR/xjdhdr-random-code/blob/master/Game_related_files/My%20Modifications/The%20Elder%20Scrolls%204%3B%20Oblivion/aaTest%20plugins/Test%20Return%20performance.esp

 

In my new tests, I have the following quest script:

Begin GameMode
	Set fQuestDelayTime to 0.0001

	Call XjTestReturnPerformanceFunctionScript
	< Repeat another 99 times >
End

The function script contains the following:

Begin Function { }
	GetFPS
	TestRef.GetDistance PlayerRef
	< repeat above two another 3800 times >
End

Test 1: Scripts were written as above: FPS was 28

 

Test 2: Same as 1 except I added "Return" to the function script above the main code, bypassing the whole thing: FPS was 350-380

Begin Function { }
	Return
	GetFPS
	TestRef.GetDistance PlayerRef
	< repeat above two another 3800 times >
End

Test 3: Same as 1 except function script's code was encapsulated inside a "If 1 == 2" block: FPS was 139

Begin Function { }
	If 1 == 2
	GetFPS
	TestRef.GetDistance PlayerRef
	< repeat above two another 3800 times >
	EndIf
End

Test 4: Same as 1 except I inserted an early Return as recommended by the wiki page: FPS was 350-380

Begin Function { }
	If 1 == 1
		Return
	EndIf
	GetFPS
	TestRef.GetDistance PlayerRef
	< repeat above two another 3800 times >
End

So, contrary to my previous results, it seems that the article is actually 100% correct. Clearly, Oblivion's scripting engine isn't actually executing the functions in a false If check but it is still doing something that an early Return negates.

 

Edit: I should probably also list my PC's specs for comparison:

CPU: AMD Ryzen 5 1600

GPU: GeForce 1080 Ti

SSD: Samsung SSD 850 Evo

OS: Windows 10 Pro x64 1703

Link to comment
Share on other sites

The way I understand it is the game engine READS the code looking for either a RETURN or END

in order to EXIT the code its reading.

 

 

IF its READING (not PROCESSING) all the code in my menumode scipts when its in gamemode, then my mod needs to be reworked.

I have a menu block that has almost 1200 lines of code.

 

I can break my menumode code down and make it call for OBSE user made functions so long as those aren't read all the time either.

 

 

Thanks for running those tests

it puts things in perspective

Link to comment
Share on other sites

[NOTE: this was posted before I noticed the two posts above]

 

@ XJDHDR

I think we are in the same page here (pun intended).

 

Clarifying my point of view (1) : My “if 1 == 2” example mas meant as an irony. It would be ridiculous if I had any performance gain by including that IF in my scripts (but this is what the WIKI text implies).

 

Clarifying my point of view (2) : I think the article is either wrong or irrelevant.

 

My reasoning for thinking it is wrong:

  1. There is NO Way the script engine executes code inside false IF conditions. It would be disastrous. So when the article says “PROCESS all code inside of an If block” it certainly does not mean “EXECUTE all code inside of an If block”. If, by “Process”, the author meant “Execute”, he is wrong.

My reasoning for thinking it is irrelevant:

  1. What the engine might implement is a pre-analysis of the code for whatever reason, in which case it might go thru (process) all the code prior to actual execution. In this scenario, I cannot imagine why it would stop in the first Return it finds (but who knows . . .). Anyway, the efficiency effect of a pre-analysis process would be negligible.

 

It is too bad Beth deleted the Forums. There were a lot of good technical discussions there. But the link in the WIKI page does not seem to refer to the matter at hand (Hamma included the link about six months before Halo112358 added the text we are discussing here. See the page History)

 

It would be nice if you could test the exact two examples in the article. I doubt very much it will yield noticeable results (let alone “dramatic performance gains”, as mentioned)

Edited by QQuix
Link to comment
Share on other sites

@ XJDHDR

 

You Test1 is equivalent to the first WIKI example ("unoptimized")

 

Could you test one additional scenario: the so-called "optimized" in the WIKI second example:

Begin Function { }
  If 1 == 2
    Return
  EndIf
  GetFPS
  TestRef.GetDistance PlayerRef
  < repeat above two another 3800 times >
End
Link to comment
Share on other sites

Yeah, by "processing" they clearly didn't mean "execute". Though I'm still wondering what exactly could even cause those observed slow-downs, considering ObScript isn't "interpreted" line by line inside the game but pre-compiled on each script/plugin save in the CS.

 

"Interpreter" languages are parsed line by line, command by command, and each call is translated into machine code individually, then executed immediately.

"Compilation" is translating, and not rarely also optimizing, script code into machine code "before" execution later, and is vastly more performant than interpreter languages could ever be.

 

So if it is already "compiled", then what exactly does a missing return statement "add" to the processing, if there isn't even a thing like the return, or at least no exact equivalent, in compiled code? But of course I'm also missing the very vital information of how exactly the code looks once compiled, or better yet "into what" the ObScript is compiled by the CS on save.

 

I mean, if even after compilation the entire code is still an interpreter language, i.e. parsed and executed line by line inside the game each time, "then" I could understand it in a way, as even non-executed, skipped lines will still be read, parsed and translated every time, or at least in a very dense, idiotic kind of interpreter code, which I suspect we're looking at here. This is what could be meant by "processing" here. But from my programmer's knowledge compiled code normally isn't interpreted anymore.

Link to comment
Share on other sites

Sorry for the late reply.

 

@Moktah

I did some more testing by moving my test script into a MenuMode block and added a small GameMode block above it. My framerate was normal (350-380) in GameMode and dropped to around 40 (can't remember exactly) in MenuMode. It looks like this quirk is limited to the block that was called and not the blocks that aren't currently running. And yes, StopQuest completely stops any script attached to the quest.

 

 

@QQuix

My apologies! I missed the sarcasm.

Test 1 was supposed to be just to set a baseline for the performance impact of my scripts without any attempts to stop it running. Test 3 was supposed to be representative of an "unoptimised" script, using an If block to bypass the complex code. Test 4 conversely represented the "optimised", using an early Return. Or at least that is how I interpreted what the article said. I gave your proposal a try though: I took my script from test 4 and made this change:

If 1 == 2

My FPS was around 29. I can't say that there was any difference between this and unoptimised. There was a ~1 FPS improvement but I can't tell whether it was a genuine improvement or just a variation within the margin of error.

 

 

@DrakeTheDragon & QQuix

It is possible that the scripts are "compiled" for an interpreter in the game but I can't accept that. A competent interpretor would compile the entire script into machine code either on startup or the first run. It certainly wouldn't happen every time the script runs. Additionally, if the Return command bypasses this step in some way, then I don't know why this isn't causing some massive problems from not compiling most of the script. And if this is what is happening, then technically the CS is not compiling the scripts but sort of assembling them (even this isn't entirely accurate).

 

Another possibility I can think of is that the game is validating the script before running it. This makes more sense to me because this validation being bypassed wouldn't break the script execution (assuming the script is in fact valid). This would be excessive though because not only does the CS already validate the script before compiling, there is no reason why this validation needs to be done every time you run it and not once on startup.

Link to comment
Share on other sites

@ XJDHDR

 

I have a feeling we are reading the Wiki text differently,

 

The way i understand it, it says that, if you have an if block and an else block, you should place the one that has a Return first.

 

Like, instead of doing something like this:

Begin GameMode
  if  Gamehour < 12
    <some long code>
  else
    <some short code>
  Endif
End
It would be better to do this:

Begin GameMode
  if  Gamehour >= 12
    <some short code>
    Return    '--- Adding a Return to prevent 'processing' of the long code below
  else
    <some long code>
  Endif
End

 

Or instead of doing something like this:

Begin GameMode
  if  Gamehour < 12
    <some long code>
  Endif
End
It would be better to do this:

Begin GameMode
  if  Gamehour >= 12
    Return
  Endif
  <some long code>
End

If this understanding of the Wiki text is correct, extrapolating this last example, I considered that the article recommendation would also apply to:

Instead of doing something like this:

Begin GameMode
  <some long code>
End
It would be better to do this:

Begin GameMode
  if  1 == 2
    Return
  Endif
  <some long code>
End

Note that, in all three cases, the actual execution is exactly the same in both codes, but, according to my understanding of the wiki text, it claims that the second code runs faster.

 

Which does not seem right to me (but, again, who knows...).

Link to comment
Share on other sites

Well, a competent "Interpreter" would not compile the entire script on startup or first run. It's the difference between compiled languages and interpreter languages that the latter "translates" line by line, and every script run over again. That's why they are so significantly slower than compiled languages, i.e. where only direct machine code is run at all and nothing of the original language is left.

 

The suspicion of the "compiling" performed by the CS on save only being some sort of translating into another, maybe more efficient, interpreter language isn't a that invalid one.

The suspicion of scripts being "validated" on runtime isn't a bad one either, considering that the use of e.g. OBSE functions in a game without OBSE isn't checked and responded to any earlier than the point at which the first OBSE function gets actually "called", then simply leading to the script aborting without any signal or message, but every line "before" the OBSE function call very well having been executed without a hitch.

The game very well "could" do such checks only once on start-up, and if it's on every start-up of the game, to incorporate changes by e.g. the game having been re-started with a previously missing requirement, but actual observations seem to tell different.

 

The game's scripting engine doesn't really seem to be very well thought out or designed at all, from a professional's point of view in hindsight. So "expected" behavior or solutions isn't really something I'd expect to see.

 

 

But @QQuix: What makes you take from the WiKi article that your last block of code would actually be better to do than the same code without the always-false "if"? That's not what the article says. It says that ending a script with a "return" statement before a non-executed code block (due to a false "if" condition) comes is more performant than not using a "return". But that doesn't mean that a "return" statement that's never reached (due to an always false "if" condition) would do the same.

 

Whether it actually is "interpretation" of code lines or just "validation" of such, the "return" statement seems to be the only thing able to stop it, and only if it is actually reached. So even if it's validation, the observed behavior dictates said validation will only stop, if the condition for the "return" inside an "if" is actually met not just at any "return" it -could- at a different run potentially reach.

 

 

Though is it just me, or is anybody else getting a heavy migraine the longer you think about how or what Bethesda could've actually done here and how it meets or doesn't meet, or possibly could meet, with the actual observations? :wacko:

 

Only folks like IanPatt, Behippo, Scruggs, Shademe et al. (forgive me, if I didn't get you all), who had an insight into the actual game's/scripting engine's code, will really be able to tell more here at this point.

Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...