WoWInterface

WoWInterface (https://www.wowinterface.com/forums/index.php)
-   Lua/XML Help (https://www.wowinterface.com/forums/forumdisplay.php?f=16)
-   -   About add-ons optimization (https://www.wowinterface.com/forums/showthread.php?t=47694)

Malsomnus 08-23-13 11:15 AM

About add-ons optimization
 
Hey, just thought I'd ask for some good tips for optimizing my add-ons. Any really obvious things I should do/ tools I should use?

So far what I've been doing is focusing on functions that run frequently (OnUpdate, UNIT_AURA, log events) and removing all calls to global functions/ variables, plus making sure no variables are created in that scope. The only tool I have for it is WowGlobalFinder, and, err, the game's default display of the 3 add-ons that take up the most memory.
What other techniques and add-ons should I know about? I can only assume there are a bunch of tips and tricks for working with strings and tables that I'm not aware of...

Phanx 08-23-13 03:10 PM

In descending order, I'd say these should be your areas of focus:

1. Make sure you are not leaking any globals, and reconsider the necessity of any intentional globals, including the names of frames and other objects. Your main addon object and/or main display frame are good candidates for global names; other objects generally don't need names unless you're using Blizzard templates that require them.

2. Upvalue any globals you're reading in frequently called functions, like OnUpdate, CLEU, UNIT_AURA, etc. Don't bother upvaluing globals you only read infrequently, such as in response to the user changing an option, the player leaving combat, etc.

3. Avoid using OnUpdate scripts for anything other than updating a visual display, such as a statusbar, and move as much of that work as you can to other places, such as event handlers. For example, if you're showing a timer bar for a buff on the player, don't call UnitAura each time your OnUpdate script runs -- store the info you need in variables, and update those variables when UNIT_AURA fires for the player unit instead. Depending on what you're updating, you may be able to offload some/all of the work into C code by using animations.

4. Avoid calling functions when you can, as it's really slow. Some common examples of unnecessary function calls include:

(a) using local x = select(2, ...) instead of local _, x = ...
(b) using string.format("raid%d", i) instead of "raid"..i
(c) using for i, v in ipairs(tbl) do instead of for i = 1, #tbl do local v = tbl[ i ]

5. Avoid creating new tables when you can reuse existing tables. In keeping with #4, don't use wipe unless you actually need to -- for example, if you have a table that's storing info about a buff obtained via UnitAura, you don't need to wipe it, since the keys don't change, and you just overwrite the values.

6. Keep variables limited to the narrowest scope necessary. For example, if a variable is only used inside a loop, define it locally inside the loop, not outside.

7. Call string functions directly, eg. format("banana", "%l", strupper, 1) instead of of ("banana"):format("%l", strupper, 1) -- both incur the cost of a function call, but the second also incurs the costs of getting a metatable and performing a table lookup. If you upvalue format then the direct-call method is enormously faster, but even as a global lookup it's still slightly faster. The only time I'd recommend using the meta-method is when you're chaining multiple string operations; in that case, it's still the slower way to do it, but the increased readability of your code is usually worth it.

If you want more specifics, post your code.

Malsomnus 08-23-13 04:13 PM

No, I don't think you want me to post the thousands of lines of all my add-ons :p

Good tips, I especially like 5 (because I haven't thought of it at all).
However, 4 surprised me a bit. When I was doing my testing, defining a single number variable in OnUpdate had a very visible effect on my performance; calling a function (or twenty) didn't seem to have such an effect.

Phanx 08-23-13 05:07 PM

Static memory usage is almost completely irrelevant, and I have nothing but contempt for whichever Blizzard developer thought it would be a good idea to put "how much memory your addons are using" into the default UI. Most users (and most addon developers) do not understand what those numbers mean, and having them displayed just leads to annoyed developers having to explain over and over that "lower memory usage" is not intrinsically good or a worthwhile goal.

The metrics you should be caring about -- the ones that actually matter -- are increasing memory usage (how quickly your addon's memory usage is growing) and CPU time. Those are the things that actually affect framerates. I use OptionHouse to measure them, but there are plenty of other options, including Broker plugins. Make sure you disable CPU profiling when you're done, as leaving it enabled will slow down everything across the board.

This thread on WowAce from last year includes some benchmark tests I did showing the differences in speed (CPU time) for global lookups vs. upvalues, and different styles of calling string methods:

http://forums.wowace.com/showthread....000#post322000

Also, you should just attach your files, or post a link to your addon download. It doesn't really matter whether it's 100 lines or 100,000 lines; any bad coding habits will be just as easy to scan through and see regardless of the size, and I have a lot of free time at work. :p

Malsomnus 08-23-13 05:20 PM

Don't worry, I get that static memory bit. What I meant was, that display helped me see how SanityCheck was taking up rapidly increasing amounts of memory.
I wouldn't go as far as to flame the very idea of that display... my machine, for one, is old enough for me to care if I have several heavy add-ons loaded, even if they don't use much CPU. I doubt it was intended for this purpose; Blizzard, or any other sane organization, would not put that kind of tool on the main display of their game.

Also, just to make sure we're on the same page: when you say "upvalue" you mean this sort of assignment, right?
Lua Code:
  1. local _G = _G

Anyway, I will definitely get that OptionHouse and see about that old thread, thanks.
And if you're truly bored enough, please have a look at this and this and maybe this. Do not under any circumstances look at the code of MooTrack, it was my first add-on over 50 lines and the code sucks immensely :p

Rainrider 08-23-13 08:47 PM

Quote:

Originally Posted by Phanx (Post 283386)
6. Keep variables limited to the narrowest scope necessary. For example, if a variable is only used inside a loop, define it locally inside the loop, not outside.

I have the following code:
lua Code:
  1. local AddComboPointsBar = function(self, width, height, spacing)
  2.     local comboPoints = {}
  3.     local maxCPoints = MAX_COMBO_POINTS
  4.  
  5.     for i = 1, maxCPoints do
  6.         local cPoint = self.Overlay:CreateTexture("oUF_Rain_ComboPoint_"..i, "OVERLAY")
  7.         cPoint:SetSize((width - maxCPoints * spacing - spacing) / maxCPoints, height)
  8.         cPoint:SetPoint("BOTTOMLEFT", self.Overlay, (i - 1) * cPoint:GetWidth() + i * spacing, 1)
  9.         cPoint:SetTexture(unpack(ns.colors.cpoints[i]))
  10.         comboPoints[i] = cPoint
  11.     end
  12.  
  13.     self.CPoints = comboPoints
  14. end

If we would ignore the fact that I need maxCPoints as an upper bound for the loop for the sake of the example, wouldn't it be still better to define maxCPoints outside the for-loop, despite it is only needed inside? Wouldn't this spare me a global look-up for MAX_COMBO_POINTS and a garbage collection on every loop iteration? I don't know how GC works in loops, but if it collects on every iteration would this be cheaper than having to move one scope up to find maxCPoints?

The other question is whether it is better to use:
Code:

cPoint:SetTexture(ns.colors.cpoints[i][1], ns.colors.cpoints[i][2], ns.colors.cpoints[i][3])
instead of the function call to unpack? I mean is there something like "a function call equals this many table look-ups"?

SDPhantom 08-23-13 09:39 PM

That's actually more specific of a situation, as your definition of maxCPoints is needed in all iterations in the for loop, it's best to keep it where it is instead of reinitializing the same value to the same local multiple times. This can impact performance to do so unnecessarily.

As a rule of thumb, if a variable is needed in all iterations of a loop instead of in each independent iteration, it's best to define it as an upvalue instead of inside the loop.



As far as using unpack() versus manually indexing tables, it depends on how you're indexing the table for each return and at some point, I would think unpack() would start to be more appealing, but no data exists to suggest at what point (if any) this will start to happen.

It can be guessed at that indexing the table will happen in C side when dealing with unpack(), but another question is how would it compare to manual indexing in a single assignment statement? To make the comparison fair, the manual indexing will need to be a single-dimensional table. For example, ns.colors.cpoints[i][1] will use 4 indexing operations for a single value when storing the specific table into a local, then indexing it in your function call will result in only 1 index operation per value.

For example:
Code:

local points=ns.colors.cpoints[i];
cPoint:SetTexture(points[1], points[2], points[3]);


Rainrider 08-24-13 08:18 AM

What is the difference between "needed in all iterations" and "needed in each independent iteration"? Please ignore the use of maxCPoints as the upper bound of the loop as this would be an obvious reason to define it locally in a higher scope as I also need it in each iteration. I thought that the point here would be that I assign a global (MAX_COMBO_POINTS) to maxCPoints, so that I would have a local variable definition, a global look-up and a garbage collect(??) for each loop iteration, where the global look-up is the most expensive part. So would the global look-up be reason enough to define maxCPoints outside the loop despite it being used only in the loop?

Vrul 08-24-13 09:34 AM

Quote:

Originally Posted by Rainrider (Post 283402)
What is the difference between "needed in all iterations" and "needed in each independent iteration"? Please ignore the use of maxCPoints as the upper bound of the loop as this would be an obvious reason to define it locally in a higher scope as I also need it in each iteration. I thought that the point here would be that I assign a global (MAX_COMBO_POINTS) to maxCPoints, so that I would have a local variable definition, a global look-up and a garbage collect(??) for each loop iteration, where the global look-up is the most expensive part. So would the global look-up be reason enough to define maxCPoints outside the loop despite it being used only in the loop?

I think you misunderstood the message Phanx was trying to convey. She meant more along the lines of your cPoint definition inside the loop as being the correct way to do that as some people would define cPoint outside the loop and update it within the loop in the hopes of avoiding non-existent overhead from garbage collection.

A good rule of thumb is that the cost of defining a local vs indexing hits the break even point at 3 look-ups. So, if you need an indexed value 3+ times you are not hurting anything by creating a local reference and using it instead.

That said, you do not need MAX_COMBO_POINTS within the loop at all. The calculation you are doing is static for each iteration so it only needs to be done once prior to the loop. Doing that also lets you get rid of the cPoint:GetWidth() index/call as you will already have that value.
Code:

local AddComboPointsBar = function(self, width, height, spacing)
    local comboPoints, overlay, pointColors = {}, self.Overlay, ns.colors.cpoints
    width = (width - (MAX_COMBO_POINTS - 1) * spacing) / MAX_COMBO_POINTS
    spacing = width + spacing

    for i = 1, MAX_COMBO_POINTS do
        local cPoint = overlay:CreateTexture(nil, "OVERLAY")
        cPoint:SetSize(width, height)
        cPoint:SetPoint("BOTTOMLEFT", (i - 1) * spacing + 1, 1)
        local color = pointColors[i]
        cPoint:SetTexture(color[1], color[2], color[3], color[4])
        comboPoints[i] = cPoint
    end

    self.CPoints = comboPoints
end

Changes:
1. Moved the size calculations to outside the loop
2. Remove the name of the points to avoid clutter in _G
3. Changed the math a bit on sizing to make full use of the width passed
4. Used local references for indexes used in the loop (if you don't use the alpha color parameter remove the color[4] bit)

With that said, I can't imagine that function ever being called enough times to have ever warranted more that a quick pass for optimization. You only really need to scrutinize code for heavily called stuff like OnUpdate scripts and certain event handlers (CLEU, UNIT_AURA, etc...).

Rainrider 08-24-13 11:45 AM

You are totally right, Vrul, thank you for your proposal, I'm going to make the changes. The loop iterates only 5 times and the function is called only once for the target frame in my oUF layout, so yes, it doesn't need that much of optimization. The global names are so I could debug frames and textures positioning with /fstack (had hard time with those because of wrong parenting and overuse of SetFrameLevel and stuff). So yes, it is overall a bad example for the need of optimization, I just wanted to show some real code as it raised questions for me and I could apply the answers elsewhere.

So, assumed I cannot move the calculation involving MAX_COMBO_POINTS, it's better to define it outside the loop so that I could spare the global look-up on every loop iteration. If it would be a local function call, or a local table look-up or a literal, I shall declare the variable for this in the loop. Is this the right way to sum it up?

Vrul 08-24-13 12:57 PM

Quote:

Originally Posted by Rainrider (Post 283479)
So, assumed I cannot move the calculation involving MAX_COMBO_POINTS, it's better to define it outside the loop so that I could spare the global look-up on every loop iteration.

Yes

Quote:

Originally Posted by Rainrider (Post 283479)
If it would be a local function call, or a local table look-up or a literal, I shall declare the variable for this in the loop. Is this the right way to sum it up?

If the variable is dependent on i in some way then define it in the loop, otherwise it would be defined outside as its value will not change during the loop.

SDPhantom 08-24-13 04:39 PM

Quote:

Originally Posted by Rainrider (Post 283402)
What is the difference between "needed in all iterations" and "needed in each independent iteration"?

To explain this simply, every single time code in a loop is run is considered a single iteration of the loop. All locals defined inside a loop only exist in a single iteration independent of all the others that may be run. As an example, a loop that runs code 5 times has 5 iterations whose scope is independent of each other.

myrroddin 08-25-13 01:42 AM

To illustrate:
Lua Code:
  1. for i = 1, 5 do
  2.     -- all iterations
  3.     local var = "dog"
  4.     print(var)
  5. end
  6.  
  7. -- var never needs to be changed
  8. local var = "dog"
  9. for i = 1, 5 do
  10.     print(var)
  11. end
In the first example, you create five times the amount of garbage collected compared to the second.

Malsomnus 08-25-13 04:02 AM

Quote:

Originally Posted by myrroddin (Post 283505)
To illustrate:
Lua Code:
  1. for i = 1, 5 do
  2.     -- all iterations
  3.     local var = "dog"
  4.     print(var)
  5. end
  6.  
  7. -- var never needs to be changed
  8. local var = "dog"
  9. for i = 1, 5 do
  10.     print(var)
  11. end
In the first example, you create five times the amount of garbage collected compared to the second.

This is why I can't quite figure out the general statement "define variables in the lowest scope possible" :confused:

Phanx 08-25-13 06:00 AM

Quote:

Originally Posted by Malsomnus (Post 283507)
This is why I can't quite figure out the general statement "define variables in the lowest scope possible" :confused:

Here's an example:

Code:

local unit
for i = 1, 4 do
    unit = "party"..i
    print(UnitName(unit), "is a", UnitClass(unit))
end

In this example, the "unit" variable is only used inside the "for" loop, and its value is dependent on the iterator's value, so it should only be defined in that scope, not in a higher scope:

Code:

for i = 1, 4 do
    local unit = "party"..i
    print(UnitName(unit), "is a", UnitClass(unit))
end

On a side note, in some cases, the narrowest scope necessary for a variable is no scope at all. I've definitely seen cases of this in addons:

Code:

local x = 4
for i = 1, x do
    local unit = "party"..i
    print(UnitName(unit), "is a", UnitClass(unit))
end

In that case, the variable "x" doesn't need to exist at all; you should just use its value directly in the loop construction, as in the previous example.

Phanx 08-25-13 06:09 AM

Quote:

Originally Posted by SDPhantom (Post 283394)
As far as using unpack() versus manually indexing tables, it depends on how you're indexing the table for each return and at some point, I would think unpack() would start to be more appealing, but no data exists to suggest at what point (if any) this will start to happen.

I did some benchmarking on this a while ago. I don't know whether t[a][b][1], t[a][b][2], t[a][b][3] is faster than unpack(t[a][b]) (though I'd guess it is) but I do know that t[1], t[2], t[3] is significantly faster than unpack(t), so I would strongly advise using SDPhantom's alternative instead of using unpack:

Quote:

Originally Posted by SDPhantom (Post 283394)
Code:

local points=ns.colors.cpoints[i]
cPoint:SetTexture(points[1], points[2], points[3])



Malsomnus 08-25-13 06:42 AM

Phanx: Sorry, what I meant to say was not that I don't understand the statement, but that I don't understand the reasoning behind it, which appears counter-intuitive to me.
If we take your first and second examples, what do you gain by moving the variable into the loop? It seems like you just define 4 variables instead of just 1, which means more CPU time spent on creating the variable and more work for the GC.

Lombra 08-25-13 10:10 AM

I won't comment too much on the performance aspect, but there's probably nothing to gain by doing it that way. It's possible that there's some minuscule performance loss, but it'll never ever be something that anyone will notice, much less worry about. Assigning the value to the variable is the significant portion of the code, not the declaration of the variable.

Anyway, the real reason is readability and consistency. If you have a variable that's only relevant in one iteration of a loop, it has no place outside of that loop. If you have a variable that doesn't change for the entire loop however, it makes sense to define that variable and assign the value only once.

Phanx 08-25-13 09:52 PM

Quote:

Originally Posted by Malsomnus (Post 283541)
Phanx: Sorry, what I meant to say was not that I don't understand the statement, but that I don't understand the reasoning behind it, which appears counter-intuitive to me.
If we take your first and second examples, what do you gain by moving the variable into the loop? It seems like you just define 4 variables instead of just 1, which means more CPU time spent on creating the variable and more work for the GC.

You're creating 4 string values and performing 4 variable assignments either way. It doesn't matter whether you're overwriting an existing variable or not, so you should use scoping appropriately, and keep your code clean.

kurapica.igas 08-26-13 03:57 AM

If an addon has A.lua(at the top of the toc file), B.lua

A.lua:
Code:

local addonName, addon = ...

if not getmetatable(addon) then
        setmetatable(addon, {
                __index = function(self,  key)
                        -- keep vars in the _G to the addon to reduce the access cost
                        local v = _G[key]
                        if v ~= nil then
                                rawset(self, key, v)
                                return rawget(self, key)
                        end
                end,

                __metatable = true,
        })
end

setfenv(1, addon)

-- Standalone code environment, also all files in one addon share the same environment
function myFunc() end

print(_G.myFunc)  -- nil, you get print from _G, _G won't get myFunc from your addon

B.lua
Code:

local addonName, addon = ...

setfenv(1, addon)

myFunc()  -- you can call the function myFunc defined in A.lua


Rainrider 08-26-13 09:51 AM

That's an interesting one, but is it really worth it? You add everything from _G you use in your addon to the addon namespace, even if you only access it once. An upvalue is still faster than the non-global environment and you loose the ability to access _G directly. Or am I wrong about it?

Phanx 08-26-13 03:35 PM

That looks like a giant cluster**** and I would not recommend doing anything like it. If you need functions from one of your addon's files in another, just put them in the namespace table and call them as methods.

Also, that's not really relevant to this thread. If you want to discuss using custom function environments, please take that discussion to its own thread.

kurapica.igas 08-26-13 09:11 PM

This is used to keep your variables not pollute the _G, and gain some code flexibility.Also, the up-value is not as quick as you think.

Here is a test, a() function contains a long calculation, and after it, there are three call types :

1. Define function in the global, call the global function directly.
2. Define up-values, the use the up-values to "speed up" the calculation.
3. Use the standalone environment, do the calculation.

Lua Code:
  1. do
  2.     function a()
  3.         local sum = 0
  4.         for i = 1, 100000 do
  5.             sum = sum + i
  6.         end
  7.     end
  8.  
  9.     oclock = os.clock
  10.  
  11.     do
  12.         -- Normal calls
  13.         function callA()
  14.             collectgarbage()
  15.  
  16.             local startTime = oclock()
  17.  
  18.             for i = 1, 10000 do
  19.                 a()
  20.             end
  21.  
  22.             local finsih = oclock()
  23.  
  24.             print("Normal cost ", finsih - startTime)
  25.         end
  26.  
  27.         callA()
  28.     end
  29.  
  30.     do
  31.         -- so la, loclock are up-value
  32.         local la = a
  33.         local loclock = oclock
  34.  
  35.         function callA()
  36.             collectgarbage()
  37.             local startTime = loclock()
  38.  
  39.             for i = 1, 10000 do
  40.                 la()
  41.             end
  42.  
  43.             local finsih = loclock()
  44.  
  45.             print("Up-value cost ", finsih - startTime)
  46.         end
  47.  
  48.         callA()
  49.     end
  50.  
  51.     do
  52.         local addon = {}
  53.  
  54.         if not getmetatable(addon) then
  55.             setmetatable(addon, {
  56.             __index = function(self,  key)
  57.                 -- keep vars in the _G to the addon to reduce the access cost
  58.                 local v = _G[key]
  59.                 if v ~= nil then
  60.                     rawset(self, key, v)
  61.                     return rawget(self, key)
  62.                 end
  63.             end,
  64.  
  65.             __metatable = true,
  66.             })
  67.         end
  68.  
  69.         setfenv(1, addon)
  70.  
  71.         -- Make sure metatable operations won't happen again when call callA
  72.         a = a
  73.         oclock = oclock
  74.  
  75.         -- so a, oclock are global in the environment, not up-value
  76.         function callA()
  77.             collectgarbage()
  78.             local startTime = oclock()
  79.  
  80.             for i = 1, 10000 do
  81.                 a()
  82.             end
  83.  
  84.             local finsih = oclock()
  85.            
  86.             print("Standalone environment cost ", finsih - startTime)
  87.         end
  88.  
  89.         callA()
  90.     end
  91. end

The result may not be exactly because there are too many things should cost the cpu in the same time, but you can see a lot in it, I run these code three times in lua 5.1.2 on mac shell:

Quote:

Normal cost 16.129601
Up-value cost 16.357202
Standalone environment cost 16.308925
Quote:

Normal cost 16.197299
Up-value cost 16.478886
Standalone environment cost 16.390823
Quote:

Normal cost 16.203371
Up-value cost 16.468056
Standalone environment cost 16.404645
So the up-value part is the slowest.

In an addon, you don't need access all things in the _G, only what you need is saved to your addon, and if you want access something once, you always can do it like :

print(_G.CAT_FORM)

Just access if from _G table, you won't save the CAT_FORM in your addon.

kurapica.igas 08-26-13 11:11 PM

Oh, one more thing, about the memory usage, the up-values cost much more if you use the local vars in many functions in your add-on, Here is a little test :

Lua Code:
  1. do
  2.     clear = function() collectgarbage() return collectgarbage("count") end
  3.  
  4.     for i = 1, 10 do
  5.         _G["a"..i] = function() end
  6.     end
  7.  
  8.     memStep0 = clear()
  9.  
  10.     do
  11.         local a1, a2, a3, a4, a5, a6, a7, a8, a9, a10 = a1, a2, a3, a4, a5, a6, a7, a8, a9, a10
  12.  
  13.         for i = 1, 100 do
  14.             _G["A"..i] = function()
  15.                 a1() a2() a3() a4() a5() a6() a7() a8() a9() a10()
  16.             end
  17.  
  18.             _G["A"..i]()
  19.         end
  20.     end
  21.  
  22.     memStep1 = clear()
  23.  
  24.     do
  25.         local addon = {}
  26.         addon._Addon = addon  -- the addon itself
  27.  
  28.         if not getmetatable(addon) then
  29.             setmetatable(addon, {
  30.             __index = function(self,  key)
  31.                 -- keep vars in the _G to the addon to reduce the access cost
  32.                 local v = _G[key]
  33.                 if v ~= nil then
  34.                     rawset(self, key, v)
  35.                     return rawget(self, key)
  36.                 end
  37.             end,
  38.  
  39.             __metatable = true,
  40.             })
  41.         end
  42.  
  43.         setfenv(1, addon)
  44.  
  45.         for i = 1, 100 do
  46.             _Addon["A"..i] = function()
  47.                 a1() a2() a3() a4() a5() a6() a7() a8() a9() a10()
  48.             end
  49.  
  50.             _Addon["A"..i]()
  51.         end
  52.     end
  53.  
  54.     memStep2 = clear()
  55.  
  56.     print("Cost for up-values", memStep1 - memStep0)
  57.     print("Cost for standalone environment", memStep2 - memStep1)
  58. end

So, here is the result:

Quote:

Cost for up-values 22.275390625
Cost for standalone environment 9.9453125

SDPhantom 08-26-13 11:42 PM

Quote:

Originally Posted by Phanx (Post 283562)
Quote:

Originally Posted by Malsomnus (Post 283541)
Phanx: Sorry, what I meant to say was not that I don't understand the statement, but that I don't understand the reasoning behind it, which appears counter-intuitive to me.
If we take your first and second examples, what do you gain by moving the variable into the loop? It seems like you just define 4 variables instead of just 1, which means more CPU time spent on creating the variable and more work for the GC.

You're creating 4 string values and performing 4 variable assignments either way. It doesn't matter whether you're overwriting an existing variable or not, so you should use scoping appropriately, and keep your code clean.

Explaining The Reason:
The point of keeping variables in their relevant scope is akin to only allocating memory when you need to use it and proper cleanup when you don't in other languages. All this is done behind the scenes with Lua's garbage collector, but it's still a good practice to follow nonetheless. These so-called "good programming practices" make it easy and simple to program reliable code regardless of which language you're using.




The code posted by Rainrider and Phanx are both correct, although the situations of the variables in question by each are completely different.

Malsomnus 08-27-13 04:15 AM

Quote:

Originally Posted by SDPhantom (Post 283588)
Explaining The Reason:
The point of keeping variables in their relevant scope is akin to only allocating memory when you need to use it and proper cleanup when you don't in other languages. All this is done behind the scenes with Lua's garbage collector, but it's still a good practice to follow nonetheless. These so-called "good programming practices" make it easy and simple to program reliable code regardless of which language you're using.

The code posted by Rainrider and Phanx are both correct, although the situations of the variables in question by each are completely different.

The only reason I was going on about it was because in my testing, merely defining a single variable in the local scope of an OnUpdate function slowed my game's performance to a crawl. Moving that variable outside that scope and reusing it immediately solved the problem.
While it's obvious that, code-wise, a variable should be defined in the smallest scope possible, I think that that is not a tip that belongs in a discussion about optimization :p

Resike 08-27-13 04:28 AM

So it is worth to upvalue globals now or not?

Lombra 08-27-13 09:49 AM

Quote:

Originally Posted by Malsomnus (Post 283591)
The only reason I was going on about it was because in my testing, merely defining a single variable in the local scope of an OnUpdate function slowed my game's performance to a crawl. Moving that variable outside that scope and reusing it immediately solved the problem.

Can you paste the code of this test?

Phanx 08-27-13 04:40 PM

Quote:

Originally Posted by Resike (Post 283592)
So it is worth to upvalue globals now or not?

There is no blanket answer to that question. If you're accessing a global in an OnUpdate script, CLEU handler, or other code path that runs frequently (especially in combat) then yes, you should upvalue it.

If you're accessing the global in response to the user checking a box in your options panel, or in response to an event like PLAYER_LOGIN or PLAYER_REGEN_DISABLED that doesn't fire very often, then while you will technically see a speed improvement by upvaluing it, in practical terms there is no value in doing so, so I'd recommend you keep your code tidy and not clutter it up with a bunch of practically useless upvalues.

Phanx 08-27-13 04:41 PM

Quote:

Originally Posted by Malsomnus (Post 283591)
The only reason I was going on about it was because in my testing, merely defining a single variable in the local scope of an OnUpdate function slowed my game's performance to a crawl. Moving that variable outside that scope and reusing it immediately solved the problem.

Was that variable's value a table or function? If so, that is your problem, and it has nothing to do with where the variable is defined.

But, without seeing the actual code in question, it's really pointless to talk about it.

kurapica.igas 08-27-13 08:30 PM

Well, if you need a var in the OnUpdate or something happened frequently, I suggest you may try the coroutine. Also a test example :

1. First part is using a local var 'sum' to contains the sum result. And then we call it 10000 times.
2. Second part is using a coroutine to keep everything, we also can it 10000 times.

Lua Code:
  1. do
  2.     oclock = os.clock
  3.  
  4.     do
  5.         local sum = 0
  6.  
  7.         function Sum(num)
  8.             for i = 1, num do
  9.                 sum = sum + i
  10.             end
  11.         end
  12.  
  13.         function callSum()
  14.             collectgarbage()
  15.             local startTime = oclock()
  16.  
  17.             for i = 1, 10000 do
  18.                 Sum(i)
  19.             end
  20.  
  21.             local finsih = oclock()
  22.  
  23.             print("Result is", sum)
  24.             print("Up-value cost ", finsih - startTime)
  25.         end
  26.  
  27.         callSum()
  28.     end
  29.  
  30.     print("----------------------------")
  31.  
  32.     do
  33.         create = coroutine.create
  34.         resume = coroutine.resume
  35.         running = coroutine.running
  36.         status = coroutine.status
  37.         wrap = coroutine.wrap
  38.         yield = coroutine.yield
  39.  
  40.         function Sum(num)
  41.             local sum = 0
  42.  
  43.             while num do
  44.                 for i = 1, num do
  45.                     sum = sum + i
  46.                 end
  47.                 num = yield()
  48.             end
  49.  
  50.             print("Result is", sum)
  51.         end
  52.  
  53.         function callSum()
  54.             collectgarbage()
  55.             local startTime = oclock()
  56.             local thread = create(Sum)
  57.  
  58.             for i = 1, 10000 do
  59.                 resume(thread, i)
  60.             end
  61.  
  62.             resume(thread)
  63.  
  64.             local finsih = oclock()
  65.  
  66.             print("Thread cost ", finsih - startTime)
  67.         end
  68.  
  69.         callSum()
  70.     end
  71. end

The result is :

Quote:

Result is 166716670000
Up-value cost 1.406231
----------------------------
Result is 166716670000
Thread cost 0.853961
Normally, like an OnUpdate handler, you can do it like :

Lua Code:
  1. local thread = create(function(...)
  2.     local var1, var2
  3.  
  4.     while true do
  5.         -- Handle your code
  6.         yield()
  7.     end
  8. end)
  9.  
  10. frame:SetScript("OnUpdate", function(self)
  11.     resume(thread)
  12. end)

So, when your frame is visible, the thread will be called again and again, when not, the thread will be stop until the frame is shown again.

But, if your handle code is tiny, just use upvalue, in the previous example, if you change the code
Lua Code:
  1. for i = 1, num do
  2.     sum = sum + i
  3. end
To
Lua Code:
  1. sum = sum + num

The result should be

Quote:

Result is 50005000
Up-value cost 0.0021880000000003
----------------------------
Result is 50005000
Thread cost 0.0052459999999996
So, the choose is based on your code, and the coroutine is more complex than the upvalue, if you do it wrong, it may crash your game.

Phanx 08-27-13 11:19 PM

Please stop derailing this "basic tips for optimization" thread with your posts about coroutines and custom function environments. If you want to post tutorials on those subjects, please do it a new thread.

ballagarba 08-28-13 01:08 AM

I find his stuff very interesting to be honest.

1. Don't do premature optimization.
2. Don't sacrifice readability for negligible optimizations.

That being said, here's a PDF on performance tips for Lua written by its lead architect.

http://www.lua.org/gems/sample.pdf

kurapica.igas 08-28-13 04:32 AM

Quote:

Originally Posted by ballagarba (Post 283616)
I find his stuff very interesting to be honest.

1. Don't do premature optimization.
2. Don't sacrifice readability for negligible optimizations.

That being said, here's a PDF on performance tips for Lua written by its lead architect.

http://www.lua.org/gems/sample.pdf

Thanks for the book.

I also take back the word 'The upvalue is the slowest', I'm confused about the result too, redo the test several times today, only 1 time the upvalue is slower than others, the diff between them is little.

I prefer the custom environment just because after some time, the custom environment will store all things that the addon needed, from the point, the custom environment table will be stable compares to the _G.

Malsomnus 08-28-13 10:18 AM

Quote:

Originally Posted by ballagarba (Post 283616)
1. Don't do premature optimization.
2. Don't sacrifice readability for negligible optimizations.

Yep. Every programmer should tattoo this somewhere, just to be on the safe side :p

Kagura 08-29-13 03:49 AM

On the subject to declaring variables to their most confining scope next should be noted:
Lua Code:
  1. local a
  2. for i=1, 5 do
  3.     a = i
  4. end
Code:

-- Compiles into

main <test.lua:0,0> (7 instructions, 28 bytes at 0x80049128)
0+ params, 5 slots, 0 upvalues, 5 locals, 2 constants, 0 functions
        1      [3]    LOADK          1 -1    ; 1
        2      [3]    LOADK          2 -2    ; 5
        3      [3]    LOADK          3 -1    ; 1
        4      [3]    FORPREP        1 1    ; to 6
        5      [4]    MOVE            0 4
        6      [3]    FORLOOP        1 -2    ; to 5
        7      [5]    RETURN          0 1
]]


Lua Code:
  1. for i=1, 5 do
  2.     local a
  3.     a = i
  4. end

Code:

-- Compiles into

main <test.lua:0,0> (8 instructions, 32 bytes at 0x80049128)
0+ params, 5 slots, 0 upvalues, 5 locals, 2 constants, 0 functions
        1      [2]    LOADK          0 -1    ; 1
        2      [2]    LOADK          1 -2    ; 5
        3      [2]    LOADK          2 -1    ; 1
        4      [2]    FORPREP        0 2    ; to 7
        5      [3]    LOADNIL        4 4
        6      [4]    MOVE            4 3
        7      [2]    FORLOOP        0 -3    ; to 5
        8      [5]    RETURN          0 1
]]

and

Lua Code:
  1. for i=1, 5 do
  2.     local a = i
  3. end

Code:

-- Compiles into

main <test.lua:0,0> (7 instructions, 28 bytes at 0x80049128)
0+ params, 5 slots, 0 upvalues, 5 locals, 2 constants, 0 functions
        1      [2]    LOADK          0 -1    ; 1
        2      [2]    LOADK          1 -2    ; 5
        3      [2]    LOADK          2 -1    ; 1
        4      [2]    FORPREP        0 1    ; to 6
        5      [3]    MOVE            4 3
        6      [2]    FORLOOP        0 -2    ; to 5
        7      [4]    RETURN          0 1

As you can guess, the second is a bit slower, but it should also be noted that the gain from this is only going to be noticable for large amount of variable declarations.

While doing a large amount of variable declarations, next should also be noted (I'll leave the compiled versions out since I don't want to pollute the thread):

Lua Code:
  1. function UnitAura(unitId, index)
  2. end
  3.  
  4. local startTime, endTime
  5. startTime = os.clock()
  6. for j = 1, 1000000 do
  7.     local name, rank, icon, count, dispelType, duration, expires, caster, isStealable, shouldConsolidate, spellID, canApplyAura, isBossDebuff, value1, value2, value3
  8.     for i = 1, 40 do
  9.         name, rank, icon, count, dispelType, duration, expires, caster, isStealable, shouldConsolidate, spellID, canApplyAura, isBossDebuff, value1, value2, value3 = UnitAura("player", i)
  10.     end
  11. end
  12. endTime = os.clock()
  13.  
  14. print(endTime-startTime)
  15.  
  16. startTime = os.clock()
  17. for j = 1, 1000000 do
  18.     for i = 1, 40 do
  19.         local name, rank, icon, count, dispelType, duration, expires, caster, isStealable, shouldConsolidate, spellID, canApplyAura, isBossDebuff, value1, value2, value3 = UnitAura("player", i)
  20.     end
  21. end
  22. endTime = os.clock()
  23.  
  24. print(endTime-startTime)
  25.  
  26. startTime = os.clock()
  27. for j = 1, 1000000 do
  28.     for i = 1, 40 do
  29.         local name, rank, icon, count, dispelType, duration, expires, caster, isStealable, shouldConsolidate, spellID, canApplyAura, isBossDebuff, value1, value2, value3
  30.         name, rank, icon, count, dispelType, duration, expires, caster, isStealable, shouldConsolidate, spellID, canApplyAura, isBossDebuff, value1, value2, value3 = UnitAura("player", i)
  31.     end
  32. end
  33. endTime = os.clock()
  34.  
  35. print(endTime-startTime)
  36.  
  37. --- Results
  38. $ lua test.lua
  39. 5.859
  40. 2.562
  41. 6.375

If you really want to optimize your addon, you need to look at compiled code and understand how function calls/lua stack works.

SDPhantom 08-29-13 03:52 PM

This is still deviating from the point Phanx and I were making. Phanx is stating as a general rule that locals should stay in the tightest scope possible. My posts state that like most rules, there are some exceptions over the argument whether locals should stay in or out of loops.

Among these is the point that when dealing with constants or CPU-intensive calculations that don't change in the loop, you're best left upvaluing them instead of having the loop reinitialize the variable with the same value multiple times.

Phanx 08-29-13 05:38 PM

Yep, you guys are definitely missing the point. This is a thread about simple, general tips that don't require a lot of coding experience or deep knowledge of how Lua works internally -- it's not meant to cover every possible scenario, and it's not meant to delve into complicated schemes for extreme optimization of every single CPU cycle. I'm tired of asking, but if you want to provide lengthy benchmarking results and tutorials covering every possible exception to the rule, or extreme micro-optimization, please start your own threads for that stuff.

Resike 09-04-13 09:21 AM

Quote:

Originally Posted by Kagura (Post 283637)
Lua Code:
  1. function UnitAura(unitId, index)
  2. end
  3.  
  4. local startTime, endTime
  5. startTime = os.clock()
  6. for j = 1, 1000000 do
  7.     local name, rank, icon, count, dispelType, duration, expires, caster, isStealable, shouldConsolidate, spellID, canApplyAura, isBossDebuff, value1, value2, value3
  8.     for i = 1, 40 do
  9.         name, rank, icon, count, dispelType, duration, expires, caster, isStealable, shouldConsolidate, spellID, canApplyAura, isBossDebuff, value1, value2, value3 = UnitAura("player", i)
  10.     end
  11. end
  12. endTime = os.clock()
  13.  
  14. print(endTime-startTime)
  15.  
  16. startTime = os.clock()
  17. for j = 1, 1000000 do
  18.     for i = 1, 40 do
  19.         local name, rank, icon, count, dispelType, duration, expires, caster, isStealable, shouldConsolidate, spellID, canApplyAura, isBossDebuff, value1, value2, value3 = UnitAura("player", i)
  20.     end
  21. end
  22. endTime = os.clock()
  23.  
  24. print(endTime-startTime)
  25.  
  26. startTime = os.clock()
  27. for j = 1, 1000000 do
  28.     for i = 1, 40 do
  29.         local name, rank, icon, count, dispelType, duration, expires, caster, isStealable, shouldConsolidate, spellID, canApplyAura, isBossDebuff, value1, value2, value3
  30.         name, rank, icon, count, dispelType, duration, expires, caster, isStealable, shouldConsolidate, spellID, canApplyAura, isBossDebuff, value1, value2, value3 = UnitAura("player", i)
  31.     end
  32. end
  33. endTime = os.clock()
  34.  
  35. print(endTime-startTime)
  36.  
  37. --- Results
  38. $ lua test.lua
  39. 5.859
  40. 2.562
  41. 6.375

As soon as you going to start to use thoose varliabes in the second version, it's going to be as slow as the third one. The extra time for the first version comes from the lookup time and not beacuse it's not in the tightest scope.

Kagura 09-05-13 07:20 AM

Quote:

Originally Posted by Resike (Post 284146)
As soon as you going to start to use thoose varliabes in the second version, it's going to be as slow as the third one. The extra time for the first version comes from the lookup time and not beacuse it's not in the tightest scope.

Actually it won't be slower. But Phanx is right, I think the thread is derailing too much from "simple optimization" to getting every bit of performance you can possibly get.

p.s. If you are really curious, the test results with your suggestion is : here

Resike 09-05-13 10:41 AM

Quote:

Originally Posted by Kagura (Post 284161)
Actually it won't be slower. But Phanx is right, I think the thread is derailing too much from "simple optimization" to getting every bit of performance you can possibly get.

p.s. If you are really curious, the test results with your suggestion is : here

Yeah but in this test you added another bunch of variable to lookup for the 1st and the 3rd version again. If you want them to test properly then you should have 1 line to set up the variables and 1 lookup line (Where you upvalue thoose variables). Which is already is in the 1st and the 3rd version.

Lua Code:
  1. function UnitAura(unitId, index)
  2. end
  3.  
  4. local time = 1000000
  5.  
  6. time = debugprofilestop()
  7. for j = 1, 1000000 do
  8.         local name, rank, icon, count, dispelType, duration, expires, caster, isStealable, shouldConsolidate, spellID, canApplyAura, isBossDebuff, value1, value2, value3
  9.         for i = 1, 40 do
  10.                 name, rank, icon, count, dispelType, duration, expires, caster, isStealable, shouldConsolidate, spellID, canApplyAura, isBossDebuff, value1, value2, value3 = UnitAura("player", i)
  11.         end
  12. end
  13. print(format("%.0f ms", debugprofilestop() - time))
  14.  
  15. time = debugprofilestop()
  16. for j = 1, 1000000 do
  17.         for i = 1, 40 do
  18.                 local name, rank, icon, count, dispelType, duration, expires, caster, isStealable, shouldConsolidate, spellID, canApplyAura, isBossDebuff, value1, value2, value3 = UnitAura("player", i)
  19.                 name = 1
  20.                 rank = 1
  21.                 icon = 1
  22.                 count = 1
  23.                 dispelType = 1
  24.                 duration = 1
  25.                 expires = 1
  26.                 caster = 1
  27.                 isStealable = 1
  28.                 shouldConsolidate = 1
  29.                 spellID = 1
  30.                 canApplyAura = 1
  31.                 isBossDebuff = 1
  32.                 value1 = 1
  33.                 value2 = 1
  34.                 value3 = 1
  35.         end
  36. end
  37. print(format("%.0f ms", debugprofilestop() - time))
  38.  
  39. time = debugprofilestop()
  40. for j = 1, 1000000 do
  41.         for i = 1, 40 do
  42.                 local name, rank, icon, count, dispelType, duration, expires, caster, isStealable, shouldConsolidate, spellID, canApplyAura, isBossDebuff, value1, value2, value3
  43.                 name, rank, icon, count, dispelType, duration, expires, caster, isStealable, shouldConsolidate, spellID, canApplyAura, isBossDebuff, value1, value2, value3 = UnitAura("player", i)
  44.         end
  45. end
  46. print(format("%.0f ms", debugprofilestop() - time))

Tested it ingame:
7980 ms
8073 ms
8664 ms

Also it's pretty fast with integers only, much slower with strings/other variables. ~8500 ms

~600 ms gain on a million calls.

aallkkaa 01-09-18 11:29 PM

Quote:

Originally Posted by Phanx (Post 283386)
2. Upvalue any globals you're reading in frequently called functions, like OnUpdate, CLEU, UNIT_AURA, etc. Don't bother upvaluing globals you only read infrequently, such as in response to the user changing an option, the player leaving combat, etc.

Regarding the OnUpdate functions, I presume you mean globals you would call each time OnUpdate is run, right?
I mean, in the example bellow (from a non publshed little addon of mine), the function AlkaT_SpORCo_update() is called only once every 0.25 seconds, and it is in this function that I call two global functions. If I understood correctly, upvalueing these two functions would bring no benefit at all, right?
Lua Code:
  1. local AlkaT_UpdateInterval = 0.25;
  2. local AlkaT_timeSinceLastUpdate = 0;
  3. AlkaT_BMapTexts:SetScript("OnUpdate", function(self, AlkaT_elapsed)
  4.     AlkaT_timeSinceLastUpdate = AlkaT_timeSinceLastUpdate + AlkaT_elapsed;
  5.     if (AlkaT_timeSinceLastUpdate > AlkaT_UpdateInterval) then
  6.         AlkaT_SpORCo_update("player");
  7.         AlkaT_timeSinceLastUpdate = 0;
  8.     end
  9. end);

Seerah 01-10-18 02:13 PM

The point of upvaluing global functions called frequently is to reduce the amount of time it takes to get a return from those functions. A local is much faster to access than a global is.

Your script calls your AlkaT_SpORCo_update function and has to wait for it to finish before moving on to the next line in the script (AlkaT_timeSinceLastUpdate = 0). The faster it is completed, the faster it can move on. Your AlkaT_SpORCo_update function has to wait on the global functions that it calls before moving on in its code. And so on. Lua is single-threaded.

aallkkaa 01-10-18 07:24 PM

Thank you for your reply, Seerah.
I was however already aware of what you said. Funny though, it made the thoughts clearer in my mind - I don't know how you did that, but thank you! :cool:

So, I guess my real question should rather be: What would be the cons, if any, of upvalueing those global functions?
I now recall that, on the thread which brought me to this one, there was a link to another thread, specifically on upvalueing, so I guess I'll have a look at that when I get some time. If I still have questions about this snippet afterwards, I'll post them back here. ;)

