ProjectsWhat's NewDownloadsCommunitySupportCompany
Forum Index » S.T.A.L.K.E.R.: Shadow of Chernobyl Forum » Mod discussion
Script optimization question

1 2 3 4
Posted by/on
Question/AnswerMake Newest Up Sort by Descending
  22:46:56  28 January 2013
profilee-mailreply Message URLTo the Top
Decane
Senior Resident
 

 
On forum: 04/04/2007
 

Message edited by:
Decane
01/28/2013 22:57:38
Messages: 1701
Script optimization question

Of the following codes, which is fastest and why?

(A1)

local act = db.actor
function test()
  if not has_alife_info("tester") then
    if IsStalker(act) then
      act:give_info_portion("tester")
    end
  end
end


(A2)

function test()
  local act = db.actor
  if not has_alife_info("tester") then
    if IsStalker(act) then
      act:give_info_portion("tester")
    end
  end
end


(B1)

local act = db.actor
function test()
  if not has_alife_info("tester") and IsStalker(act) then
    act:give_info_portion("tester")
  end
end


(B2)

function test()
  local act = db.actor
  if not has_alife_info("tester") and IsStalker(act) then
    act:give_info_portion("tester")
  end
end


Apart from remarks relating to the stuff above, other optimization tips are also welcome.

EDIT: Also, which of these is faster?

if db.actor then
...


if db.actor ~= nil then
..


Or are they equally fast?

All this is strictly theoretical speed, by the way; I know that in practice the difference will be negligible.
  23:05:12  28 January 2013
profilee-mailreply Message URLTo the Top
ThunderFreak
Senior Resident
 

 
On forum: 08/07/2009
Messages: 685
I would say version A2 is fastest.

Why?
Local variable declarations are faster than global declarations. Which means local declaration outside a function block are not belonging to a function in first place. Those variables need to be indexed.
Declarations within a function block are directly belonging to that function block. No need to index them.
To call an indexed variable causes to search thru the index, which takes time. To call a variable directly causes no time.

'AND' operations are quite slow, so it's better to two 'if'-conditions in two lines rather than in in one line linked with 'AND'.

Hope you understand what I mean.
  00:08:09  29 January 2013
profilee-mailreply Message URLTo the Top
SetaKat
Ex modder, Zones only ferret and will someday release a game
(Resident)

 

 
On forum: 02/20/2010
Messages: 6340
Thanks for that info on 'and' operators being slow, will remove them from my own scripts now, should get a speed boost.
  00:43:15  29 January 2013
profilee-mailreply Message URLTo the Top
Decane
Senior Resident
 

 
On forum: 04/04/2007
Messages: 1701

---QUOTATION---
Local variable declarations are faster than global declarations. Which means local declaration outside a function block are not belonging to a function in first place. Those variables need to be indexed.
Declarations within a function block are directly belonging to that function block. No need to index them.
To call an indexed variable causes to search thru the index, which takes time. To call a variable directly causes no time.

'AND' operations are quite slow, so it's better to two 'if'-conditions in two lines rather than in in one line linked with 'AND'.
---END QUOTATION---


Interesting; thanks for replying.

Now what about these two?

(A1)

function something()
	local act = db.actor
	local vrtx = act:game_vertex_id()
	do something_else(act, vrtx)
end


(A2)

function something()
	local act = db.actor
	local vrtx = db.actor:game_vertex_id()
	do something_else(act, vrtx)
end


------------

And these?

(B1)

function r()
local t = {"friend", "enemy"}
	for k, v in pairs (t) do
		if db.actor == v then
			return
		end
	end
end


(B2)

function r()
	if db.actor == "friend" then
		return
	end
	if db.actor == "enemy" then
		return
	end
end


(We could also exploit the knowledge that "friend" and "enemy" are mutually exclusive to write:)

(B3)

function r()
	if db.actor == "friend" then
		return
	elseif db.actor == "enemy" then
		return
	end
end


(B4)

function r()
	if db.actor == "friend" then
		return
	else
		if db.actor == "enemy" then
			return
		end
	end
end


Which of these is fastest?
  01:23:28  29 January 2013
profilee-mailreply Message URLTo the Top
Daemonion
All About Audio
(Resident)

 

 
On forum: 09/27/2011
Messages: 567
What different in speed are we talking about here? A tenth of a second?
  02:19:58  29 January 2013
profilee-mailreply Message URLTo the Top
SetaKat
Ex modder, Zones only ferret and will someday release a game
(Resident)

 

 
On forum: 02/20/2010
Messages: 6340
Between B1 and B2, B2 is faster. The pairs() function is supposed to be quite slow.
Between A1 and A2, I'd imagine A1 is faster, since it doesn't need to look up the actor object a 2nd time.
B3 and B4, going by what I remember from C#, are exactly the same when compiled and excuted. I'd imagine it'll be the same for Lua when interpreted.
  02:35:30  29 January 2013
profilee-mailreply Message URLTo the Top
Alundaio
Sad Clown
(Resident)

 

 
On forum: 04/05/2010
 

Message edited by:
Alundaio
01/29/2013 3:09:09
Messages: 2230
pairs() is slow. Using a regular for loop with an indexed table is TWICE as fast when appropriate. There is a myth that ipairs is faster then pairs but they are about the same. I seen a few benchmarks with for loop v. pairs v. while v. ipairs and it showed for loop was slowest but that's a lie. I used to have a really good source for lua optimization that showed benchmarks for the most common functions and routines but I can't seem to find it.

As for B3 and B4 it's actually faster to do this in lua:


return val == "friend" or val == "enemy"



if-else:

if db.actor:relation(npc) == game_object.friend then
	return 1
elseif db.actor:relation(npc) == game_object.enemy then
	return 2
elseif db.actor:relation(npc) == game_object.neutral then
	return 3
end



short-hand is faster:

return db.actor:relation(npc) == game_object.friend and 1 or db.actor:relation(npc) == game_object.enemy and 2 or db.actor:relation(npc) == game_object.neutral and 3




It's barely noticeable when you use different methods unless you are doing thousands of calculations in a short time. The true key to lua optimization is actually localizing and scoping variables properly; it can be a dramatic difference.

But all that said, "Premature optimization is the root of all evil."
  10:35:35  29 January 2013
profilee-mailreply Message URLTo the Top
Decane
Senior Resident
 

 
On forum: 04/04/2007
 

