Old 04-13-2017, 02:45 PM   #1
amagalma
Human being with feelings
 
amagalma's Avatar
 
Join Date: Apr 2011
Posts: 3,451
Default 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...
__________________
Most of my scripts can be found in ReaPack.
If you find them useful, a donation would be greatly appreciated! Thank you! :)

Last edited by amagalma; 04-13-2017 at 03:37 PM.
amagalma is offline   Reply With Quote
Old 04-13-2017, 05:17 PM   #2
lb0
Human being with feelings
 
Join Date: Apr 2014
Posts: 4,171
Default

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.
__________________
Projects - Reascripts - Lua:
Smart Knobs 2 | LBX Stripper | LBX Floating FX Positioner
Donate via Paypal | LBX Tools Website
lb0 is offline   Reply With Quote
Old 04-13-2017, 05:28 PM   #3
Lokasenna
Human being with feelings
 
Lokasenna's Avatar
 
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,551
Default

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.
__________________
I'm no longer using Reaper or working on scripts for it. Sorry. :(
Default 5.0 Nitpicky Edition / GUI library for Lua scripts / Theory Helper / Radial Menu / Donate
Lokasenna is offline   Reply With Quote
Old 04-13-2017, 06:05 PM   #4
amagalma
Human being with feelings
 
amagalma's Avatar
 
Join Date: Apr 2011
Posts: 3,451
Default

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!
__________________
Most of my scripts can be found in ReaPack.
If you find them useful, a donation would be greatly appreciated! Thank you! :)

Last edited by amagalma; 04-13-2017 at 07:18 PM.
amagalma is offline   Reply With Quote
Old 04-13-2017, 08:07 PM   #5
Lokasenna
Human being with feelings
 
Lokasenna's Avatar
 
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,551
Default

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.
__________________
I'm no longer using Reaper or working on scripts for it. Sorry. :(
Default 5.0 Nitpicky Edition / GUI library for Lua scripts / Theory Helper / Radial Menu / Donate
Lokasenna is offline   Reply With Quote
Old 04-14-2017, 01:48 AM   #6
amagalma
Human being with feelings
 
amagalma's Avatar
 
Join Date: Apr 2011
Posts: 3,451
Default

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..
__________________
Most of my scripts can be found in ReaPack.
If you find them useful, a donation would be greatly appreciated! Thank you! :)

Last edited by amagalma; 04-14-2017 at 02:57 AM.
amagalma is offline   Reply With Quote
Old 04-14-2017, 02:37 AM   #7
amagalma
Human being with feelings
 
amagalma's Avatar
 
Join Date: Apr 2011
Posts: 3,451
Default

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?
__________________
Most of my scripts can be found in ReaPack.
If you find them useful, a donation would be greatly appreciated! Thank you! :)

Last edited by amagalma; 04-14-2017 at 02:58 AM.
amagalma is offline   Reply With Quote
Old 04-14-2017, 05:28 AM   #8
Lokasenna
Human being with feelings
 
Lokasenna's Avatar
 
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,551
Default

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?
__________________
I'm no longer using Reaper or working on scripts for it. Sorry. :(
Default 5.0 Nitpicky Edition / GUI library for Lua scripts / Theory Helper / Radial Menu / Donate
Lokasenna is offline   Reply With Quote
Old 04-14-2017, 10:13 AM   #9
amagalma
Human being with feelings
 
amagalma's Avatar
 
Join Date: Apr 2011
Posts: 3,451
Default

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!
__________________
Most of my scripts can be found in ReaPack.
If you find them useful, a donation would be greatly appreciated! Thank you! :)
amagalma is offline   Reply With Quote
Old 04-15-2017, 06:02 AM   #10
amagalma
Human being with feelings
 
amagalma's Avatar
 
Join Date: Apr 2011
Posts: 3,451
Default

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()
__________________
Most of my scripts can be found in ReaPack.
If you find them useful, a donation would be greatly appreciated! Thank you! :)
amagalma is offline   Reply With Quote
Old 04-15-2017, 08:44 AM   #11
eugen2777
Human being with feelings
 
eugen2777's Avatar
 
Join Date: Aug 2012
Posts: 271
Default

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
__________________
ReaScripts
eugen2777 is offline   Reply With Quote
Old 04-15-2017, 09:54 AM   #12
amagalma
Human being with feelings
 
amagalma's Avatar
 
Join Date: Apr 2011
Posts: 3,451
Default

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!
__________________
Most of my scripts can be found in ReaPack.
If you find them useful, a donation would be greatly appreciated! Thank you! :)
amagalma is offline   Reply With Quote
Old 04-15-2017, 10:22 AM   #13
amagalma
Human being with feelings
 
amagalma's Avatar
 
Join Date: Apr 2011
Posts: 3,451
Default

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...
__________________
Most of my scripts can be found in ReaPack.
If you find them useful, a donation would be greatly appreciated! Thank you! :)
amagalma is offline   Reply With Quote
Old 04-15-2017, 10:50 AM   #14
eugen2777
Human being with feelings
 
eugen2777's Avatar
 
Join Date: Aug 2012
Posts: 271
Default

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.
__________________
ReaScripts

Last edited by eugen2777; 04-15-2017 at 10:55 AM.
eugen2777 is offline   Reply With Quote
Old 04-15-2017, 11:02 AM   #15
amagalma
Human being with feelings
 
amagalma's Avatar
 
Join Date: Apr 2011
Posts: 3,451
Default

Oh! Sorry!.. Thanks!
__________________
Most of my scripts can be found in ReaPack.
If you find them useful, a donation would be greatly appreciated! Thank you! :)
amagalma is offline   Reply With Quote
Old 04-15-2017, 12:02 PM   #16
Lokasenna
Human being with feelings
 
Lokasenna's Avatar
 
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,551
Default

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.
__________________
I'm no longer using Reaper or working on scripts for it. Sorry. :(
Default 5.0 Nitpicky Edition / GUI library for Lua scripts / Theory Helper / Radial Menu / Donate
Lokasenna is offline   Reply With Quote
Old 04-19-2017, 02:51 AM   #17
amagalma
Human being with feelings
 
amagalma's Avatar
 
Join Date: Apr 2011
Posts: 3,451
Default

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?
__________________
Most of my scripts can be found in ReaPack.
If you find them useful, a donation would be greatly appreciated! Thank you! :)

Last edited by amagalma; 04-19-2017 at 02:59 AM.
amagalma is offline   Reply With Quote
Old 04-19-2017, 02:07 PM   #18
lb0
Human being with feelings
 
Join Date: Apr 2014
Posts: 4,171
Default

Quote:
Originally Posted by amagalma View Post
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.
__________________
Projects - Reascripts - Lua:
Smart Knobs 2 | LBX Stripper | LBX Floating FX Positioner
Donate via Paypal | LBX Tools Website

Last edited by lb0; 04-19-2017 at 02:17 PM.
lb0 is offline   Reply With Quote
Old 04-19-2017, 02:29 PM   #19
amagalma
Human being with feelings
 
amagalma's Avatar
 
Join Date: Apr 2011
Posts: 3,451
Default

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
__________________
Most of my scripts can be found in ReaPack.
If you find them useful, a donation would be greatly appreciated! Thank you! :)
amagalma is offline   Reply With Quote
Reply

Thread Tools
Display Modes

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

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

Forum Jump


All times are GMT -7. The time now is 07:48 AM.


Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2024, vBulletin Solutions Inc.