P.s.: Your comment about the single-threaded nature of Lua wasn't exactly new to me, but, honestly, I had presumed it rather than properly looked into it. I'm not in the ITC industry (never was) but codeing has been a hobby of mine since I was about 15 (back then mainly in plain C), over 25 years now. And to be honest, the multi-threading technology was never something I fully understood (nor dived much into), codeing-wise.
The way I see it, even something like Lua coroutines is only a sequential pause-this-thread, then jump-to-another, then back-to-the-first (at the simplest case of only two "coroutines").
I can understand event-driven programming and have a shallow understanding of CPU IRQs as well, so I have a vague idea on how two (or more) cores CPUs work together.
But again, it's only a vague understanding of it, and as far I remember, I've never actually done any programming that wasn't, in my mind, "single-threaded".
I guess I could have started a new topic about this but I fear I would quickly loose the "thread" of it in the ensueing discussion, so I'm just dropping this here now. :D

myrroddin 01-10-18 10:39 PM

This is a huge oversimplification, and is not 100% accurate, but it gets the point across:
Lua Code:
  1. local some_boy = "Harry"
  2. local some_girl = "Sally"
In Lua, since it is single threaded, it executes line 1, then line 2. A multithreaded language would execute both lines simultaneously, because at the bare minimum, multiple threads by nature start with two threads or executions per compute cycle.