Message edited by:
Decane
01/29/2013 10:56:06
Messages: 1701
I should not have written these in haste; I forgot to mention that in the case of B3 and B4, "friend" and "enemy" are the only possible values for the variable db.actor. So there is no "neutral" or anything else. However, if there were, then would B3 be faster? I suspect so because the "else" in B4 is doing the check, 'if not db.actor == "friend"', which seems to be equivalent to 'if db.actor == "enemy" or db.actor == "neutral"' with two other possibilities (???), so that the code in B4 is actually interpreted like this:

function r()
	if db.actor == "friend" then
		return
	elseif db.actor == "neutral" or db.actor == "enemy" then
		if db.actor == "enemy" then
			return
		end
	end
end

And of course, in this case, B4 would be slower since it is doing a redundant check. Is this correct?

--------------

Alundaio, thanks for those insights. GSC's code is filled to the brim with ipairs() ...

--------------

I have a few more questions. Which of these is faster, if either:

(A1)
function is_bandit_happy()
	local bandit_mood = sim_faction.get_mood("bandit")
	return (bandit_mood ~= "depressed")
	and (bandit_mood ~= "a bit blue")
	and (bandit_mood ~= "content")
end

(A2)
function is_bandit_happy()
	local bandit_mood = sim_faction.get_mood("bandit")
	return not (bandit_mood == "depressed")
	and not (bandit_mood == "a bit blue")
	and not (bandit_mood == "content")
end

Assume that our 'bandit mood table' does not contain a value for "happy". If I had to guess, I would assume that ("" ~= "") is shorthand for (not "" == ""), but in this case, lua would need to interpret the shorthand, whereas it would not need to do that with the reference notation. So in theory, (not "" == "") would be faster?

Also, can I do this in lua:


(A3)
function is_bandit_happy()
	local bandit_mood = sim_faction.get_mood("bandit")
	return bandit_mood ~= ("depressed" or "a bit blue" or "content")
end

If so, will this make the code any faster?

--------------

Finally, a question about localizing variables (again): is it faster to push local variables inside a function as much as possible (i.e. to make their scope as narrow as possible) or is it better to define them so that their scope is as wide as possible (within the function)? Example:


(B1)
function bandit_is_happy()
	local bandit_mood = sim_faction.get_mood("bandit")
	if bandit_mood == "happy" then
		local bandit_is_happy
		-- define and do something with variable bandit_is_happy
	end
end

vs.


(B2)
function bandit_is_happy()
	local bandit_mood = sim_faction.get_mood("bandit")
	local bandit_is_happy
	if bandit_mood == "happy" then
		-- define and do something with variable bandit_is_happy
	end
end

Thanks for all the replies, guys. This has been very fruitful so far.

EDIT: Removed smilies.
  13:59:01  29 January 2013
profilee-mailreply Message URLTo the Top
ThunderFreak
Senior Resident
 

 
On forum: 08/07/2009
 

Message edited by:
ThunderFreak
01/29/2013 20:55:44
Messages: 685
One hint to boost your code a little bit more.

NEVER declare a variable inside a for ... end block.
1. This variable is not valid outside the block. Also valid for if ... end as well.
2. As long you are looping thru for ... end evertime a variable declaration (local variable = ) is called a new variable will be created which ends up in indexed variables, because as long you are looping the variable is not deleted.

B2 is not really faster to B1 but the variable declaration is outside the if ... end block. This makes the variable valid inside the and outside the if ... end block.
  15:36:23  29 January 2013
profilee-mailreply Message URLTo the Top
Decane
Senior Resident
 

 
On forum: 04/04/2007
Messages: 1701
Good stuff!
  20:55:12  29 January 2013
profilee-mailreply Message URLTo the Top
ThunderFreak
Senior Resident
 

 
On forum: 08/07/2009
Messages: 685
You got mail.
  21:06:28  29 January 2013
profilee-mailreply Message URLTo the Top
Decane
Senior Resident
 

 
On forum: 04/04/2007
Messages: 1701
Wow, thanks a million! I read your comments and they are way more detailed than I could have expected; that was a huge help. Very kind of you.
  00:22:24  30 January 2013
profilee-mailreply Message URLTo the Top
Alundaio
Sad Clown
(Resident)

 

 
On forum: 04/05/2010
 

Message edited by:
Alundaio
01/30/2013 4:48:47
Messages: 2230

---QUOTATION---
One hint to boost your code a little bit more.

NEVER declare a variable inside a for ... end block.

---END QUOTATION---



Yeah that is important. Too bad GSC didn't practice this. In CoP, gulag_general.script is RIDDLED with these.


function is_bandit_happy()
	local bandit_mood = sim_faction.get_mood("bandit")
	return bandit_mood ~= "depressed" or  bandit_mood ~= "a bit blue" or bandit_mood ~= "content"
end

  03:45:50  9 February 2013
profilee-mailreply Message URLTo the Top
NatVac
Senior Resident
 

 
On forum: 06/15/2007
Messages: 4282
>> NEVER declare a variable inside a for ... end block.

Um... actually, it really doesn't matter.

First some background on what follows: A long while back Decane (!) posted a set of links for learning Lua. Here's some info from the Lua users wiki he linked.

Locals are faster than globals, as Thunderfreak first said:
http://lua-users.org/wiki/OptimisingUsingLocalVariables

This is true even if there are register spills (when there are more vars than CPU registers) resulting in memory storage.

