• 0

    posted a message on String creation
    Quote from yssaril
    if i remember right lua stores all strings in a hash so once created they are good to go and i think the bottom way is faster since you don't have the function call but a straight concat.


    But the concat costs some time, and it has to do in two parts.. if valOne=1 and valTwo=2 then lua first has to do (1 .. "/") = "1/", and then ("1/" .. 2) = "1/2". If "1/" wasn't already in memory, it has to allocate it on the spot, and then probably GC it again later.

    If you're worried about it, test it. It's easy:

    do
      local n = 1000
      local GetTime = GetTime
      local text,t0,t1
      collectgarbage()
      t0 = GetTime()
      for i=1,n do
        text = "%d/%d":format(n,n+1)
      end
      t1 = GetTime()
      print(n,"format()s = ",(t1-t0),"seconds")
      collectgarbage()
      t0 = GetTime()
      for i=1,n do
        text = n .. "/" .. (n+1)
      end
      t1 = GetTime()
      print(n,"concats = ",(t1-t0),"seconds")
    end
    


    Increase n until you're getting long enough times to compare (say, .5 seconds) and see which is faster.
    Posted in: Lua Code Discussion
  • 0

    posted a message on String creation
    As for the former, no, lua "internalizes" all strings, so "%d/%d" is allocated once at compile-time and referred to by a memory pointer after that.

    However the result of :format(), and the second code you posted, I think will create a new string every time. But between the two I'm not sure which is faster.
    Posted in: Lua Code Discussion
  • 0

    posted a message on Table caching and efficiency
    Quote from Xinhuan
    next() doesn't iterate anything when you pass it nothing. All next() does is return the very first key/value pair in the table where "first" is however the data is stored in its internal representation of the table's data.


    The paper I posted from lua.org (http://www.lua.org/gems/sample.pdf) disagrees with you. But you got me curious, so I tested it myself.

    Quote from Xinhuan
    In other words, the following 3 pieces loops are identical, with the second one being faster since its one less function call, and it doesn't have to adjust the right hand side of the "in" operator to 3 values.


    This much is true, but doesn't apply to a table cache, as we'll see in a moment. With sz=100000:

    -- for...pairs()
    do
    	local GetTime,pairs = GetTime,pairs
    	local t,t0,t1
    	t = {}
    	for i = 1,sz do t[i] = i end
    	collectgarbage()
    	t0 = GetTime()
    	for k in pairs(t) do
    		t[k] = nil
    	end
    	t1 = GetTime()
    	print("empty",sz,"element table with for-pairs:",(t1-t0),"seconds") -- 0.015
    end
    
    -- for...next(t,nil)
    do
    	local GetTime,next = GetTime,next
    	local t,t0,t1
    	t = {}
    	for i = 1,sz do t[i] = i end
    	collectgarbage()
    	t0 = GetTime()
    	for k,v in next,t,nil do
    		t[k] = nil
    	end
    	t1 = GetTime()
    	print("empty",sz,"element table with for-next(t,nil):",(t1-t0),"seconds") -- 0.016
    end
    
    -- for...next(t)
    do
    	local GetTime,next = GetTime,next
    	local t,t0,t1
    	t = {}
    	for i = 1,sz do t[i] = i end
    	collectgarbage()
    	t0 = GetTime()
    	for k,v in next,t do
    		t[k] = nil
    	end
    	t1 = GetTime()
    	print("empty",sz,"element table with for-next(t):",(t1-t0),"seconds") -- 0.015
    end


    They're all very fast and sure enough the middle one is even just a tiny tad slower, as you predicted. But this doesn't apply to table caching, because next() only works this fast when you're able to keep passing it the last key it returned. When you're retrieving tables from a cache, you probably have no idea what the last table was that was returned, so you're constantly using next(t,nil), which is slow.

    Again with sz=100000:

    -- while...next()
    do
    	local GetTime,next = GetTime,next
    	local t,k,t0,t1
    	t = {}
    	for i = 1,sz do t[i] = i end
    	collectgarbage()
    	t0 = GetTime()
    	k = next(t)
    	while (k) do
    		t[k] = nil
    		k = next(t)
    	end
    	t1 = GetTime()
    	print("empty",sz,"element table with while-next:",(t1-t0),"seconds") -- 13.231
    end


    That's 1000x slower to empty a 100000-element table, and it's because as the table approaches empty, it still has tons of unused hash slots in its internal representation (read that paper I posted -- really, read it, especially the section "About Tables"). So every time you call next() without telling it where to pick up where it left off, it has to scan the table looking for the first used slot again, which gets very slow near the end.

    Now a more practical example of this phenomenon:

    -- Xinhuan's tablePool
    -----------------------------------------------------------------------------
    -- Table Pool for recycling tables
    do
    	local setmetatable,next,type,pairs,GetTime = setmetatable,next,type,pairs,GetTime
    	local tablePool = {}
    	local v,t0,tM,t1
    	
    	setmetatable(tablePool, {__mode = "kv"}) -- Weak table
    	-- Get a new table
    	local function newTable()
    		local t = next(tablePool) or {}
    		tablePool[t] = nil
    		return t
    	end
    	-- Delete table and return to pool -- Recursive!! -- Use with care!!
    	local function delTable(t)
    		if type(t) == "table" then
    			for k, v in pairs(t) do
    				if type(v) == "table" then
    				    delTable(v) -- child tables get put into the pool
    				end
    				t[k] = nil
    			end
    			t[true] = true -- resize table to 1 item
    			t[true] = nil
    			setmetatable(t, nil)
    			tablePool[t] = true
    		end
    		return nil -- return nil to assign input reference
    	end
    	collectgarbage()
    	t0 = GetTime()
    	for i = 1,sz do
    		v = {}
    		delTable(v)
    	end
    	tM = GetTime()
    	for i = 1,sz do
    		v = newTable()
    	end
    	t1 = GetTime()
    	print("recycle/reuse",sz,"tables from Xin's tablePool:",
    			(t1-t0),"seconds (", -- 46.327
    			(tM-t0),"create/recycle,", -- 0.279
    			(t1-tM),"reuse)" -- 46.048
    	)
    end
    
    -- taleden's tableCache
    do
    	local GetTime,wipe = GetTime,wipe
    	local t,v,t0,tM,t1
    	t = {}
    	collectgarbage()
    	t0 = GetTime()
    	for i = 1,sz do
    		v = {}
    		wipe(v)
    		t[#t+1] = {}
    	end
    	tM = GetTime()
    	for i = 1,sz do
    		v = t[#t] or {}
    		t[#t] = nil
    	end
    	t1 = GetTime()
    	print("recycle/reuse",sz,"tables from tal's table cache:",
    			(t1-t0),"seconds (", -- 0.223
    			(tM-t0),"create/recycle,", -- 0.186
    			(t1-tM),"reuse)" -- 0.037
    	)
    end


    The functions-and-next() based solution is 200x slower than the basic t[#t] solution. And notice, the slowdown in the former is not in that big fat delTable() function -- it's in the innocuously streamlined newTable() function, and it's because of that next().

    Now one final baseline comparison:

    -- no cache
    do
    	local GetTime = GetTime
    	local t,t0,t1
    	collectgarbage()
    	t0 = GetTime()
    	for i = 1,sz do
    		t = {}
    	end
    	t1 = GetTime()
    	print("create",sz,"empty tables:",(t1-t0),"seconds") -- 0.055
    end


    Not using a table cache at all, it takes a bit less than twice as long to create 100000 fresh tables as it does to get them out of a t[#t] table cache. However, it took almost 4x as long to create and cache those tables originally, which means even the t[#t] cache is only worthwhile if you think you'll be reusing each new table ~8 times.

    Another consideration is the type and size of tables you're recycling. Again according to the paper I posted, lua makes an internal distinction between the array- and hash-components of a table. So if you have a table full of string keys, and recycle it, and re-use it as a numeric array (or vice versa), you may be wasting a lot of memory. Separate table caches for array-, hash- and mixed-type tables might be worthwhile.

    Hopefully this highlights the bizarre world that is program optimization, and reinforces rule #1.


    Edit: Just to be clear, <3 Xin. I hope this is taken in the spirit-of-science with which it was meant, and not the omg-yur-stoopid-codar that comes out so often in these circles.

    Edit2: Added the uncached-table-creation comparison, to highlight the tiny margin of speed improvement to be gained from caching.
    Posted in: Lua Code Discussion
  • 0

    posted a message on Table caching and efficiency
    I think it's a bad idea to use next() to get the next table out of your cache. As the cache table itself gets big (with lots of recycled tables in it), and then gets empty again (as those tables are re-used), next() has to iterate over more and more of the (mostly empty) cache to find something to give you. This linear-time process can be avoided by treating the cache as a FIFO stack, always addressing the last element, which (I believe) is constant-time.

    I'm also leery of table caching routines that involve calling functions, as function calls have their own overhead which makes the whole point of the caching comparatively less useful.

    And, I'm unsure about the weak keys idea. As soon as the garbage collector runs, the whole cache vanishes and you're back to creating new tables every time, except even worse because you have cache-check overhead without the cache-hit benefit. But I guess if the collector runs significantly less often than the cache is used, it could be worthwhile.
    Posted in: Lua Code Discussion
  • 0

    posted a message on Table caching and efficiency
    Quote from arnath2
    My addon deals with the quest tracker and currently, to store watched quests, I'm using a table of { questId, timer } where the timer represents a 5 minute timer for automatically watched quests. I realized recently that I was making a new table every time a unique quest watch was added which I feel is probably a bad thing to do. However, I'm confused on ways I could improve this. I was considering making some kind of table cache, e.g., when I stop using a quest watch table, store it in a cache table and retrieve it when I need a new one. However, I don't know if all the table.inserts and table.removes that this would involve would be better than just letting some tiny tables get garbage collected.

    So, I have two questions. One, how bad is making a table every time a new quest watch is added (e.g., is it worth changing this at all given that it doesn't happen very often)? And two, what's a good idea for a method to avoid making a new table every time?


    Consider well the first two rules of program optimization:
    1. Don't do it.
    2. (for experts only) Don't do it yet.

    How often are you creating new tables? How often are old tables discarded? If it's only every time a quest is added/dropped/completed/whatever, don't worry about it. Really. Table initialization isn't *that* bad and lua's garbage collector does its job well enough to handle a few stray tables.

    Table caching and re-use only becomes worthwhile when you're creating/abandoning many tables a second.

    If you were to find yourself in that situation, however, http://www.lua.org/gems/sample.pdf is a good starting point.

    I believe table.insert and table.remove would be bad. Ideally you want linear-time operations on your cache of tables, so you don't want to have to re-number them all, which tinsert and tremove tend to do. You also don't want to use next() or pairs() for this purpose because they start to take linear time as the cache itself approaches being empty after being full of abandoned tables.

    What I've been doing lately which (I think) is reasonably performant is:
    -- "tbl" is no longer needed
    wipe(tbl) -- unless you're sure it's already empty and ready to be reused
    tableCache[#tableCache+1] = tbl
    tbl = nil
    
    -- we need a new "tbl"
    tbl = tableCache[#tableCache] or {}
    tableCache[#tableCache] = nil
    


    AFAIK the table-size operator (#) queries an internally-maintained table size counter and is therefore constant time. If I'm wrong about that then this solution is not ideal.

    But again, and I can't stress this enough: optimization is an interesting (for some) pursuit, but don't stress out over it unless you're really, really, really sure it's worthwhile. It very often isn't, or if it is, you should be focusing your attention elsewhere, on parts of the code that are slow and you didn't realize it. This leads to code profiling, which is a whole nother line of inquiry.
    Posted in: Lua Code Discussion
  • 0

    posted a message on Auracle - Official Thread
    Quote from jcox611
    how do i get icons to appear for spells/effects that i dont personally generate?


    It shouldn't matter who applied the aura -- if Auracle is detecting it and showing the proper colors/timer/spiral, then it should also be getting the correct icon texture from the game API. Whether it shows this texture on the tracker itself depends on your settings, so make sure the "Autoupdate" option is checked in the "Icon" tab of the trackers' settings.
    Posted in: General AddOns
  • 0

    posted a message on Auracle - Official Thread
    Having had no major bugs reported on beta 0.4, I'm calling it release 1.0.0. Thanks everyone for your testing, debugging and ideas. :)
    Posted in: General AddOns
  • 0

    posted a message on Elkanos Buff Bars & Target Buffs/Debuffs
    This ought to be possible. UnitAura() tells you the unitid of the caster, so I'd guess you could check for "target" for the self-(de)buffs. Ones "from the environment" I'm not sure about though -- what does UnitAura() give you for those? nil? empty string?
    Posted in: AddOn HELP!
  • 0

    posted a message on X-Perl Thread
    Quote from Aristoteles

    local isWoW3dot1 = select(2,GetBuildInfo()) >= "9614"


    I didn't think you could use >= with a string in Lua. Won't that just compare some internal hash value or memory address?
    Posted in: Unit Frames
  • 0

    posted a message on Detecting all Debuff types
    At least, not as easily as having them classified for you by UnitAura().

    But if you're just looking for this information on-screen (as opposed to building it into your own addon or something),this is the sort of thing Auracle (http://www.wowace.com/addons/auracle/) was designed for.

    You'd have to manually assemble a list of debuffs for each category (i.e. stun = Kidney Shot, Cheap Shot, Hammer of Justice, Bash, ...) but it would then let you easily see which categories of debuff were active.
    Posted in: Lua Code Discussion
  • 0

    posted a message on Auracle - Official Thread
    Quote from Mists
    That solved it, the settings are persistent now, thanks!


    Faaantastic. :)

    Is anyone still having any trouble, or does that cover all the known issues (besides the CC/disembedded thing)?
    Posted in: General AddOns
  • 0

    posted a message on Fragile
    Quote from Tekkub
    The new threat system will tell you when someone's gaining threat on you by colors. A lot of UFs should implement the same system... it's something like red == you have aggro, orange == you have aggro but someone is close to pulling off you, yellow == you don't have aggro but you're about to pull it off someone. I may have gotten yellow and orange reversed. Point is, that should be the information you're after anyway, who is about to pull aggro isn't a huge deal, but you could see who at a glance to the UFs when yours goes orange.


    Aloft has that same scheme for nameplates (or did last I used it). I loved it for tanking, except that it was clearly designed with squishies in mind and not tanks: it was kind of an eyesore to tank AoE packs, all glowing red all over the screen, and then try to pick out the one mob that wasn't red anymore. Would have been awesome to be able to reverse the logic (yellow when you have threat but someone's gaining, red when you don't have threat), but there didn't ever seem to be an option for that.

    So I guess my point is, anyone who implements this kind of color-based aggro alert should remember to let us set it backwards. :)
    Posted in: Addon Ideas
  • 0

    posted a message on Auracle - Official Thread
    Quote from Mists
    Another problem I noticed with r53: all the windows/trackers are reset once I reload UI. I deleted SavedVars (not a problem since I only have a couple of trackers defined for focus), then I defined a new tracker, reload UI, tracker gone. Window/tracker styles are still there, is just the actual trackers/windows that don't seem to be persistent.


    Thanks, this led me to a bug that might be to blame.

    http://static.wowace.com/content/files/372/539/Auracle-r54.zip
    Posted in: General AddOns
  • 0

    posted a message on Auracle - Official Thread
    Quote from uhman
    After updating to r53 all configured windows and trackers were deleted. Is this behaviour intended or are there ways to use the old settings?


    No it's not intended, it means I did indeed goof the new saved var update. I won't be able to look at it til later today however. I hope you backed up your saved vars file like I advised... :)
    Posted in: General AddOns
  • 0

    posted a message on Auracle - Official Thread
    Quote from taleden
    I can't think of why it would fail to start but not have any errors.


    Scratch that -- naturally, typing "I can't think of a reason" and then looking at the code again made me think of a reason. Try r53 (http://static.wowace.com/content/files/372/507/Auracle-r53.zip).
    Posted in: General AddOns
  • To post a comment, please or register a new account.