If a language supported up to the new 36 threads from AMD and Intel, you could send 36 commands at the same time.

As to your question about cons, the two biggest about upvaluing are:
  • Initial startup time is a bit slower as your code has to look up the globals then store them in memory to variables. Unless you upvalue a crazy amount of globals, you won't notice this slow down on modern computers.
  • Your app, program, script, addon, whatever consumes more RAM because it is storing more things in memory. Again, with modern computers this isn't a big deal, but a player with 8GB of RAM while playing WoW doesn't have overhead to spare if he/she installs a hundred or so addons.
Not listed is a very minor con, in that unless you are looking up a global often, ie// during OnUpdate or rapid event firings, you don't gain much, if anything.

Seerah 01-11-18 01:11 PM

(Someone with 8GB of RAM would be fine ;) )

semlar 01-11-18 02:12 PM

Upvaluing globals is a micro-optimization that you shouldn't bother actively thinking about when you're coding; looking up a variable is very fast and any addon that would see an appreciable difference from storing a local copy to access is probably not going to be saved by this.

Write your program to do what you want it to do, then if something is impacting performance, clean it up. Anything that isn't an obvious performance gain should be deferred. Premature optimization can introduce unexpected behavior that can end up wasting a lot of your time tracking down and debugging.

Creating a local copy of a variable comes with the unintended (or intentional) consequence of preventing it from being replaced or hooked by other addons later in the loading process.