Local functions are slower when encountered in execution (i.e., don't declare functions inside loops)
http://lua-users.org/wiki/MinimisingClosures

Next, some contemplations that might impact what really happens with locals in loops:

Lua is designed for gaming. That implies speed of execution. To this end, most Lua deployments including STALKER use the just-in-time compiler. This means that each line is not continually being interpreted.

Most stack-based compilers (all?) simply pre-allocate on the stack all locals declared inside a function. This means that with the logical exception of closures (those aforementioned functions that have additional info associated with their context, making them dynamic), all local variables are simply indexed off the stack and it doesn't matter where they are declared inside the function.

Note that the stack in modern compilers is smart, using CPU registers for some stack items. Quantity of local vars might matter (see "register spills" above), but their impact should still be constant per n local variables.

Finally, some empirical observations:

I created a test with two local variables of interest, one declared outside the outer of two nested loops and one declared inside the inner loop. The loops were for ix=i,100,1 do for id_=1,4000000,1 do [...] end end, with the [...] containing enough repeated assignments of simple-addition calculations to the locals (10 statements) to make the process last several seconds.

There was no discernible difference. Each execution took just under seven seconds. (Slow box.)

I then added another local variable to each loop, so that the first test had two outside locals and the second had two inside locals declared and assigned. The statements were changed to share the variables but no new statements were added.

No change in test results. Each execution still took just under seven seconds.

In the second test two locals were declared in the inner loop and assigned a initial calculated result 400 million times. (With the ten statements, over four billion statements were executed.)

In a third test, I moved the local declarations of the first test outside the function. It took almost a minute to execute the same code, further supporting Thunderfreak's first reply.

No, that's not rigorous science, but it is more in the ballpark than any speculation.

It may be that some of your tests may experience an initial overhead of parsing/compiling. I expected a need to pre-load the test script, but the game was already doing that for me; my test framework lists the available utilities and their versions, which requires that the scripts be valid. So if you do this test on your own, run it several times.

Conclusion: Don't use local functions inside functions. Other local variables are fine, with the exception of table creation.

With that said, this is mainly a "peephole" optimization discussion. If we are talking about local tables, there's a trade-off. While it doesn't matter where local tables are declared within a function, it may be sometimes better to hoist constant-data tables out of the function and set local vars equal to the external tables. This results in a small hit when loading, but removes the hit incurred on each call to the function using the table.

There are a lot of good thought-stimulating ideas here. I would like to add a couple of my own: I believe there are many important areas ripe for optimization that might have a greater impact. One is large-resource management. The moment virtual-memory usage demands swapping external (non-RAM) storage is the moment the framerate plumments in-game. The brain-dead prefetch was loading in everything, resulting long level loads followed by lag anyway due to dumping unused stuff to load in needed stuff. Not to mention that some vanilla prefetch resources are not even used by the game...

The game is rife with stuff not used. It loads logic for test objects and puts the checks for those cases in the smart terrain assignments. It checks for specific entities as part of tests that were not removed. There are lots of extra support for demos and dev-specific explorations that the game loads and then processes each time, like info_portions and XML structures. Some is simply loaded in memory, some is constantly being checked.

>> "friend" and "enemy" are the only possible values for the variable db.actor

If we are talking SoC post init I think you mean this as a shorthand for db.actor.something or db.actor:something() as db.actor holds a game object.

If it's either/or, I recommend using a boolean, not a string compare (slower):

function is_friend() return db.actor:relation(npc) == game_object.friend end

If doing if-then-else using the same db.actor:relation(npc), assign it to a local var first and use that in the comparisons. This is only slightly slower if the first "if" test is true, and much faster for subsequent tests.

>> There is a myth that ipairs is faster then pairs

I've never heard this myth. I've only heard that ipairs is for accessing the numerically-indexed members of a table in sequential order. There is no guarantee on order for pairs. Also, tables can have non-numerically-indexed items combined with numbered ones; I think ipairs will ignore the ones not numbered.

Due to your comments here, guys, I'm already looking at more optimizations myself. Similar to Alundaio's quote from Donald Knuth, there's M.A. Jackson's Rules of Optimization:

Rule 1: Don't do it.
Rule 2 (for experts only): Don't do it yet.
  21:30:29  10 February 2013
profilee-mailreply Message URLTo the Top
Decane
Senior Resident
 

 
On forum: 04/04/2007
Messages: 1701
NatVac, thanks for your comments. On their account, I now have another question: how do you go about identifying which files are not used by the game at all? I would like to do something similar to your 'SHOC Treatment' project for Clear Sky, but at present I am pretty much limited to commenting out redundant debug code like printf() and print_table() since I don't know of any way to batch-identify unused functions.

Actually, it would be convenient to have a method of identifying unused sounds, textures and meshes in addition to unused scripts; particularly since the CS database files are filled with unused SOC assets.
  11:52:36  18 February 2013
profilee-mailreply Message URLTo the Top
NatVac
Senior Resident
 

 
On forum: 06/15/2007
Messages: 4282
My comments about the "local vars in loops" test might have been a bit confusing. The point was that the location of local var declarations inside a function didn't matter (inside nested loops or outside), but vars declared outside the function are much more slowly accessed.

>> how do you go about identifying which files are not used by the game at all?

It's mostly informed trial-and-error. Here are some of my techniques for SoC which might apply in some fashion to the other games. Some are probably mentioned in this thread and elsewhere already. Of course, one should make backups and test each change or group of changes before going on to the next. Keep a list of changes; some change-related problems might become apparent only after extended play or later in the game.

Identifying the unused resources:

I use Notepad++ to search in all resource files. I locate the starting points for the processing of data; in SoC I traced all the loading of nested files via #include directives. I have an extracted base of resources in a single directory for the different patch versions, but the primary search is in the 1.0004 gamedata subdirectory as the later versions don't add any significant stuff. Thisincludes an extracted spawns subdirectory.

You need a smart extraction from the database files, as the files must be complete up to that version and the newer must supplant the older. I remember that items.ltx and at least one other config or script file were not in gamedata.dbb the way you would expect.

I use search filters to limit which files are checked.

I use the "Execute Lua Script Command" utility in the ZRP Support Utility toolkit. You can type in the name of the script file and press Enter to see if the script is valid. It shows up as a table if it is valid, otherwise it shows "nil". Such scripts are not used by the game. (The game might try to use it, but that will be a crash.) So a quick way to identify broken files would be a script loop that would check a script file for existence (example: xxxx.script) and then check to see if there is also a table for it (type(xxxx) == "table" or at least not nil).

You can't know that a script is loaded by the game that way because of an effect similar to the Heisenberg uncertainty principle: The very act of checking a script object in Lua will load that file if it is not already loaded.

Special note: When determining if a resource file is used, removing files from a gamedata directory isn't enough. You have to remove them from the database files as well. A possible workaround is to use a zero-length file in gamedata\.

First I look for test code. Extremely wasteful tests occurred in code for a particular entity (e.g., esc_wolf) that would only print out something. I look at the files. Some are suspicious just by their names, like the x1.script through x18.script files, or the task_number.script files.

You can embed debug statements in script files to see if/when they are called. Statements at file scope (i.e., in the file but not in any function) are executed when the file is parsed successfully.

You can delete the suspicious script and run the game. It will complain if it can't find the script. See the special note above.

Other script files can be checked by searching all script files for their name sans ".script".

I was able to remove about half the scripts from SoC as unused or unneeded. Similar tactics can be used for textures and meshes, although some of the references are in the non-text files. The game can help by crashing with a "can't find" message.

I would expect CS to not have most of the unused SoC junk while it adds new and unused junk of its own.

After the unused files are removed:

It helps greatly to learn the system. Knowing that certain resources are not used can help identify what can be further pruned, like gulag references to Dead City or kishka in SoC's config\misc\gulag_tasks.ltx, or test logic in the same file.

At some point you will start asking yourself, "Do NPCs really need a xr_test scheme?" and start experimenting with reducing the load. I found out that almost all NPCs were being assigned sounds only used by special stalkers. Each NPC is now only burdened with about 40% of his original load in the recent ZRP versions.

>> at present I am pretty much limited to commenting out
>> redundant debug code like printf() and print_table()

Those can actually work in CS, but that change will probably go a long way to reclaiming time and memory.

Related stuff for commenting out/removing:

Statistics and collection of any data that is not used except by the printing/logging functions. I get a kick out of a modder's "Eureka!" when he discovers that the game_stats tables built in bind_stalker.script are the source of "leaks" -- they are not, but because they just keep accumulating stats, your memory usage grows and grows in each game session. And the smart terrain stats were always loaded but only used by folks who enabled the display of "spots".

Code that encloses only printing/logging statements. Often a function will declare a local variable, assign it a complex value, then only use it in a subsequent print statement, or there will be a block that tests a value only to print it. You can safely remove all of that.

You can also try to identify bottlenecks and memory wasters. Use debug statements liberally. As discussed previously in this thread, sometimes a simple change to the way that code executes can speed up execution. I don't know about the other games, but prefetch in SoC was not very useful, making for a long load only to result in unused data getting swapped out anyway.

>> particularly since the CS database files are filled with unused SOC assets.

So are the SoC database files.

And xoen, bamah and others showed the value of compressing many of the textures for a faster load and less memory footprint without a noticeable loss of visual quality.
  05:21:57  10 March 2013
profilee-mailreply Message URLTo the Top
Alundaio
Sad Clown
(Resident)

 

 
On forum: 04/05/2010
Messages: 2230
Lua optimization: http://stackoverflow.com/questions/154672/what-can-i-do-to-increase-the-performance-of-a-lua-program
  23:42:07  8 April 2013
profilee-mailreply Message URLTo the Top
NatVac
Senior Resident
 

 
On forum: 06/15/2007
Messages: 4282
Thanks for linking that, Alundaio! I've lost a lot of time since I saw this almost a month ago, converting string concats in SoC in quite a few files and I'm still not done.

SoC's NPC logic is largely dynamically assigned and a large part is constructed on-demand with string concatenations in loops. My testing on the changed code shows some memory savings but not necessarily any processing savings. I'm far from done with the changes and testing, though.

The rest of the linked optimizations I'd seen before, but the argument for table.concat() was quite persuasive.
  00:42:02  9 April 2013
profilee-mailreply Message URLTo the Top
LoNer1
Настоящий Cталкер, Русская Версия
(Resident)

 

 
On forum: 10/23/2009
 

Message edited by:
LoNer1
04/09/2013 0:42:51
Messages: 1890

---QUOTATION---
Thanks for linking that, Alundaio! I've lost a lot of time since I saw this almost a month ago, converting string concats in SoC in quite a few files and I'm still not done.

SoC's NPC logic is largely dynamically assigned and a large part is constructed on-demand with string concatenations in loops. My testing on the changed code shows some memory savings but not necessarily any processing savings. I'm far from done with the changes and testing, though.

The rest of the linked optimizations I'd seen before, but the argument for table.concat() was quite persuasive.
---END QUOTATION---



NatVac, I have a slight mancrush on you, seriously

You keep this community alive, constantly replying and updating your flawless additions I don't have any input here, but thanks! I just know there's good stuff going on when the last person to reply to 6 threads is you!
  05:03:00  27 April 2013
profilee-mailreply Message URLTo the Top
NatVac
Senior Resident
 

 
On forum: 06/15/2007
Messages: 4282
There's been some reporting that shaders can affect stuttering. Supposedly getting improved shaders can reduce the pauses considerably. I can't tell yet.

¯¯¯¯¯¯¯¯¯¯
Thanks, I think, LoNer1. But the community is surviving just fine without my being here that often. There are a lot of consecutive posts by me because I don't post that often, and mainly catch up on the unanswered ones when I get a large block of time to reply.

And I'm still human, last I checked. My errors are compounded when I've missed a lot of sleep, so it is good that you guys can catch what I miss.
  18:22:09  27 April 2013
profilee-mailreply Message URLTo the Top
Jketiynu
Swartz
(Resident)

 

 
On forum: 04/05/2007
 

Message edited by:
Jketiynu
04/27/2013 18:28:26
Messages: 867
Natvac, if you don't mind I have a few questions for you regarding script optimization.

I've been reading lots of tutorials on LUA optimization as well as finding a few tools that claim to help in optimization.

Here's my questions:

1) Does removing "white space" make any difference? Likewise does it make any difference to remove comments rather than just leaving them commented out? I've seen multiple claims of this, however I wonder if it would make any real difference with Stalker scripts?

