renoise.Document() behaviour [solved]

So I thought I’d release my first plugin today,
but for the sake of improvement, I’ve completely screwed it up.
Everything I needed worked beautifully before I thought I’d also want to save the settings.
It occurred to me to use the Document class instead of global variables.
All variables I replaced the references to Document. But I didn’t do that.
For example FREQUENCY_1 I referred to Document () Config.osc.frequency1.value everywhere in the code.
But because of this my code has slowed down by at least 1000% !!!
It is normal ?

thanks.

I don’t quite remember how significant the ‘overhead’ of accessing document properties is, but it is slower than a normal variable for sure.

Quick fix: try to just ‘cache’ the value as a normal variable wherever it is being heavily used/read. (small sidenote: is there any chance that you’re reading the value way too frequent? Any room for optimization?)

PS. Normally, if I used a document for saving data, I would only prepare it just before saving. Not as a data container for very frequent access. The most useful things of the document class (except for save_as) are the bangs/observability, imho. And perhaps an occasional value bind. :slight_smile:

1 Like

I’m already working on it.
I just have to load the values from the loaded Document into the variables and change the document only when saving. I had no idea it would be so slow.

There should be no caching necessary. The only possible extra overhead of the document is saving or rebuilding the document constantly, which never is done automatically - by default only happens before a tool gets unloaded or when you trigger a save manually.

An advantage of the document values is that you can bind them directly to the GUI widgets. Then the GUI automatically gets updated when the document value changes and the document automatically gets updated when the value in the GUI changes.

If you don’t mind sharing your slow version of the tool, we could have a more detailed look at this.

Here’s an example from the XRNX example tools:

http://files.renoise.com/xrnx/XrnxStarterPack330.zip

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

-- documents_and_views

-- as already noted in 'available_controls'. views can also be attached to 
-- external document values, in order to seperate the controller code from the 
-- view code. We're going to do this tight now and do start by create a very 
-- simple example document. Please have a look at Renoise.Document.API for more 
-- detail about such documents


-- DOCUMENT

-- create a simple document
local example_document = renoise.Document.create("ExampleDocument") {
  my_flag = false,
  some_velocity = 127,
  pad_x = 0.5,
  pad_y = 0.5
}

-- notifier callbacks
local function my_flag_notifier()
  local my_flag_value = example_document.my_flag.value
  
  show_status(("'my_flag' changed to '%s' by either the GUI "..
    "or something else..."):format(my_flag_value and "True" or "False"))
end

local function some_velocity_notifier()
  local some_velocity = example_document.some_velocity.value

  show_status(("'some_velocity' value changed to '%s' by either the GUI ".. 
    "or something else..."):format(some_velocity))
end

local function pad_value_notifier()
  local x, y = example_document.pad_x.value, example_document.pad_y.value

  show_status(("'pad_xy' value changed to '%.2f,%.2f' by either the GUI ".. 
    "or something else..."):format(x, y))
end

-- attach to the document 
example_document.my_flag:add_notifier(my_flag_notifier)
example_document.some_velocity:add_notifier(some_velocity_notifier)

example_document.pad_x:add_notifier(pad_value_notifier)
example_document.pad_y:add_notifier(pad_value_notifier)


-- GUI

function documents_and_views()

  local vb = renoise.ViewBuilder()

  local DIALOG_MARGIN = renoise.ViewBuilder.DEFAULT_DIALOG_MARGIN
  local CONTENT_SPACING = renoise.ViewBuilder.DEFAULT_CONTROL_SPACING

  -- now we pass over the document struct to the views
  local checkbox_row = vb:row {
    vb:text { 
      text = "my_flag", 
      width = 80 
    },
    vb:checkbox { 
      bind = example_document.my_flag --> bind
    }
  }
  
  local valuebox_row = vb:row {
    vb:text { 
      text = "some_velocity", 
      width = 80 
    },
    vb:valuebox {
      bind = example_document.some_velocity, --> bind
      min = 0, 
      max = 0x7f
    }
  }
  
  local xypad_row = vb:row {
    vb:xypad {
      bind = { --> bind
        x = example_document.pad_x, 
        y = example_document.pad_y
      },
      width = valuebox_row.width,
    }
  }
  
  renoise.app():show_custom_dialog(
    "Documents & Views", 
    
    vb:column {
      margin = DIALOG_MARGIN,
      spacing = CONTENT_SPACING,
      uniform = true,
 
      vb:column {
        spacing = CONTENT_SPACING,
        
        checkbox_row,
        valuebox_row
      },
      xypad_row
    }
  )
end

I’m surprised. Reading example_document.pad_x.value is magnitudes slower than if stored in a temporary variable (on my computer). Maybe it’s over-optimization in most cases, idk.

hello,
unfortunately I have already rewritten the code back to use global variables.

Of course a temporary Lua variable is faster - nothing can be faster than that within Lua, but this should not be the bottleneck here.

For synthesis/sample-rendering it seems to be.

Just reading a document value won’t be the problem, but a document (class) property lookup will indeed also create some overhead. Just like calling one of multiple Lua or Renoise Lua API functions.

But this then is more a general optimization problem: there’s no need to do something again and again in a loop body, if the result is always the same.

Well, exactly as you said:

There’s no need to create a complete, second level cache for all document values. Simply copy & cache them when it really hurts.

For example:

function slow_function()
  for i=1,1000,1 do
    calculate_something(my_document.some_property.value)
  end
end

function get_some_property_value()
  return 55
end

function another_slow_function()
  for i=1,1000,1 do
    calculate_something(get_some_property_value())
  end
end

function faster_function()
  local some_property = my_document.some_property.value
  for i=1,1000,1 do
    calculate_something(some_property)
  end
end

function another_faster_function()
  local some_property = get_some_property_value()
  for i=1,1000,1 do
    calculate_something(some_property)
  end
end

Moving anything (Lua function calls, Renoise API calls) out of the loop, when the result does not change within the loop, of course always will be faster. If that’s relevant or necessary, depends on how heavy or time critical the loop actually is. When for example calculating a new sample buffer, this indeed will make a big difference in summary. In general, it won’t.

Reading this again, this maybe exactly was the problem?

And I seem to have created more confusion than being helpful here. Sorry guys. :wink:
Hopefully the examples help to clear this up.

yes,
i read and write value right into doc variable without creating local instance like in slow_function().
this is really unusable for my needs.

btw:
exist some tool to measure time spended in function ? or just measure using.os.clock ?
memory leakage ?
thanks

I think it was probably some misuse of terminology on my part (I’m not an engineer!).

The easy way is to use testpad.lua and something like

local start_time = os.clock()
for i = 1, 100000 do
 -- whatever
end
print(os.clock()-time)

I think there might be one more small optimization you can do (saving a ‘lookup’ in the tool you posted), but I didn’t check the difference…

    local set = new_buffer.set_sample_data
    for i=1, elements do
    [...]
    set(new_buffer, 1,i,gauss[i])

hello,
I’ll try it. thanks.
I was looking for something to avoid rehashing when writing to the table. But it seems that table.setn() is deprecated.May be this helps.