For example, let's say you want to monitor when another addon modifies a cvar so you know not to touch that cvar in the future, so you hook the global function SetCVar. When an addon calls SetCVar it also runs your function and everyone is happy.

But what if this addon creates a local reference to SetCVar before you hook it? Now when it calls SetCVar your function hook doesn't run, and everyone is sad because your addon ends up overwriting their addon's settings because it didn't see the change it made.

Additionally, lua actually has an internal limit of 60 upvalues. This isn't something you normally have to worry about, but you could conceivably run into it by redeclaring a large number of globals as local variables in your addon, and then trying to access them all in a function.

Resike 01-11-18 06:38 PM

Quote:

Originally Posted by semlar (Post 326456)
Upvaluing globals is a micro-optimization that you shouldn't bother actively thinking about when you're coding; looking up a variable is very fast and any addon that would see an appreciable difference from storing a local copy to access is probably not going to be saved by this.

Write your program to do what you want it to do, then if something is impacting performance, clean it up. Anything that isn't an obvious performance gain should be deferred. Premature optimization can introduce unexpected behavior that can end up wasting a lot of your time tracking down and debugging.

Creating a local copy of a variable comes with the unintended (or intentional) consequence of preventing it from being replaced or hooked by other addons later in the loading process.

For example, let's say you want to monitor when another addon modifies a cvar so you know not to touch that cvar in the future, so you hook the global function SetCVar. When an addon calls SetCVar it also runs your function and everyone is happy.