2) Does the length of things like a name of a local variable name perform better when shorter? Again I'm seeing claims that it does.
Example:

local reallylongstringofcharacters = bone_index

vs.

local a = bone_index



Thank you for any help.
  02:14:03  9 May 2013
profilee-mailreply Message URLTo the Top
NatVac
Senior Resident
 

 
On forum: 06/15/2007
Messages: 4282
I can give you my slightly-informed opinions on this.

>> 1) Does removing "white space" make any difference?

If we are talking scripts or configuration files: It might make loading a teensy bit faster and debugging a lot harder. There would be no impact on script execution due to the JIT compiler. It might make a small difference on ini_file() processing, as you are looking at the raw lines for some operations. If you repeatedly read the same lines in event loop processing, this might be worth optimizing.

If we are talking shaders, you guys know much more than I. I would expect the shader compiler to remove any overhead. Maybe the compile runs faster if there is less to process. Also, if the shader routines for a complex scene take more space than the instruction buffer on the card (at least 64K on modern cards), you can expect swapping to RAM with an FPS hit. But that's more a scene design or shader coding issue than a white space issue.

---QUOTATION---
Likewise does it make any difference to remove comments rather than just leaving them commented out? I've seen multiple claims of this, however I wonder if it would make any real difference with Stalker scripts?
---END QUOTATION---


Just in initial loading and parsing. The game uses a JIT compiler, so actual code execution doesn't even know about the comments. You can save a few milliseconds off the script parsing, and the load from disk is not going to be appreciably reduced if you remove all the comments.

What I've noticed is some significant improvements in loading and execution when valid but unused junk is kept from being loaded and subsequently managed by the game. Don't load it, don't have memory allocated for it, don't have objects defined and processed if they are not going to be used.

---QUOTATION---
2) Does the length of things like a name of a local variable name perform better when shorter? Again I'm seeing claims that it does.
---END QUOTATION---


