Thread Tools Display Modes
07-06-12, 05:40 PM   #1
Haleth
This Space For Rent
 
Haleth's Avatar
Featured
Join Date: Sep 2008
Posts: 1,173
A question on memory allocation/access

I'm wondering a couple of things about lua, some of them specific to WoW (I think), that have been bothering me for a while.

1. Take this example code:

Code:
local function myFunction()
	local name = GetSomeName()
	-- do other things
end
Assume the function is used multiple times. Is this any less efficient than:

Code:
local name
local function myFunction()
	name = GetSomeName()
	-- do other things
end
I assume that in the first case, a new variable is allocated each time the function runs. But is it cleared up as soon as the function finishes, perhaps by an automatic garbage collector? Otherwise, the second example (using the same variable and simply changing its content) would be more efficient, right?

2. A similar example (assume this function is called multiple times as well):

Code:
local function myFunction()
	myFrame:SetScript("OnUpdate", function()
		-- do things
	end)
	-- do other things
	myFrame:SetScript("OnUpdate", nil)
end
Is this less efficient than:

Code:
local function onUpdate()
	-- do things
end

local function myFunction()
	myFrame:SetScript("OnUpdate", onUpdate)
	-- do other things
	myFrame:SetScript("OnUpdate", nil)
end
3. In for-loops, if your end condition is the return value of a function, does the loop run this function on every cycle to compare the current index against it or is the return value stored at the start of the loop? In other words, is this:

Code:
local myNumber = GetSomeNumberFromServer()
for i = 1, myNumber do
	-- do things
end
More efficient than this:

Code:
for i = 1, GetSomeNumberFromServer() do
	-- do things
end
4. I know that importing global variables (such as _G) into your code can speed things up a tiny tiny bit when you use them often. But how is this? If I'm correct, tables are passed by reference. How would creating an other reference which is the same as the existing one make things any faster?

Thanks!

Last edited by Haleth : 07-06-12 at 05:47 PM.
  Reply With Quote
07-06-12, 05:56 PM   #2
Phanx
Cat.
 
Phanx's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2006
Posts: 5,617
You're lucky my browser restores the contents of form fields when I hit Back. Otherwise I'd be pretty pissed to have spent 20 minutes answering your questions only to be told that the thread did not exist after I hit Submit.

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

(1) Declaring a variable inside a function vs. outside

(a)
Code:
local function myFunction()
	local name = GetSomeName()
	-- do other things
end
(b)
Code:
local name
local function myFunction()
	name = GetSomeName()
	-- do other things
end
Short answer:
I'd recommend (a), not (b), but there is no significant difference, so do what you want.

Long answer:
In terms of total memory/CPU usage, these are functionally identical. Both will create a new value (returned by the GetSomeName call) and create a pointer to it named "name" each time the function is run. What is different is when that value will be garbage-collected. In version (a) the value is discarded as soon as the function's execution finishes. In version (b) the value is held in memory until the next time the function is executed, and then discarded when the "name" pointer is redirected to the new value. From a code-readability perspective, I personally think (a) is cleaner and easier to read, too.

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

(2) Defining a function inside another function

(a)
Code:
local function myFunction()
	myFrame:SetScript("OnUpdate", function()
		-- do things
	end)
	-- do other things
end
(b)
Code:
local function onUpdate()
	-- do things
end

local function myFunction()
	myFrame:SetScript("OnUpdate", onUpdate)
	-- do other things
end
Short answer:
Version (a) is bad. Don't do that. Functions are expensive to create. Do (b) instead.

Long answer:
There are a couple of specific situations where (a) is okay.

(i) You are only calling "myFunction" once the whole time your addon is loading and running. If you're calling it more than once, define the OnUpdate script handler function outside of "myFunction". If you're not sure whether you'll call it more than once, do (b) instead.

(ii) You are using "myFunction" to construct a custom function based on other variables that are also defined inside "myFunction". Many addons using AceConfig options tables do this. Here's an example from Grid that creates some dynamic options:

Code:
	for class, name in pairs(classes) do
		local class, name = class, name
		auraOptions.class.args[class] = {
			name = name,
			desc = string.format(L["Show on %s players."], name),
			type = "toggle",
			get = function()
					return GridStatusAuras.db.profile[status][class] ~= false
				end,
			set = function(_, v)
					GridStatusAuras.db.profile[status][class] = v
					GridStatusAuras:UpdateAllUnitAuras()
				end,
		}
	end
If you're not doing either of those things, you should use (a).

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

(3) Function return values in for loop parameters

(a)
Code:
local myNumber = GetSomeNumberFromServer()
for i = 1, myNumber do
	-- do things
end
(b)
Code:
for i = 1, GetSomeNumberFromServer() do
	-- do things
end
No functional difference. "GetSomeNumberFromServer" will only be called once. This is clearly stated in the Lua manual section on for loops.

In general, I'd recommend (b) since it is less code to read. The only reason to do (a) would be if you need to use the same value more than once:

Code:
local n = GetNumRaidMembers()
if n > 0 then
	for i = 1, n do
		-- do things
	end