But what if this addon creates a local reference to SetCVar before you hook it? Now when it calls SetCVar your function hook doesn't run, and everyone is sad because your addon ends up overwriting their addon's settings because it didn't see the change it made.

Additionally, lua actually has an internal limit of 60 upvalues. This isn't something you normally have to worry about, but you could conceivably run into it by redeclaring a large number of globals as local variables in your addon, and then trying to access them all in a function.

I doubt upvaluing could cause this, since you are only creating a pointer to the same function that lives in a much smaller scope then the global one, nothing else.
And since both calls also goes up to another C function the time execute it would be a minuscule bit faster call, for the memory size of the function pointer.

aallkkaa 01-14-18 12:29 AM

Thanks for all your replies.

@ myrroddin
Very nice explanation ons single-threaded vs multi-threaded languages. That was pretty much what I thought, it's the "huge oversimplification, and is not 100% accurate" part I'm sure would loose me... :D

On the cons of upvalueing, again, nice explanation, and makes perfect sense. But I was a bit surprised that 8GB would give so little leeway, having WoW running (mostly) alnoe.
Seerah seems to have a different view on this, so... :confused:


@ aemiar
Those are very interesting points.
Regarding that little addon of mine (prints player coordinates and speed on BattlefieldMinimap), it's pretty much done. I actually picked it up as example fpr its simplicity. But thanks for the hint, it is a good coding principle, I think.
And you pretty much answered it in regards to my original question: upvalueing, in my case, where the globals are called only once per 0.25 seconds isn't worth it; might be if they were called every time OnUpdate was called (only to check how long since last call presently). But, as it is, no point.
Quote:

Originally Posted by semlar (Post 326456)
Additionally, lua actually has an internal limit of 60 upvalues.

I preseume this is per addon ...?


@ Resike
Quote:

Originally Posted by Resike (Post 326459)
I doubt upvaluing could cause this, since you are only creating a pointer to the same function that lives in a much smaller scope then the global one, nothing else.
And since both calls also goes up to another C function the time execute it would be a minuscule bit faster call, for the memory size of the function pointer.

On this I have a couple questions.
I thought it was (NOT actual code):
Lua Code:
  1. global function DoSomething()
  2. call C function;
  3. end
and then, doing local DoSomething = _G[DoSomething] would bassically do:

A)
Lua Code:
  1. local function DoSomething()
  2. call C function;
  3. end

or...
B)

Lua Code:
  1. local function DoSomething()
  2. _G[DoSomething]();
  3. end

Which way does it work like?

Lombra 01-14-18 07:52 AM

A, I guess. The function never changes, you just get a new reference to it.

MunkDev 01-14-18 08:17 AM

Quote:

Originally Posted by aallkkaa (Post 326487)
Which way does it work like?

It works more closely to A, but that's not the whole story. Look at this image:


Imagine that a is a reference to the function you want to upvalue. By accessing the function through a variable, you're essentially just retrieving the pointer to the memory where the function is stored (b). When you upvalue a function, you're copying the contents of a, that is to say the memory address to your function, but not the actual function. The function you end up calling is the same, whether global or local in your scope.