Before I tested this, I was of the thought that code execution was not affected by var length for locals. My opinion has not changed after testing. Using 27- and 28-character names had no discernable difference in test code execution versus 2-character names used millions of times, for both vars local to the routine or just local to the script object. (See test info in previous post in this thread.)

I didn't test tables, but strings are hashed in the game even for them. The one contributor would be the extra few milliseconds to parse through the script, something you will not normally notice unless something else is wrong.

Note that the foregoing did not include the cost of using loadfile() or loadstring() during game execution. In those cases, you would be dynamically parsing text to execute it as code, and in this case, yes, shorten var names, get rid of spaces, comments, etc. If possible, don't use those functions except during initialization or in debugging. Better still, just put that code in a normal script file. Repeatedly recompiling the same code is the opposite of optimization.
  19:53:40  10 May 2013
profilee-mailreply Message URLTo the Top
Jketiynu
Swartz
(Resident)

 

 
On forum: 04/05/2007
Messages: 867

---QUOTATION---
Helpful stuff.
---END QUOTATION---



Thank you!

I had "optimized" various scripts and was wondering why it seemed to make no difference in performance

That's good because it means modding and merging will be easier as it makes for some hard-to-read scripts if you do the stuff I mentioned.
  22:09:15  19 August 2013
profilee-mailreply Message URLTo the Top
Decane
Senior Resident
 

 
On forum: 04/04/2007
 

Message edited by:
Decane
08/20/2013 0:04:38
Messages: 1701
Suppose that I have the table and function below:
t = {1, 2, 3}

function a()
	for k, v in pairs (t) do
		return 2 >= v
	end
end

I want to know if the function returns the truth value of the first iteration of the pairs() loop (i.e. "true", because 2 >= 1), or if it iterates through the entire table and consequently returns the truth value of the third and final iteration of the pairs() loop (i.e. "false", because 2 >/= 3).

In general, I would wish to know how a "return" declaration behaves when it takes place inside a key-value pairs() loop. (I realize that this is not strictly an optimization question, but it relates to optimization through its implications.)

EDIT: Simplified the question.
  08:08:23  20 August 2013
profilee-mailreply Message URLTo the Top
NatVac
Senior Resident
 

 
On forum: 06/15/2007
Messages: 4282
Decane, the return statement returns when encountered in the script with the value or values parsed from the argument. In your example, the processing of the first pair in the table would execute the return statement with the boolean value of the compare using the value of that first pair. The remainder of the table processing would be skipped.

You are using a table with just values, equivalent to { [1]=1, [2]=2, [3]=3 }. You might expect the first value to be 1 in your loop. But--

"The order in which the indices are enumerated is not specified even for numeric indices." The docs say use a numerical for or use the ipairs function for this.

As an example of the order issue, look at your inventory from game to game. Certain artifacts will always appear before or after others for the whole game, but this order will vary from game to game. It may be that your table is safe by definition, but tables that are manipulated prior to the loop processing have no guaranteed order.

I'd expect a true value returned for the function with that specific data, but otherwise it's a toss-up.
  09:36:21  20 August 2013
profilee-mailreply Message URLTo the Top
Decane
Senior Resident
 

 
On forum: 04/04/2007
Messages: 1701
Very helpful as always, NatVac. Thank you!
  11:41:47  21 August 2013
profilee-mailreply Message URLTo the Top
Decane
Senior Resident
 

 
On forum: 04/04/2007
 

Message edited by:
Decane
08/22/2013 23:49:20
Messages: 1701
More optimization (or just 'good code') questions

(1) Is a boolean compare always preferred to string compares where feasible, even if the boolean compare makes use of lots of "and" statements? For example:

(A1)
function a()
	if not x then
		return false
	end
	if not y then
		return false
	end
	if not z then
		return false
	end
	return something or something_else
end


(A2)
function a()
	return x and y and z and (something or something_else)
end

¯¯¯¯¯¯¯¯¯¯¯¯

(2) How would the following functions be ranked in order of speed, and why? Based on what has already been written in this thread about string compares, I would hypothesize that B3 and B4 both are faster than either of B1 and B2. Based on what has been written about local variables, I would hypothesize that B4 is the fastest of them all. But I really have no clue about B1 vs. B2.

(B1)
function b(p)
	if p[1] == "a" or p[1] == "b" or p[1] == "c" then
		return f() == p[1]
	end
	return false
end


(B2)
function b(p)
	if p[1] ~= "a" then
		if p[1] ~= "b" then
			if p[1] ~= "c" then
				return false
			end
		end
	end
	return f() == p[1]
end


(B3)
function b(p)
	return (p[1] == "a" or p[1] == "b" or p[1] == "c") and f() == p[1]
end


(B4)
function b(p)
	local a1 = p[1] == "a"
	local b1 = p[1] == "b"
	local c1 = p[1] == "c"
	local fx = f() == p[1]
	return (a1 or b1 or c1) and fx
end


Finally, a syntax question. Instead of listing each local boolean on a separate line like I did above, can I write:
local a1, b1, c1, fx = (p[1] == "a") and (p[1] == "b") and (p[1] == "c") and (f() == p[1])

...?

¯¯¯¯¯¯¯¯¯¯¯¯
(3) In xr_conditions.script, almost every function has three arguments (actor, npc, p). But many of the functions do not use more than one, namely p. Would it make any sense to erase the other two unused variables from their argument places, assuming that they are not used?

¯¯¯¯¯¯¯¯¯¯¯¯
(4) Which of these is faster:

(C1)
function xyz()
	if not a then
		return
	end
	if not b then
		return
	end
	return x == y
end


(C2)
function xyz()
	if not a or not b then
		return
	end
	return x == y
end

...?

¯¯¯¯¯¯¯¯¯¯¯¯
(5) And lastly, which of these is faster:

(D1)
function f()
	local a, b, c = x(), y(), z()
	return (a == b) or c
end


(D2)
function f()
	local condition = (x() == y()) or z()
	return condition
end


...?
  09:33:56  23 August 2013
profilee-mailreply Message URLTo the Top
NatVac
Senior Resident
 

 
On forum: 06/15/2007
Messages: 4282
I don't know any really helpful answers for optimization that can't be better stated by the Lua users group (that you originally linked); some links on page 2 of this thread. Another page from that site: http://lua-users.org/wiki/OptimisationCodingTips -- see the Table Access VM code for the Lua 4 version (still applies to Lua 5.1, methinks) to see how the syntax structure is converted into VM code, what I call bytecode.