end
__________________
Retired author of too many addons.
Message me if you're interested in taking over one of my addons.
Don’t message me about addon bugs or programming questions.
  Reply With Quote
07-06-12, 07:09 PM   #3
Haleth
This Space For Rent
 
Haleth's Avatar
Featured
Join Date: Sep 2008
Posts: 1,173
Thanks for those replies, a very interesting read.

I figured it'd be cleaner if I made an own topic rather than leaving a PM quote. I don't know of any browser these days that doesn't cache posts (maybe IE), but sorry.

What do you think about (4)?
  Reply With Quote
07-06-12, 07:56 PM   #4
Phanx
Cat.
 
Phanx's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2006
Posts: 5,617
I sometimes have to use Firefox or Chrome at work, and have lost entered form data on numerous occasions with both. It may depend partly on the type of form, I suppose.

As for #4, my lunch hour was over, and the boss was lurking.

(4) Local copies of global variables

_G is actually a special case in that it's a global variable that points to a table containing all global variables. Accessing it is a global lookup. In order from slowest to fastest:
  1. print(_G.x)
    • global lookup + table lookup every time
  2. print(x)
    • global lookup every time
  3. local _G = _G; print(_G.x)
    • global lookup once
    • local lookup + table lookup every time
  4. local x = _G.x; print(x)
    • global lookup + table lookup once
    • local lookup every time
  5. local x = x; print(x)
    • global lookup once
    • local lookup every time
When you assign a variable as the value of a new variable, what happens depends on what kind of value the original variable had. If it's a string, a number, or a boolean, then it's passed by value: the two variables are not connected, and point to unique objects (basically). If it's a table or a function, then it's passed by reference: both variables point to the same object.

As for the original question -- how a local copy of a global variable is faster -- consider this global table "x":

Code:
x = {
	cat = "sensory overload",
	dog = "fate accepted",
	lizard = "lizard",
}
You can look up values in it:

Code:
print(x["cat"])
This costs 1 global lookup (to find "x") and 1 table lookup (to find the "cat" key in "x").

Code:
local x = x
print(x["cat"])
This costs 1 local lookup (to find "x") and 1 table lookup.

You can't avoid the table lookup (unless you're only ever interested in one value in the table, in which case you should just do "local var = x["cat"]" at the top of your file and use "var" instead of looking up "x["cat"]" every time. What you can do is replace the slow global lookup with a fast local lookup. That's why you should always create local upvalues to global tables/functions if you're going to access them very often.

If you're only calling the function or looking up a value in the table when the player opens an options window, or in response to events like PLAYER_ALIVE or OnClick, you don't really need to bother, because the speed increase is pointless.

However, if you're calling or looking up inside an OnUpdate script, or in a function that's called very frequently such as an event handler for COMBAT_LOG_EVENT_UNFILTERED or UNIT_HEALTH, then all of those infinitesimal speed increases add up, especially in real-life situations where users are running 5, 10, 50, or 100 addons at the same time.
__________________
Retired author of too many addons.
Message me if you're interested in taking over one of my addons.
Don’t message me about addon bugs or programming questions.

Last edited by Phanx : 07-06-12 at 07:59 PM.
  Reply With Quote
07-06-12, 08:22 PM   #5
Haleth
This Space For Rent
 
Haleth's Avatar
Featured
Join Date: Sep 2008
Posts: 1,173
I understand that global 'lookups' are slower than local ones - I'm interested as to why exactly though, considering they point to the same memory location in the case of tables. If I'm guessing correctly, are variables stored in hash tables and is the table for global variables slower to search than the one used for locals?

Thanks for the insightful post btw - will be saving this for future reference.
  Reply With Quote
07-06-12, 10:37 PM   #6
Torhal
A Pyroguard Emberseer
 
Torhal's Avatar
AddOn Author - Click to view addons
Join Date: Aug 2008
Posts: 1,196
The reference to the memory location is located SOMEWHERE within the global table (which also has to be looked up and found!) - you have to find that reference. Sure, it always points to the same memory location but unless you store that location in a variable you're continually looking it up (since Lua has no mechanism for saying "Oh, we already found this - I'll remember where it was!" unless you tell it to do so by using a variable.)
__________________
Whenever someone says "pls" because it's shorter than "please", I say "no" because it's shorter than "yes".

Author of NPCScan and many other AddOns.
  Reply With Quote
07-07-12, 02:39 AM   #7
Phanx
Cat.
 
Phanx's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2006
Posts: 5,617
Think of the global table as a book, and you want to find your favorite passage to read it to your friend. You can either look over the shelves to find the book, and then flip through the book to find the passage -- or you can leave the book open on your coffee table open to the relevant page with the passsage highlighted.
__________________
Retired author of too many addons.
Message me if you're interested in taking over one of my addons.
Don’t message me about addon bugs or programming questions.
  Reply With Quote
07-07-12, 07:03 AM   #8
Ketho
A Pyroguard Emberseer
 
Ketho's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2010
Posts: 1,026
Thanks Phanx for the very useful pointers

(This also made me remember the old WowAce Codings Tips page)
  Reply With Quote

WoWInterface » Developer Discussions » Lua/XML Help » A question on memory allocation/access


Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off