Resike 01-14-18 10:17 AM

Quote:

Originally Posted by MunkDev (Post 326491)
It works more closely to A, but that's not the whole story. Look at this image:


Imagine that a is a reference to the function you want to upvalue. By accessing the function through a variable, you're essentially just retrieving the pointer to the memory where the function is stored (b). When you upvalue a function, you're copying the contents of a, that is to say the memory address to your function, but not the actual function. The function you end up calling is the same, whether global or local in your scope.

Exactly, by upvalues you only save that _G function call nothing else. But you see how inefficient to call _G in an OnUpdate function, or any other function that gets triggered frequently.

Global call: Pointer to the global table _G -> lookup for the subtable _G.func -> get the table's pointer value -> call the C function from the returned pointer

(Since lua does not support multithreading calling _G and accessing it's subtable's value will take 2 cycles!)

Upvalued call: Pointer to the upvalued func function -> get the table's pointer value -> call the C function from the returned pointer

Seerah 01-14-18 03:18 PM

Quote:

Originally Posted by aallkkaa (Post 326487)
Thanks for all your replies.

@ myrroddin
Very nice explanation ons single-threaded vs multi-threaded languages. That was pretty much what I thought, it's the "huge oversimplification, and is not 100% accurate" part I'm sure would loose me... :D