I can suggest general guidelines (generally good) and opinions (generally ... well, they're opinions).

(1) I don't see any string compares there. I suspect you mean that "==" and "~=" are boolean while "and" and "or" are strings. The first set are relational operators and the second set are logical operators, and they are all eaten at parse time to produce similar low-level bytecode.

First, I think the compiler is smart enough so that generated code tests for "not A" in the same amount of time as it tests for "A". No slowdown contribution here.

But relational operators are apparently slower than logical operators in that the type of each argument is reportedly first checked before they are compared with each other. I say "reportedly" because the bytecode for the "<=" test in the assert example linked above is just JMPLE, so the engine would have to be performing the type check. Are they both numbers? "No" means false. If "yes", then are they the same value? Relational comparisons return boolean results.

The logical tests are different. The statement "return a or b" evaluates a, tests it if false or nil and returns a if not so, else it directly evaluates b and returns it without editorial comment. With "return a and b" you get the same initial test but a false or nil results in that being returned, otherwise b is evaluated and returned. Logical comparisons return false, nil or non-false/non-nil results which could be objects, numbers (unlike C/C++, 0 is NOT false here) or boolean true.

As for the "if" forms of logical comparisons, I'm confident that an evaluation result sets the status flags, so conditional branching requires no additional comparison after the evaluation of b.

Different rules likely apply when a and/or b are constants.

As far as the (asked but not intended) question "which compare faster: booleans or strings" is concerned: I understand that only numbers are directly accessed and everything else is in a hash table. It might depend on where in that table the boolean object or string object is located during the hash lookup -- unless the booleans are treated as numbers (that's what I'd do) in which case the booleans would be faster.

(2) I'd expect B3 to be fastest at run time. B4 creates locals that are only used once, but their creation is based on the same comparison as used in B3, which by default will use short-cut evaluation and return early, without wasting a call to f(), if any prior test fails. B1 and B2 would be tied for 2nd fastest and B4 is last because f() is always called.

Why are B1 and B2 slower than B3? Only because "if x ~= value then return false end" is slower than "return x == value". In the first example the result is evaluated, tested and either branching around or falling through to load the argument 'false' to return. The second example just evaluates a result and returns it.

Re can I write [...]: While you can write that, the answer you want is "no, not valid syntax unless you replace the 'and' keywords with commas". ...Well, it's valid syntax, but not for what you want; only a1 can have a non-nil value.

Tip: Only if a complex expression is evaluated more than once is it likely suitable for a local alias assignment. This includes loops where there can be more than one iteration.

So I'd use a local p1 = p[1] in B3 and adjust the return line to use it.

Now that I've said the above: This is based on my speculation and examination of compiled C/C++, not Lua (other than the VM code in examples on web pages like the link above). Actual testing might say otherwise. In the end, the time spent evaluating all this example code is a minuscule speck against the boulders of nested function calls, loops, global references, table management and dynamically-parsed scripts.

(3) I'm not really understanding the "from their argument places" part of the question. I would not remove "actor, npc" from the "function test(actor, npc, p)" part because xr_logic.script's pick_section_from_condlist() will always pass all three values on the stack (here, CPU registers where possible) to all the xr_condition.script functions (some of which depend on actor and/or npc) even if any or all the parameters are nil. So the table p is the third reference. Removing the unused arguments from the formal parameter list for one would require that you either do it for all or test for a specific exception which defeats the purpose.

You can remove any unused references inside the function body, though.

(4) Which is faster? At run time, neither. (Unrelated: Those function return nil if the first test matches, but that is still considered the logical equivalent of 'false' -- except that any direct comparison of the 'nil' result with 'false' is 'false'.)

(5) D2 is faster, for the same reasons as (2). I'd drop the condition declaration and just return (x() == y()) or z().

Maybe some of this will make sense to you. Others may have useful answers; I hope they will contribute.
  14:27:31  23 August 2013
profilee-mailreply Message URLTo the Top
Decane
Senior Resident
 

 
On forum: 04/04/2007
Messages: 1701
NatVac, thank you. Your replies are always so informative that writing a simple "thank you" seems somehow deficient, and leaves me feeling a little bit guilty that I did not put in more effort to finding these things out on my own.

Be that as it may, you have removed many a layer of confusion from me, even if a few of the peripheral concepts you referred to in the process (e.g. "stack", "CPU register") are foreign to me (for now). So, "спасибо"!
  12:32:26  9 August 2014
profilee-mailreply Message URLTo the Top
Decane
Senior Resident
 

 
On forum: 04/04/2007
 

Message edited by:
Decane
08/11/2014 3:38:37
Messages: 1701
Thread resurrection

I've been reading about LUA optimization from various sources lately, but there are a couple of things I am still unclear about:

(1) Under what circumstances, and to what scope (i.e. function-scope or file-scope) should one localize the pairs function? Suppose e.g. that we have a file like the CS game_relations.script in which more than half of the used functions feature at least one pairs call, and one function even features a nested call (i.e. pairs within pairs). With my current understanding, I would guess that in this case, pairs should be localized at file-scope and then localized again separately in the one function (set_level_faction_community for those who have the file) where there is a nested instance of it. I think this because I think it would be a waste of memory to localize pairs inside a function if it is used just once, but not so if it is used more than once.

(2) Suppose we have a block like this:

	local goodwill = 0
	if new_goodwill == "enemy" then
		goodwill = -8000
	elseif new_goodwill == "friend" then
		goodwill = 12000
	end


I've currently got this 'optimized' as:

local goodwill = (new_goodwill == "enemy" and -8000) or (new_goodwill == "friend" and 12000) or 0


But is the second style of code always (theoretically) faster than the first? Are there any instances where, on purely performance-grounds, an if-then-else block would be preferred to the second style of code (I don't know the proper name for this style so I keep referring to it as 'the second style'.)

(3) I read here (http://springrts.com/wiki/Lua_Performance) that when inserting a function as a parameter/argument to another function, that inputted function should be localized. But in S.T.A.L.K.E.R., there are plenty of instances where a function within file-scope is left unlocalized and is taken as an argument by another function within a function in that same file-scope. For instance, in death_manager.script, there is the global function keep_item, which is taken as an argument by iterate_inventory within the function create_release_item:

self.npc:iterate_inventory(keep_item, self.npc)


What should be done about cases like this? Localize the inputted function itself? Or bind it to a local variable within the function in which it appears, if it appears more than once therein?

(4) Is it performance-enhancing to localize 'self.object' in functions where it appears more than once? E.g. xr_motivator.script is filled with such references. Is 'self.object' treated as a local variable automatically (i.e. is it an implicit parameter of a class function even where the function has no explicit parameters at all, as in the case of e.g. motivator_binder:clear_callbacks)? And the same question applies to 'self.npc' where that is relevant.

(5) In xr_effects/xr_conditions, most or all functions take 'p' as an argument. Now, 'p' is treated as a table, so in the relevant functions, we have references to p[1], p[2], etc. I take it that in these cases, it is good to localize just as we do normally? I.e.:


function stop_cam_effector(actor, npc, p)
	if p then
		if type(p[1]) == "number" then
			if p[1] > 0 then
				level.remove_cam_effector(p[1])
			end
		end
	end
end


... becomes:

function stop_cam_effector(actor, npc, p)
	if p then
		local p1 = p[1]
		if type(p1) == "number" then
			if p1 > 0 then
				level.remove_cam_effector(p1)
			end
		end
	end
end


(I am expecting here that all the checks in the function pass most of the time.)

(6) In some of GSC's scripts, and in some mod scripts, I have seen people localize the key and value .. erm .. variables? .. of the pairs function, like this:

function xxx(actor, npc, p)
	local i, v = 0, 0 -- localized
	for i, v in pairs (p) do
		if v == "a" then
			-- something
		elseif v == "b" then
			-- something
		end
	end
end


What is the point of this?

EDIT:

(7) In the link referenced in (3), it also says (in test 2) that "it isn't faster to localize a class method IN the function call." But surely what is meant here is the for iterator function, and not whatever function in whose scope it falls? Or is it better to localize e.g. math.random at file-scope rather than function-scope? (I don't think it is, but some clarity here would be welcome.)

EDIT 2:

(8) I read here (http://lua-users.org/wiki/OptimisationCodingTips) that pairs is faster than a for-loop for tables wherein the element order doesn't matter. But it was my understanding that for-loops are preferable to pairs wherever their use is possible. So which is it? E.g. in death_manager.script, under function init_drop_settings, there is the table community_list which by default is iterated over with pairs. Should it be replaced by a for i = 1, #community_list -loop instead or not?

EDIT 3:

And finally, a special note for NatVac if/when you ever read this. If you thought SoC's gulag_general.script was poorly optimized with that nice concatenated string declaration at the beginning, check out the CS version of it: http://pastebin.com/Fhvfr8Pu. I'm going to try using the table.concat method to tame that thing since doing it the way you did it in the ZRP crashes the game with an out-of-memory error for the CS version.
  09:32:48  11 August 2014
profilee-mailreply Message URLTo the Top
NatVac
Senior Resident
 

 
On forum: 06/15/2007
Messages: 4282

---QUOTATION---
(1) Under what circumstances, and to what scope (i.e. function-scope or file-scope) should one localize the pairs function? Suppose e.g. that we have a file like the CS game_relations.script in which more than half of the used functions feature at least one pairs call, and one function even features a nested call (i.e. pairs within pairs). With my current understanding, I would guess that in this case, pairs should be localized at file-scope and then localized again separately in the one function (set_level_faction_community for those who have the file) where there is a nested instance of it. I think this because I think it would be a waste of memory to localize pairs inside a function if it is used just once, but not so if it is used more than once.
---END QUOTATION---


(1) I have no solid idea. You could set up a 10000-iteration test loop to see which is faster.

Personally, I'd not bother. Lua is designed for gaming. The pairs function is likely quite optimized, like certain keywords and symbols so that it directly invokes the function via VM code as if it were local. That would make the choice a toss-up, but then there's the extra local declaration and assignment...

As far as nesting is concerned: If it is local at function scope, it's local in the nested loops as well. That should apply to functions. But any locals defined/assigned inside a loop can have unintended impact unless you know how they are used; see section 2.6 in the Lua 5.1 reference.

---QUOTATION---
I've currently got this 'optimized' as:

local goodwill = (new_goodwill == "enemy" and -8000) or (new_goodwill == "friend" and 12000) or 0


But is the second style of code always (theoretically) faster than the first?
---END QUOTATION---


(2) Second approach might be running at same execution speed or maybe even a bit slower. The "and -8000" might be checking for type-sensitive logical 'true' before returning the answer. This is probably buried somewhere in one of the tips pages online. If it is that important to you, try timing both methods in a large loop.

---QUOTATION---
(3) I read here (http://springrts.com/wiki/Lua_Performance) that when inserting a function as a parameter/argument to another function, that inputted function should be localized. But in S.T.A.L.K.E.R., there are plenty of instances where a function within file-scope is left unlocalized and is taken as an argument by another function within a function in that same file-scope. For instance, in death_manager.script, there is the global function keep_item, which is taken as an argument by iterate_inventory within the function create_release_item:

self.npc:iterate_inventory(keep_item, self.npc)


What should be done about cases like this? Localize the inputted function itself? Or bind it to a local variable within the function in which it appears, if it appears more than once therein?
---END QUOTATION---


(3) One of the problems with function localization is the danger of upvalue loss in instances like recursion, I very vaguely recall without attaching my internet memory. While the processing is correctly handled by the interpreter, the JIT optimization is lost. Again, for your specific use, you could test it in a loop. But your example might not be the best one, as the iterate_inventory function is called only once, and its arguments are evaluated only once, so the implicit death_manager.keep_item indexed address is just a simple address within the iterate_inventory() function for however many items processed.

---QUOTATION---
(4) Is it performance-enhancing to localize 'self.object' in functions where it appears more than once? E.g. xr_motivator.script is filled with such references. Is 'self.object' treated as a local variable automatically (i.e. is it an implicit parameter of a class function even where the function has no explicit parameters at all, as in the case of e.g. motivator_binder:clear_callbacks)? And the same question applies to 'self.npc' where that is relevant.
---END QUOTATION---


(4) Class functions are not executed except as instances with the colon operator. All colon operations have an implicit 'self' that is the combination of the structure members the class has plus the virtual table of member function references. You could execute object:name() as object.name(object); internal to the name() member function is the self variable assigned to the implicit object which is always the first although hidden parameter (figuratively speaking; the object 'knows' itself already -- the parameter doesn't need to be passed).

But whether it is passed or calculated, it resolves to the object table, so "self.object" is an index. I don't see this as optimized because the member function does not know at parse time what the instance will be at run time.

Therefore I've been localizing "self." into "self_" as I process through scripts for cases involving multiple uses of a member variable. Quite a few have already been changed in ZRP 1.07 R5EE.

---QUOTATION---
(5) In xr_effects/xr_conditions, most or all functions take 'p' as an argument. Now, 'p' is treated as a table, so in the relevant functions, we have references to p[1], p[2], etc. I take it that in these cases, it is good to localize just as we do normally?
---END QUOTATION---


(5) Yes.

---QUOTATION---
(6) In some of GSC's scripts, and in some mod scripts, I have seen people localize the key and value .. erm .. variables? .. of the pairs function, like this:

function xxx(actor, npc, p)
	local i, v = 0, 0 -- localized
	for i, v in pairs (p) do
		if v == "a" then
			-- something
		elseif v == "b" then
			-- something
		end
	end
end


What is the point of this?
---END QUOTATION---


(6) The point is useless in the example, but the idea is to preserve the values of variables at loop exit that would otherwise be local to the for loop.

---QUOTATION---
(7) In the link referenced in (3), it also says (in test 2) that "it isn't faster to localize a class method IN the function call." But surely what is meant here is the for iterator function, and not whatever function in whose scope it falls? Or is it better to localize e.g. math.random at file-scope rather than function-scope? (I don't think it is, but some clarity here would be welcome.)
---END QUOTATION---


(7) I'll have to see the site to know the specifics. But I wonder if Lua 5.1 reference's section 2.6 has something to do with this.

---QUOTATION---
(8) I read here (http://lua-users.org/wiki/OptimisationCodingTips) that pairs is faster than a for-loop for tables wherein the element order doesn't matter. But it was my understanding that for-loops are preferable to pairs wherever their use is possible. So which is it? E.g. in death_manager.script, under function init_drop_settings, there is the table community_list which by default is iterated over with pairs. Should it be replaced by a for i = 1, #community_list -loop instead or not?
---END QUOTATION---


(8) Pairs is faster for an "unordered" list because it is just a linear sequential fetch. The ordering is first item, next item, next item, etc. Pairs just returns an iterator function, next(). (I may be confused because it is late for me and the for loop is still used for pairs.) Meanwhile a for loop that works on a table without pairs might use an index in the for statement or in the block that could be anything: number, string, even function. This requires additional processing. E.g., sorted hash compares take more time than "Next!". If there's no index processing, it could be faster than with pairs, save for the extra loop overhead like the index increment.

Using your example from death_manager.script: Instead of

item_by_community[v] = {}

you'd have

item_by_community[community_list[i]] = {}

inside the for loop. That involves indexing into community_list each time to get an index. Grabbing the next value, whatever it is? Much faster.

---QUOTATION---
If you thought SoC's gulag_general.script was poorly optimized with that nice concatenated string declaration at the beginning, check out the CS version of it: http://pastebin.com/Fhvfr8Pu. I'm going to try using the table.concat method to tame that thing since doing it the way you did it in the ZRP crashes the game with an out-of-memory error for the CS version.
---END QUOTATION---


You could make it an external file and just read it. That might be faster than table.concat() once it is in memory.
  07:40:30  25 December 2014
profilee-mailreply Message URLTo the Top
Alundaio
Sad Clown
(Resident)

 

 
On forum: 04/05/2010
 

Message edited by:
Alundaio
12/25/2014 8:20:15
Messages: 2230
I would be more concerned about memory usage rather than speed, because Stalker is a 32-bit application. Avoiding bad practices alone is enough optimization for speed.

If you want to optimize Stalker for better RAM usage you need to reuse tables and avoid unnecessary string manipulation. This also has the side effect of boosting speed performance because there is less for the Garbage Collector to clean up.

What the engine needs is better FS functionality or at least a C implementation to create lists through lua. IsStalker, IsMonster, etc. would be better if it was implemented engine side.

Another thing is that all these configuration files are stored into memory as the system ini and then scripts reorganize this data again into lua tables. If you are not manipulating this information it might be better just to read it directly with the FS ini methods every time you need it.

It would be interesting to profile if storing a bool value into a table and reading it, is that much faster than just using system_ini():r_bool() every time you need it. The only reason you would need to store information that already exists elsewhere in memory should only be if you done heavy manipulation to it, like parsing a list from a string or something.

Pretty much every scheme and script binder is guilty of such a thing.



---QUOTATION---
And finally, a special note for NatVac if/when you ever read this. If you thought SoC's gulag_general.script was poorly optimized with that nice concatenated string declaration at the beginning, check out the CS version of it: http://pastebin.com/Fhvfr8Pu. I'm going to try using the table.concat method to tame that thing since doing it the way you did it in the ZRP crashes the game with an out-of-memory error for the CS version.

---END QUOTATION---



GSC's gulag general is the devil. It's a piece of crap. Literally thousands of string concatenations. The cop one is even worse, it's the reason loading times are much longer in cop than in the other games and why your RAM usage skyrockets on load. I save that stuff to file so that it doesn't have to recreate the dynamic gulag every time you load the game. That information is read-only anyway, and never changes. Only a modder would need to clear out this 'cache' to see new gulag changes. In my mod, the game on medium settings loads within a few seconds on subsequent loads (after the first load of a fresh application start) because of it.
  14:00:32  25 December 2014
profilee-mailreply Message URLTo the Top
Decane
Senior Resident
 

 
On forum: 04/04/2007
Messages: 1701
What I ended up doing with the CS gulag_general was to collect all the strings generated by load_job() into a table without manipulating them in the interim, and to then use table.concat() to concatenate them just once when load_ltx() is called. So essentially, something like this:
local ltx

function load_job()
	-- create a table to hold some strings; reference it with the file-local var:
	ltx = {}
	...
	-- fill the table:
	ltx[1] = some_string
	ltx[2] = some_other_string
	ltx[3] = yet_some_other_string
	...
end

function load_ltx()
	local dyn_ltx = (type(ltx) == "table" and table.concat(ltx)) or nil
	-- destroy the reference to the string table so the garbage collector can delete it:
	ltx = nil
	-- return the table-concatenated string if load_job() was executed, else return nil:
	return dyn_ltx
end

Still not as optimized as your solution, Alundaio, but a significant improvement over vanilla nevertheless.
 
Each word should be at least 3 characters long.
Search:    
Search conditions:    - spaces as AND    - spaces as OR   
 
Forum Index » S.T.A.L.K.E.R.: Shadow of Chernobyl Forum » Mod discussion
 

All short dates are in Month-Day-Year format.


 

Copyright © 1995-2020 GSC Game World. All rights reserved.
This site is best viewed in Internet Explorer 4.xx and up and Javascript enabled. Webmaster.
Opera Software products are not supported.
If any problem concerning the site functioning under Opera Software appears apply
to Opera Software technical support service.