|
|
|
04-13-2017, 02:45 PM
|
#1
|
Human being with feelings
Join Date: Apr 2011
Posts: 3,451
|
Lua code optimization - help
Hello!
I have created a "toggle visibility for active take's volume env for the selected items", which is in ReaPack already. I am trying to optimize the code, so that it runs faster when having a lot (>1000) of items selected. I have done three different implementations. I give the three codes here:
WARNING! The functions that I share here are run 50000 times(!) for runtime calculation. Do not try them with more than one item selected.
IMPLEMENTATION 1:
Code:
local reaper = reaper
local add = "<VOLENV\nACT 1\nVIS 1 1 1\nLANEHEIGHT 0 0\nARM 0\nDEFSHAPE 0 -1 -1\nPT 0 1 0\n>"
local remove = "<VOLENV\nACT 1\nVIS 1 1 1\nLANEHEIGHT 0 0\nARM 0\nDEFSHAPE 0 %-1 %-1\nPT 0 1 0\n>\n"
local function CreateItemVolEnv(item)
local _, chunk = reaper.GetItemStateChunk(item, "", false)
local add2 = ">\n"..add.."\n>"
local newchunk = chunk:gsub(">\n>", add2, 1)
if not string.match(chunk, "<VOLENV") then
reaper.SetItemStateChunk(item, newchunk, false)
end
end
local function RemoveItemVolEnv(item)
local newchunk = string.gsub(chunk, remove, "")
reaper.SetItemStateChunk(item, newchunk, false)
end
local function CreateActiveTakeVolEnv(item)
local _, chunk = reaper.GetItemStateChunk(item, "", false)
local add2 = "\n"..add
local _, startafter = string.find(chunk, "TAKE SEL")
local point, _ = string.find(chunk, ">", startafter)
local first = string.sub(chunk, 1, point)
local second = string.sub(chunk, point+1)
local newchunk = first..add2..second
reaper.SetItemStateChunk(item, newchunk, false)
end
local function RemoveActiveTakeVolEnv(item)
local startrem, endrem = string.find(chunk, remove, startafter)
local first = string.sub(chunk, 1, startrem-1)
local second = string.sub(chunk, endrem+1)
local newchunk = first..second
reaper.SetItemStateChunk(item, newchunk, false)
startafter = nil
end
local function HideTakeVolEnv(take)
local VolEnv = reaper.GetTakeEnvelopeByName(take,"Volume")
if VolEnv then
local _, chunk = reaper.GetEnvelopeStateChunk(VolEnv, "")
local newchunk = string.gsub(chunk, "VIS 1", "VIS 0")
reaper.SetEnvelopeStateChunk(VolEnv, newchunk, false)
end
end
local function ShowTakeVolEnv(take)
local VolEnv = reaper.GetTakeEnvelopeByName(take,"Volume")
if VolEnv then
local _, chunk = reaper.GetEnvelopeStateChunk(VolEnv, "")
local newchunk = string.gsub(chunk, "VIS 0", "VIS 1")
reaper.SetEnvelopeStateChunk(VolEnv, newchunk, false)
end
end
local function ToggleTakeVolEnvVisible()
local show = 0
local sel_items = reaper.CountSelectedMediaItems(0)
if sel_items == 0 then goto donothing end
if sel_items > 0 then
-- Find if any of the selected items has its vol env hidden
for i = 0, sel_items-1 do
local item = reaper.GetSelectedMediaItem(0, i)
local take = reaper.GetActiveTake(item)
if take then
local VolEnv = reaper.GetTakeEnvelopeByName(take, "Volume")
if not VolEnv then
show = 1
goto dostuff
else
local _, chunk = reaper.GetEnvelopeStateChunk(VolEnv, "")
local visible = string.match(chunk, "\nVIS (%d).-\n")
if visible == "0" then
show = 1
goto dostuff
end
end
end
end
end
::dostuff::
for i = 0, sel_items-1 do
local item = reaper.GetSelectedMediaItem(0, i)
local take_cnt = reaper.CountTakes(item)
local take = reaper.GetActiveTake(item)
if take_cnt > 0 then
local VolEnv = reaper.GetTakeEnvelopeByName(take, "Volume")
if not VolEnv then
if show == 1 then
if take_cnt == 1 then CreateItemVolEnv(item)
elseif take_cnt > 1 then CreateActiveTakeVolEnv(item)
end
end
else
if show == 1 then ShowTakeVolEnv(take)
elseif show == 0 then
if take_cnt == 1 then
_, chunk = reaper.GetItemStateChunk(item, "", false)
if string.find(chunk, remove) ~= nil then
RemoveItemVolEnv(item)
chunk = nil
else
HideTakeVolEnv(take)
end
elseif take_cnt > 1 then
_, chunk = reaper.GetItemStateChunk(item, "", false)
_, startafter = string.find(chunk, "TAKE SEL")
_ = nil
if not startafter then
startafter = string.find(chunk, ">\n<VOLENV")-10
end
if string.find(chunk, remove, startafter) then
RemoveActiveTakeVolEnv(item)
chunk = nil
else
HideTakeVolEnv(take)
end
end
end
end
end
end
::donothing::
end
st = reaper.time_precise()
for i = 1, 50000 do
reaper.PreventUIRefresh(1)
ToggleTakeVolEnvVisible(item)
reaper.PreventUIRefresh(-1)
end
fin = reaper.time_precise()
elapsed_time = fin - s
function NoUndoPoint() end ; reaper.defer(NoUndoPoint)
elapsed time: 46.18secs
IMPLEMENTATION 2:
Code:
local reaper = reaper
local function ToggleVisibility(item)
local take = reaper.GetActiveTake(item)
if take then
local VolEnv = reaper.GetTakeEnvelopeByName(take, "Volume")
local _, chunk = reaper.GetItemStateChunk(item, "", false)
local act_take_guid = reaper.BR_GetMediaItemTakeGUID(take)
local t = {}
local function helper(line) table.insert(t, line) return "" end
helper((chunk:gsub("(.-)\r?\n", helper)))
local found = 0
for i in pairs(t) do
if string.match(t[i], act_take_guid:gsub("-", "%%-")) then
found = i ; break
end
end
if VolEnv then
for i = found, #t do
if string.match(t[i], "TAKE") or (t[i] == ">" and t[i-1] == ">") then break end
if string.match(t[i], "VIS %d 1 1") then
t[i] = t[i]:gsub("(VIS%s)(%d)", function(a,b) return a .. (b~1) end) ; break
end
end
else
local found2 = 0
def_env = {"<VOLENV","ACT 1","VIS 1 1 1","LANEHEIGHT 0 0","ARM 0","DEFSHAPE 0 -1 -1","PT 0 1 0",">"}
for i = found, #t do
if t[i]:match(">") and (t[i+1]:match("TAKE") or t[i+1]:match(">")) then
found2 = i+1 ; break
end
end
if found2 ~= 0 then
for i = #def_env, 1, -1 do
table.insert(t, found2, def_env[i])
end
end
end
reaper.SetItemStateChunk(item, table.concat(t, "\n"), false)
end
end
local item = reaper.GetSelectedMediaItem(0, 0)
st = reaper.time_precise()
for i = 1, 50000 do
reaper.PreventUIRefresh(1)
ToggleVisibility(item)
reaper.PreventUIRefresh(-1)
end
fin = reaper.time_precise()
elapsed_time = fin - st
elapsed time: 49.15secs
IMPLEMENTATION 3:
Code:
local reaper = reaper
local function ToggleTakeVolEnvVisible()
local sel_items = reaper.CountSelectedMediaItems(0)
if sel_items > 0 then
for i = 0, sel_items-1 do
local item = reaper.GetSelectedMediaItem(0, i)
local take = reaper.GetActiveTake(item)
if take then
local take_cnt = reaper.CountTakes(item)
local def_env = "<VOLENV\nACT 1\nVIS 1 1 1\nLANEHEIGHT 0 0\nARM 0\nDEFSHAPE [+-]?%d+%s[+-]?%d+%s[+-]?%d+\nPT 0 1 0\n>"
local _, chunk = reaper.GetItemStateChunk(item, "", false)
if take_cnt == 1 then
if chunk:find("<VOLENV") then
if not chunk:find(def_env) then
local newchunk = chunk:gsub("(\nVIS%s)(%d)", function(a,b) return a .. (b~1) end)
reaper.SetItemStateChunk(item, newchunk, false)
else
reaper.Main_OnCommand(40693, 0) -- Take: Toggle take volume envelope
end
else
reaper.Main_OnCommand(40693, 0) -- Take: Toggle take volume envelope
end
else -- (more than one takes)
local act_take_guid = reaper.BR_GetMediaItemTakeGUID(take)
local takechunk = chunk:match(act_take_guid:gsub("-", "%%-") .. ".-" .. "\nTAKE?")
or chunk:match(act_take_guid:gsub("-", "%%-") .. ".-" .. ">\n>")
if takechunk:find("<VOLENV") then
if not takechunk:find(def_env) then
local VolEnv = reaper.GetTakeEnvelopeByName(take,"Volume")
local _, envchunk = reaper.GetEnvelopeStateChunk(VolEnv, "", false)
local newchunk = envchunk:gsub("(\nVIS%s)(%d)", function(a,b) return a .. (b~1) end)
reaper.SetEnvelopeStateChunk(VolEnv, newchunk, false)
else
reaper.Main_OnCommand(40693, 0) -- Take: Toggle take volume envelope
end
else
reaper.Main_OnCommand(40693, 0) -- Take: Toggle take volume envelope
end
end
end
end
end
end
st = reaper.time_precise()
for i = 1, 50000 do
reaper.PreventUIRefresh(1)
ToggleTakeVolEnvVisible()
reaper.PreventUIRefresh(-1)
end
en = reaper.time_precise()
elapsed_time = en-st
function NoUndoPoint() end ; reaper.defer(NoUndoPoint)
elapsed time: 180.5secs
(reaper.Main_OnCommand(40693, 0) is REALLY slow)
Does any of you have any proposals on how could the code be better optimized?
BTW, while making all this I ran onto some bugs .. Lokasenna was the one who inspired me to measure the time of the functions...
Last edited by amagalma; 04-13-2017 at 03:37 PM.
|
|
|
04-13-2017, 05:17 PM
|
#2
|
Human being with feelings
Join Date: Apr 2014
Posts: 4,171
|
I've found it is the calls to SetXXXStateChunk which generally take the most time.
So could you work on the track chunks - first identifying which tracks the items are on and then changing all the item settings for each track in one go - so only a single call to SetTrackStateChunk per track with selected items on.
I've not worked much with items - and am assuming the data can be obtained and altered via the track state as well as item state.
You would need to add some code to work out what tracks the items are on - and then identifying each selected one - but avoiding 1000+ calls to Set Chunk routines is still likely to be faster. Like I said - not overly familiar with how the item data is stored - but assuming you can identify each one by a guid it might be quicker this way.
Of course - if each of 1000 items is on its own track - this method would be horrifically slow, but if the 1000 items were across a small number of tracks - it would probably be faster.
and you wouldn't be able to test the speed by toggling the envelope visibility for the same item 50000 times - so would need to develop an alternate testing method.
|
|
|
04-13-2017, 05:28 PM
|
#3
|
Human being with feelings
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,551
|
You've got a number of redundant API calls (getting a value and then getting it again in one of your functions), and you loop through the selected items more than once. I don't have time to look through it, but that seems like it could probably go away - or at least be trimmed down.
|
|
|
04-13-2017, 06:05 PM
|
#4
|
Human being with feelings
Join Date: Apr 2011
Posts: 3,451
|
Thank you both for your replies!
@lb0: Yes.. getting/writing chunks is the most slow part... I may try the track chunk method... but again.. the chunk is going to be HUUUGE and the string manipulation is going to be very difficult.. so, theoretically I am not sure this is the way to go...
@Lokasenna: Yes, you have right regarding the first implementation (which is on ReaPack).. But strangely, this one is the fastest! :S .. I consider implementation 2 to be the best one (that is why I am working on it atm)..
I have another question.. I have two tables, a bigger and a smaller one.. All the values in the smaller exist in the bigger too, in the same series.
So this works and returrns defaultexists = 1.
Code:
defaultexists = 1
if def_env[j] ~= t[VolEnvStart+j-1] then defaultexists = 0 end
Why this doesn't work?
Code:
defaultexists = 1
if def_env[j]:find(t[VolEnvStart+j-1]) == nil then defaultexists = 0 end
(returns defaultexists = 0)
IMPLEMENTATION 4 (based on implementation 2):
Code:
local reaper = reaper
local function ToggleVisibility(item)
take_cnt = reaper.CountTakes(item)
if take_cnt > 0 then
local take = reaper.GetActiveTake(item)
local def_env = {"<VOLENV","ACT 1","VIS 1 1 1","LANEHEIGHT 0 0","ARM 0","DEFSHAPE 0 -1 -1","PT 0 1 0",">"}
local VolEnv = reaper.GetTakeEnvelopeByName(take, "Volume")
local _, chunk = reaper.GetItemStateChunk(item, "", false)
local act_take_guid = reaper.BR_GetMediaItemTakeGUID(take)
local t = {}
local function helper(line) table.insert(t, line) return "" end
helper((chunk:gsub("(.-)\r?\n", helper)))
local found = 0
for i in pairs(t) do
if string.match(t[i], act_take_guid:gsub("-", "%%-")) then
found = i ; break
end
end
local VolEnvStart, defaultexists, VisLine, insert_here = 0, 1, 0, 0
for i = found, #t do
if t[i]:match("TAKE") or (t[i] == ">" and t[i-1] == ">") then
insert_here = i ; defaultexists = 0 break -- No Volume Envelope exists for the active take
end
if t[i]:match("<VOLENV") then VolEnvStart = i break end -- Volume Envelope exists
end
if VolEnvStart > 0 then
for j = 1, #def_env do -- Check if existing Volume envelope is the default
if def_env[j] ~= (t[VolEnvStart+j-1]) then defaultexists = 0 end
if string.match(t[VolEnvStart+j-1], "VIS %d 1 1") then VisLine = VolEnvStart+j-1 end
end
end
if insert_here ~= 0 then -- VolEnv does not exist, so create default
for i = #def_env, 1, -1 do
table.insert(t, insert_here, def_env[i])
end
else -- VolEnv exists
if defaultexists == 0 then -- Toggle visibility
t[VisLine] = t[VisLine]:gsub("(VIS%s)(%d)", function(a,b) return a .. (b~1) end)
else -- Remove Default envelope
for i = 1, #def_env do
table.remove(t, VolEnvStart)
end
end
end
reaper.SetItemStateChunk(item, table.concat(t, "\n"), false) -- Write the table to the item chunk
end
end
local function Main()
local sel_items = reaper.CountSelectedMediaItems(0)
if sel_items > 0 then
for i = 0, sel_items-1 do
local item = reaper.GetSelectedMediaItem(0, i)
ToggleVisibility(item)
end
end
end
st = reaper.time_precise()
for i = 1, 50000 do
reaper.PreventUIRefresh(1)
Main()
reaper.PreventUIRefresh(-1)
end
fin = reaper.time_precise()
elapsed_time = fin - st
EDIT: I just found out:
a) Setting IsUndoOptional to true instead of false, actually improves a bit the performance
b) Getting various local steady variables outside of the function, kills performance!
Last edited by amagalma; 04-13-2017 at 07:18 PM.
|
|
|
04-13-2017, 08:07 PM
|
#5
|
Human being with feelings
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,551
|
1. What do those tables contain?
string.find can do pattern-matching stuff with special characters, so if your search string happens to contain any of them it won't parse the string properly. If that's the case you could do this to make it parse the string as is:
Code:
string.find (s, pattern [, init [, plain]])
-->
def_env[j]:find(t[VolEnvStart+j-1],1,true)
2. Localized variables are always faster, though HOW much faster will definitely depend on how often you need to access them. I've tested them a few times and never managed to see much of a performance gain going from a top-level local to one inside the function. But hey, if it works it works.
|
|
|
04-14-2017, 01:48 AM
|
#6
|
Human being with feelings
Join Date: Apr 2011
Posts: 3,451
|
The small one is the default envelope
Code:
def_env = {"<VOLENV","ACT 1","VIS 1 1 1","LANEHEIGHT 0 0","ARM 0","DEFSHAPE 0 -1 -1","PT 0 1 0",">"}
So, I should first escape the "-" before string.find, right?
Plus, I should be searching the other way around... Looking for the smaller in the bigger...
Plus... I don't really care if DEFSHAPE match, since each user may have a different setting..
Last edited by amagalma; 04-14-2017 at 02:57 AM.
|
|
|
04-14-2017, 02:37 AM
|
#7
|
Human being with feelings
Join Date: Apr 2011
Posts: 3,451
|
The new code (official implementation):
Code:
-- @description amagalma_Toggle active take volume envelope visible for selected item(s)
-- @author amagalma
-- @version 2.01
-- @about
-- # Toggles visibility of (active) take volume envelopes for the selected item(s)
--
-- - Does not create undo points by default. Easily changeable in the end of the script.
--[[
* Changelog:
* v2.01 (2017-04-14)
+ small improvement
* v2.0 (2017-04-14)
+ Complete re-write of the code. No more depending on buggy actions
* v1.02 (2017-04-11)
+ fixed bug that would crop to active take when hiding empty envelope of first and active take in a multitake item
* v1.01 (2017-04-10)
+ made the action substantially faster when many items (>500) are selected
--]]
local reaper = reaper
local function ToggleVisibility(item)
local take_cnt = reaper.CountTakes(item)
if take_cnt > 0 then
local take = reaper.GetActiveTake(item)
local act_take_guid = reaper.BR_GetMediaItemTakeGUID(take)
local GetChunk = reaper.GetItemStateChunk
local _, chunk = GetChunk(item, "", true)
local def_env = {"<VOLENV","ACT 1","VIS 1 1 1","LANEHEIGHT 0 0","ARM 0","DEFSHAPE 0 -1 -1","PT 0 1 0",">"}
local t = {}
local function helper(line) table.insert(t, line) return "" end
helper((chunk:gsub("(.-)\r?\n", helper)))
local found = 0
for i in pairs(t) do
if string.match(t[i], act_take_guid:gsub("-", "%%-")) then
found = i ; break
end
end
local VolEnvStart, defaultexists, VisLine, insert_here = 0, 1, 0, 0
for i = found, #t do
if t[i]:match("TAKE") or (t[i] == ">" and t[i-1] == ">") then
insert_here = i ; defaultexists = 0 break -- No Volume Envelope exists for the active take
end
if t[i]:match("<VOLENV") then VolEnvStart = i break end -- Volume Envelope exists
end
if VolEnvStart > 0 then
for j = 1, #def_env do -- Check if existing Volume envelope is the default
if j ~= 6 then -- We don't care if DEFSHAPE matches
if not t[VolEnvStart+j-1]:find(def_env[j]) then defaultexists = 0 end
end
if string.match(t[VolEnvStart+j-1], "VIS %d 1 1") then VisLine = VolEnvStart+j-1 end
end
end
if insert_here ~= 0 then -- VolEnv does not exist, so create default
for i = #def_env, 1, -1 do
table.insert(t, insert_here, def_env[i])
end
else -- VolEnv exists
if defaultexists == 0 then -- Toggle visibility
t[VisLine] = t[VisLine]:gsub("(VIS%s)(%d)", function(a,b) return a .. (b~1) end)
else -- Remove Default envelope
for i = 1, #def_env do
table.remove(t, VolEnvStart)
end
end
end
local SetChunk = reaper.SetItemStateChunk
SetChunk(item, table.concat(t, "\n"), true) -- Write the table to the item chunk
end
end
local function Main()
local sel_items = reaper.CountSelectedMediaItems(0)
if sel_items > 0 then
for i = 0, sel_items-1 do
local item = reaper.GetSelectedMediaItem(0, i)
ToggleVisibility(item)
end
end
end
-- Uncomment undos if you want Undo points to be created
--reaper.Undo_BeginBlock()
reaper.PreventUIRefresh(1)
Main()
reaper.PreventUIRefresh(-1)
--reaper.Undo_EndBlock("Toggle active take volume envelope visible", -1)
-- Comment the line below if you have uncommented the Undo lines
function NoUndoPoint() end ; reaper.defer(NoUndoPoint)
Do you see any way that the code could be further optimized?
Last edited by amagalma; 04-14-2017 at 02:58 AM.
|
|
|
04-14-2017, 05:28 AM
|
#8
|
Human being with feelings
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,551
|
Looks alright to me.
Re: the question from last night... maybe? I'm pretty bad with escape characters and all that. Why do you want to use string.find for a simple comparison?
|
|
|
04-14-2017, 10:13 AM
|
#9
|
Human being with feelings
Join Date: Apr 2011
Posts: 3,451
|
I wanted because I wanted to avoid comparison for DEFSHAPE (%-?%d%s%-?%d%s%-?%d%) but since I found a better way, then there's no need to use it. So, back to simple comparison!
|
|
|
04-15-2017, 06:02 AM
|
#10
|
Human being with feelings
Join Date: Apr 2011
Posts: 3,451
|
Simple comparison seems to be the fastest and most efficient way to go. Check this:
Code:
reaper.ClearConsole()
function checkfind()
local t = {"DEFSHAPE 1 1 1"}
local a = {"DEFSHAPE 1 1 1"}
local st = reaper.time_precise()
for i = 1, 10000000 do
if t[1]:find(a[1]) then defaultexists = 1 end
end
local en = reaper.time_precise()
local time = en-st
reaper.ShowConsoleMsg("find method time: " .. time .."\n")
end
function checkmatch()
local t = {"DEFSHAPE 1 1 1"}
local a = {"DEFSHAPE 1 1 1"}
local st = reaper.time_precise()
for i = 1, 10000000 do
if t[1]:match(a[1]) then defaultexists = 1 end
end
local en = reaper.time_precise()
local time = en-st
reaper.ShowConsoleMsg("match method time: " .. time .."\n")
end
function checkequal()
local t = {"DEFSHAPE 1 1 1"}
local a = {"DEFSHAPE 1 1 1"}
local st = reaper.time_precise()
for i = 1, 10000000 do
if t[1] == a[1] then defaultexists = 1 end
end
local en = reaper.time_precise()
local time = en-st
reaper.ShowConsoleMsg("equality method time: " .. time .."\n")
end
checkfind()
checkmatch()
checkequal()
|
|
|
04-15-2017, 08:44 AM
|
#11
|
Human being with feelings
Join Date: Aug 2012
Posts: 271
|
You can also improve performance by 20-30% for each function in a very simple way .
Example:
Code:
----------------------------------
local start = reaper.time_precise()
for i = 1, 1000000 do
local a = math.sin(math.random())
end
reaper.ShowConsoleMsg("global sin, random: " .. reaper.time_precise() - start .."\n")
----------------------------------
local start = reaper.time_precise()
local random = math.random
local sin = math.sin
for i = 1, 1000000 do
local a = sin(random())
end
reaper.ShowConsoleMsg("local sin, random: " .. reaper.time_precise() - start .."\n")
Same for the string functions:
local match = string.match
local find = string.find
Then just use a regular call find(str, pattern); match(str, pattern).
Don't use methods str:match(), str:find() etc
|
|
|
04-15-2017, 09:54 AM
|
#12
|
Human being with feelings
Join Date: Apr 2011
Posts: 3,451
|
I had done this in my code for some functions, but without really checking if there is a performance benefit or not..
Code:
local SetChunk = reaper.SetItemStateChunk
SetChunk(item, table.concat(t, "\n"), true) -- Write the table to the item chunk
Going to measure!.. Thanks for the tip eugen!
|
|
|
04-15-2017, 10:22 AM
|
#13
|
Human being with feelings
Join Date: Apr 2011
Posts: 3,451
|
Hmm.. my measurement didn't show a benefit.. check this out:
Code:
function notlocal()
local st = reaper.time_precise()
local sel_items = reaper.CountSelectedMediaItems(0)
for i = 0, sel_items-1 do
local item = reaper.GetSelectedMediaItem(0, i)
local take = reaper.GetActiveTake(item)
local act_take_guid = reaper.BR_GetMediaItemTakeGUID(take)
local _, chunk = reaper.GetItemStateChunk(item, "", true)
end
local en = reaper.time_precise()
reaper.ShowConsoleMsg("not local functions time: ".. (en-st) .. "\n")
end
function locals()
local st = reaper.time_precise()
local CountSelItems = reaper.CountSelectedMediaItems
local sel_items = CountSelItems(0)
local GetSelItem = reaper.GetSelectedMediaItem
local GetActTake = reaper.GetActiveTake
local GetTakeGUID = reaper.BR_GetMediaItemTakeGUID
local GetItemChunk = reaper.GetItemStateChunk
for i = 0, sel_items-1 do
local item = GetSelItem(0, i)
local take = GetActTake(item)
local act_take_guid = GetTakeGUID(take)
local _, chunk = GetItemChunk(item, "", true)
end
local en = reaper.time_precise()
reaper.ShowConsoleMsg("local functions time: ".. (en-st) .. "\n")
end
function localreaper()
local reaper = reaper
local st = reaper.time_precise()
local sel_items = reaper.CountSelectedMediaItems(0)
for i = 0, sel_items-1 do
local item = reaper.GetSelectedMediaItem(0, i)
local take = reaper.GetActiveTake(item)
local act_take_guid = reaper.BR_GetMediaItemTakeGUID(take)
local _, chunk = reaper.GetItemStateChunk(item, "", true)
end
local en = reaper.time_precise()
reaper.ShowConsoleMsg("local reaper functions time: ".. (en-st) .. "\n")
end
reaper.ShowConsoleMsg("Selected items: ".. reaper.CountSelectedMediaItems(0) .. "\n")
notlocal()
locals()
localreaper()
Results========
Selected items: 60000
not local functions time: 122.92054463878
local functions time: 122.82012330209
local reaper functions time: 122.73700544908
===============
Maybe repeating an action 1million times is not a valid way to measure things...
|
|
|
04-15-2017, 10:50 AM
|
#14
|
Human being with feelings
Join Date: Aug 2012
Posts: 271
|
You misunderstood, I meant the standard libraries of Lua. Make tests for them - math, string.
You can get more information here - "Lua Performance Tips" - Roberto Ierusalimschy.
Last edited by eugen2777; 04-15-2017 at 10:55 AM.
|
|
|
04-15-2017, 11:02 AM
|
#15
|
Human being with feelings
Join Date: Apr 2011
Posts: 3,451
|
Oh! Sorry!.. Thanks!
|
|
|
04-15-2017, 12:02 PM
|
#16
|
Human being with feelings
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,551
|
You're not seeing a performance benefit with the Reaper functions because in your previous example you already made Reaper's functions local:
Code:
local reaper = reaper
The Lua libraries are "external", so it's more work to access them.
|
|
|
04-19-2017, 02:51 AM
|
#17
|
Human being with feelings
Join Date: Apr 2011
Posts: 3,451
|
Following lb0's advice, I did a small test:
Code:
local reaper = reaper
local t_ins = table.insert
local function itemchunk()
local st = reaper.time_precise()
local itemch = {}
local sel_items = reaper.CountSelectedMediaItems(0)
for i = 0, sel_items-1 do
local item = reaper.GetSelectedMediaItem(0, i)
local take = reaper.GetActiveTake(item)
local act_take_guid = reaper.BR_GetMediaItemTakeGUID(take)
local _, chunk = reaper.GetItemStateChunk(item, "", true)
local function helper(line) t_ins(itemch, line) return "" end
helper((chunk:gsub("(.-)\r?\n", helper)))
end
local en = reaper.time_precise()
reaper.ShowConsoleMsg("item chunk time: ".. (en-st) .. "\n")
end
local function trackchunk()
local st = reaper.time_precise()
local trackch = {}
local sel_tracks = reaper.CountSelectedTracks(0)
for i = 0, sel_tracks-1 do
local track = reaper.GetSelectedTrack(0, i)
local itemsintrack = reaper.CountTrackMediaItems(track)
for j = 0, itemsintrack-1 do
local item = reaper.GetTrackMediaItem(track, j)
local take = reaper.GetActiveTake(item)
local act_take_guid = reaper.BR_GetMediaItemTakeGUID(take)
end
local _, chunk = reaper.GetTrackStateChunk(track, "", true)
local function helper(line) t_ins(trackch, line) return "" end
helper((chunk:gsub("(.-)\r?\n", helper)))
end
local en = reaper.time_precise()
reaper.ShowConsoleMsg("track chunk time: ".. (en-st) .. "\n")
end
reaper.ShowConsoleMsg("Selected items: ".. reaper.CountSelectedMediaItems(0) .. "\n")
reaper.ShowConsoleMsg("Selected tracks: ".. reaper.CountSelectedTracks(0) .. "\n")
itemchunk()
trackchunk()
Results:
Code:
Selected items: 20000
Selected tracks: 2
item chunk time: 11.474870597764
track chunk time: 7.1315625969601
Code:
Selected items: 382
Selected tracks: 2
item chunk time: 0.024353335278647
track chunk time: 0.024759846542111
So, it seems that track chunks are faster. But are they reliable? I mean, should I go this way, or do I risk to mess VST plugins on track, or make Reaper crash?
Last edited by amagalma; 04-19-2017 at 02:59 AM.
|
|
|
04-19-2017, 02:07 PM
|
#18
|
Human being with feelings
Join Date: Apr 2014
Posts: 4,171
|
Quote:
Originally Posted by amagalma
Following lb0's advice, I did a small test:
Code:
local reaper = reaper
local t_ins = table.insert
local function itemchunk()
local st = reaper.time_precise()
local itemch = {}
local sel_items = reaper.CountSelectedMediaItems(0)
for i = 0, sel_items-1 do
local item = reaper.GetSelectedMediaItem(0, i)
local take = reaper.GetActiveTake(item)
local act_take_guid = reaper.BR_GetMediaItemTakeGUID(take)
local _, chunk = reaper.GetItemStateChunk(item, "", true)
local function helper(line) t_ins(itemch, line) return "" end
helper((chunk:gsub("(.-)\r?\n", helper)))
end
local en = reaper.time_precise()
reaper.ShowConsoleMsg("item chunk time: ".. (en-st) .. "\n")
end
local function trackchunk()
local st = reaper.time_precise()
local trackch = {}
local sel_tracks = reaper.CountSelectedTracks(0)
for i = 0, sel_tracks-1 do
local track = reaper.GetSelectedTrack(0, i)
local itemsintrack = reaper.CountTrackMediaItems(track)
for j = 0, itemsintrack-1 do
local item = reaper.GetTrackMediaItem(track, j)
local take = reaper.GetActiveTake(item)
local act_take_guid = reaper.BR_GetMediaItemTakeGUID(take)
end
local _, chunk = reaper.GetTrackStateChunk(track, "", true)
local function helper(line) t_ins(trackch, line) return "" end
helper((chunk:gsub("(.-)\r?\n", helper)))
end
local en = reaper.time_precise()
reaper.ShowConsoleMsg("track chunk time: ".. (en-st) .. "\n")
end
reaper.ShowConsoleMsg("Selected items: ".. reaper.CountSelectedMediaItems(0) .. "\n")
reaper.ShowConsoleMsg("Selected tracks: ".. reaper.CountSelectedTracks(0) .. "\n")
itemchunk()
trackchunk()
Results:
Code:
Selected items: 20000
Selected tracks: 2
item chunk time: 11.474870597764
track chunk time: 7.1315625969601
Code:
Selected items: 382
Selected tracks: 2
item chunk time: 0.024353335278647
track chunk time: 0.024759846542111
So, it seems that track chunks are faster. But are they reliable? I mean, should I go this way, or do I risk to mess VST plugins on track, or make Reaper crash?
|
I would say as long as you're careful not to change anything you shouldn't be changing - so ensure you're only changing the parameters you need to, and don't mess up any of the layout - it should be fine.
Saying that - I've found one or two plugins (amongst quite a few) that seem to reset themselves upon changing the track chunk (even using some SWS actions did this). But I really am talking about only one or two particular plugins (in fact - one was a VST3 version - where the VST2 version of the same plugin was fine).
I've got away with reordering the FX chunks in a chain to reorder the plugins with no problems.
The only time I've had issues is when I've messed up the layout by chopping off a \n (newline) causing 2 lines to merge - but all it did was fail to read the parameters that had been combined.
I guess if you're worried - test all (or lots of) the plugins you use to see if they cause problems.
Quick analysis of your test though - I don't see you calling the Set chunk routines - and it's these that tend to be slow - so not sure the test tells the whole story. But I may have read your code wrong.
Last edited by lb0; 04-19-2017 at 02:17 PM.
|
|
|
04-19-2017, 02:29 PM
|
#19
|
Human being with feelings
Join Date: Apr 2011
Posts: 3,451
|
No, I didn't use any Set Chunk routines in my test. I thought that since there is difference in performance with the Get Chunk routines the same would apply to the Set ones.. But maybe I should check just to be sure
|
|
|
Thread Tools |
|
Display Modes |
Linear Mode
|
Posting Rules
|
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts
HTML code is Off
|
|
|
All times are GMT -7. The time now is 10:37 PM.
|