On the cons of upvalueing, again, nice explanation, and makes perfect sense. But I was a bit surprised that 8GB would give so little leeway, having WoW running (mostly) alnoe.
Seerah seems to have a different view on this, so... :confused:

https://us.battle.net/support/en/article/76459

Banknorris 01-14-18 04:51 PM

Quote:

Originally Posted by Resike (Post 326459)
I doubt upvaluing could cause this, since you are only creating a pointer to the same function that lives in a much smaller scope then the global one, nothing else.
And since both calls also goes up to another C function the time execute it would be a minuscule bit faster call, for the memory size of the function pointer.

Not sure if I am understanding correctly what you are saying but upvaluing will indeed make your hook on global functions not run if the upvalue is set before the hooking. When you hook you create another function, but the upvalued pointer will still point to the old version (pre hook). But maybe you are talking about something else.

Resike 01-14-18 05:31 PM

Quote:

Originally Posted by Banknorris (Post 326498)
Not sure if I am understanding correctly what you are saying but upvaluing will indeed make your hook on global functions not run if the upvalue is set before the hooking. When you hook you create another function, but the upvalued pointer will still point to the old version (pre hook). But maybe you are talking about something else.

Except you are not hooking a function call, but a call for it's memory pointer's reference.

You can try it yourself:

Lua Code:
  1. hooksecurefunc("SetCVar", function(name, value)
  2.     if name == "Sound_EnableMusic" then
  3.         print(name, value)
  4.     end
  5. end)
  6.  
  7. local SetCVar = SetCVar
  8.  
  9. SetCVar("Sound_EnableMusic", 1)

You can do this in the other way around, it won't change a thing:

Lua Code:
  1. local SetCVar = SetCVar
  2.  
  3. hooksecurefunc("SetCVar", function(name, value)
  4.     if name == "Sound_EnableMusic" then
  5.         print(name, value)
  6.     end
  7. end)
  8.  
  9. _G.SetCVar("Sound_EnableMusic", 1)

lightspark 01-14-18 10:10 PM

Quote:

Originally Posted by Resike (Post 326500)
Except you are not hooking a function call, but a call for it's memory pointer's reference.

He prob meant "pre-hooking", when one pretty much re-defines a function. In that case, your upvalue will call the original.

Lombra 01-15-18 08:07 AM

Quote:

Originally Posted by Resike (Post 326500)
Except you are not hooking a function call, but a call for it's memory pointer's reference.

Well of course if you explicitly do not use a pointer created before hooking it's going to work, but no, as I've recently learnt, hooksecurefunc actually replaces the function and there's no magic at all.

This does not print anything:
Code:

local SetCVar = SetCVar

hooksecurefunc("SetCVar", function(name, value)
    if name == "Sound_EnableMusic" then
        print(name, value)
    end
end)

SetCVar("Sound_EnableMusic", 1)


jeruku 01-15-18 04:09 PM

Quote:

Originally Posted by Lombra (Post 326507)
Well of course if you explicitly do not use a pointer created before hooking it's going to work, but no, as I've recently learnt, hooksecurefunc actually replaces the function and there's no magic at all.

This does not print anything:
Code:

local SetCVar = SetCVar

hooksecurefunc("SetCVar", function(name, value)
    if name == "Sound_EnableMusic" then
        print(name, value)
    end
end)

SetCVar("Sound_EnableMusic", 1)


That does work. as hooksecurefunc is more like a callback and is called immediately after the global function is called.

Lua Code:
  1. local globalIndexName = "SetCVar"
  2. hooksecurefunc(globalIndexName, function(...) end)

So globalIndexName is a _G[index] value. You can abuse this for most work that runs alongside the default WoW interface. However, of course, it only works for functions and most frame functions that are global.

You can find some more info on WoWProgramming.

Resike 01-16-18 05:11 AM

Quote:

Originally Posted by lightspark (Post 326505)
He prob meant "pre-hooking", when one pretty much re-defines a function. In that case, your upvalue will call the original.

What do you mean by pre-hooking? Of course if your hook is wrong it's not gonna work. Or if a hook does not actually hook than don't call it a hook.

Resike 01-16-18 05:13 AM

Quote:

Originally Posted by Lombra (Post 326507)
Well of course if you explicitly do not use a pointer created before hooking it's going to work, but no, as I've recently learnt, hooksecurefunc actually replaces the function and there's no magic at all.

This does not print anything:
Code:

local SetCVar = SetCVar

hooksecurefunc("SetCVar", function(name, value)
    if name == "Sound_EnableMusic" then
        print(name, value)
    end
end)

SetCVar("Sound_EnableMusic", 1)


It makes no sense, you are saying this hook actually killing the functionality of the SetCVar calls? It also works as a described and prints the value regardless how and where do you upvalue it.


All times are GMT -6. The time now is 08:53 PM.

vBulletin © 2024, Jelsoft Enterprises Ltd
© 2004 - 2022 MMOUI