Jump to content


Photo

[help] performance: execute similar functions at the same time?

iterate performance functions

  • Please log in to reply
13 replies to this topic

#1 Raul (ulneiz)

Raul (ulneiz)

    Guruh Motha Fakka is Levitating and Knows Everything About Renoise Member

  • Normal Members
  • PipPipPipPipPipPipPipPipPipPipPipPipPip
  • 1290 posts
  • Gender:Male
  • Location:Spain

Posted 21 March 2018 - 12:26

I have another specific doubt about how to correctly program certain things...

 

Imagine that you have a function "A", function_a(), that takes approximately 200ms to execute, for example, manipulating properties of certain objects, using heavy iterations:

  • start of the function_a(): 0ms
  • end of the function_a(): ~200ms

Now, imagine that you have a function "B", function_b() that does exactly the same as the function "A", but manipulates other objects identical to the previous ones. As it does the same process, it will take the same.

  • start of the function_b(): 0ms
  • end of the function_b(): ~200ms

I understand that a function will be executed when the previous one ends:

 

main_function()

  return function_a(), function_b()

end

 

The execution time will be the sum of the two:

  • start of the main_function(): 0ms
  • end of the main_function(): ~400ms

Does this really work like this? Is there any way to force to start several functions at exactly the same moment? Think big. Imagine that you want to execute 20 functions that do the same for different objects, through iteration. I am exaggerating the figures to understand it.

 

A concrete case:

 

A function that iterates to change the color of 200 buttons in a panel A:

function change_color_panel_a()
  for i = 1, 200 do
    vb.views["ID_BUTTONS_A_"..i].color = {200,100,050}
  end
end

Other function equal, in panel B:

function change_color_panel_b()
  for j = 1, 200 do
    vb.views["ID_BUTTONS_B_"..j].color = {200,100,050}
  end
end

The process of this function as it would be?:

function change_color_all()
  return change_color_panel_a(), change_color_panel_b()
end

j = 1 will it start after i = 200?

 

or j = 1 is executed in parallel with i = 1 and likewise for the rest?

 

What would be the most correct procedure to iterate massively.

 

The objective is to understand what is the best way to program to achieve heavy iterations in the shortest possible time of total execution.

 

Imagine that you want to change the color of 2000 buttons of different panels.

 


:excl: Development of my tool: GT16-Colors

 

:excl: My API wishlist R3.1 (updated 24 July 2017):

Spoiler

 

:excl: My Renoise 3.1 wishlist (updated 26 September 2017):

Spoiler

#2 encryptedmind

encryptedmind

    Big GrandDaddy Member

  • Normal Members
  • PipPipPipPipPipPipPipPip
  • 493 posts
  • Gender:Male
  • Location:India
  • Interests:Jazz, malware, world travel, women

Posted 21 March 2018 - 13:02

I am answering to understand your question better.

This seems to be a function optimisation problem. Try loop unrolling like:

for ( ;;; ){
a[0]=1;
a[1]=2;
....
..
a[25]=26;
break;
}

instead of for(0 to 25)...

Chunking also helps; like breaking down the iteration list into well performing sub functions which are individually unrolled or in shorter iterations.

For a techniques list I find this book rather nice, Code Optimisation by Kris Kaspersky. This for optimal use of algorithms, could work on interpreted environments too.
https://www.amazon.c...e/dp/8176568686

In interpreted environments like Lua threading is not usually implemented in the scale of truly OS native compiled enviroments that make use of OS APIS and scheduling primitives directly. If Lua does indeed have some sort of threading construct then you should investigate that further.

Does Lua make use of synchronisation primitives like Mutex, Semaphore, Critical sections or delegates...if so then you can use them or simple lock primitives to implement a custom made threading system. But this won't be the same thing as an OS thread which actually preempts the CPU time slice and get scheduled by the OS thread scheduler. So you might have to increase the priority levels of each Lua thread indirectly to gain more CPU time.

From a stackoverflow answer:
"All of the Lua threading modules that exist create alternate Lua instances for each thread. Lua-lltreads just makes an entirely new Lua instance for each thread; there is no API for thread-to-thread communication outside of copying parameters passed to the new thread. LuaLanes does provide some cross-connecting code."

http://lualanes.github.io/lanes/

https://stackoverflo...-as-i-expect-it

Edited by encryptedmind, 21 March 2018 - 13:07.

encryptedmind
Victor Marak

uZIK|mAInD|Z0FTwA-RE

marak.bandcamp.com

#3 Raul (ulneiz)

Raul (ulneiz)

    Guruh Motha Fakka is Levitating and Knows Everything About Renoise Member

  • Normal Members
  • PipPipPipPipPipPipPipPipPipPipPipPipPip
  • 1290 posts
  • Gender:Male
  • Location:Spain

Posted 21 March 2018 - 13:19

Hi :)


function pht_a()
  for a = 1, 200 do
    print ("a =", a)
  end
end

function pht_b()
  for b = 1, 200 do
    print ("b =", b)
  end
end

function pht_c()
  for c = 1, 200 do
    print ("c =", c)
  end
end

function pht_main_1()
  return pht_a(), pht_b(), pht_c()
end

function pht_main_2()
  pht_a() pht_b() pht_c()
end

function pht_main_3()
  for a = 1, 200 do
    print ("a =", a)
  end
  for b = 1, 200 do
    print ("b =", b)
  end
  for c = 1, 200 do
    print ("c =", c)
  end
end

function pht_main_4()
  for i = 1, 200 do
    print ("a =", i)
    print ("b =", i)
    print ("c =", i)
  end
end


BUTTON_TEST = vb:column {
  vb:button {
    text = "BUTTON TEST",
    notifier = function() pht_main_4() end
  }
}

I have done basic tests with print, with several functions "main_x" to see the behavior. Apparently, even if you slice the interactions into smaller pieces, you'll have to execute them all, too. The set or total amount will take the same time.

 

Since I started with LUA, I have always thought that "one thing is after another". But if there is some way to force several functions to run at the same time, it would be great to know and to squeeze it to the fullest.

 

Apparently, processors like a 4-core i7 have a very high performance power. You should be able to run several functions at once without "suffer". I get the feeling that I am not taking advantage of the processing power of the CPU with my scripts with LUA.


:excl: Development of my tool: GT16-Colors

 

:excl: My API wishlist R3.1 (updated 24 July 2017):

Spoiler

 

:excl: My Renoise 3.1 wishlist (updated 26 September 2017):

Spoiler

#4 encryptedmind

encryptedmind

    Big GrandDaddy Member

  • Normal Members
  • PipPipPipPipPipPipPipPip
  • 493 posts
  • Gender:Male
  • Location:India
  • Interests:Jazz, malware, world travel, women

Posted 21 March 2018 - 13:24

So did you search something on synchronisation primitives with Lua?

Alternatively, using the for loop in all cases mean that comparisons or iterations are going to happen at all times. In such cases.,
maybe a simple unrolling without any for loop might help.

a=i;b=i and so on...for all the 1000 objects it's a one time type but that is the only way you can avoid loops.
encryptedmind
Victor Marak

uZIK|mAInD|Z0FTwA-RE

marak.bandcamp.com

#5 joule

joule

    Guruh Motha Fakka is Levitating and Knows Everything About Renoise Member

  • Normal Members
  • PipPipPipPipPipPipPipPipPipPipPipPipPip
  • 1672 posts
  • Gender:Not Telling
  • Location:Sweden
  • Interests:music, philosophy, engineering

Posted 21 March 2018 - 14:08

Does this really work like this? Is there any way to force to start several functions at exactly the same moment? Think big. Imagine that you want to execute 20 functions that do the same for different objects, through iteration. I am exaggerating the figures to understand it.


Unless you're using coroutines or timers, everything will be "sequentially predictable". One thing happens after the other according to where it's placed in the code. And doing one thing after the other is the fastest way to perform a number of tasks in (Renoise) Lua.

Even something like triggering multiple notifiers attached to some observable should be predictable in the sense that the notifier that was first attached will also be the first one to execute.

Edited by joule, 21 March 2018 - 14:10.


#6 Raul (ulneiz)

Raul (ulneiz)

    Guruh Motha Fakka is Levitating and Knows Everything About Renoise Member

  • Normal Members
  • PipPipPipPipPipPipPipPipPipPipPipPipPip
  • 1290 posts
  • Gender:Male
  • Location:Spain

Posted 21 March 2018 - 14:35

Unless you're using coroutines or timers, everything will be "sequentially predictable". One thing happens after the other according to where it's placed in the code. And doing one thing after the other is the fastest way to perform a number of tasks in (Renoise) Lua.

Even something like triggering multiple notifiers attached to some observable should be predictable in the sense that the notifier that was first attached will also be the first one to execute.

 

Thanks for confirming! So I eliminate these doubts.

 

With a timer would it be possible? As far as I know, you apply a timer to an "X" function, and everything that runs in this "X" function will execute one thing after another. If you use several timers, these will also go one after the other.

 

...

 

Coroutines? I have never used this. Would it be possible to execute 2 functions at the same time with a coroutine? 

 

I guess you mean that coroutines and timers are special cases of "wait" or pause (run later). Then with LUA (Renoise), there will be no way to execute similar functions in parallel, at the same time. If this is the case, it is best to concentrate on each function so that it is as fast as possible, including the process of "one thing after another".


:excl: Development of my tool: GT16-Colors

 

:excl: My API wishlist R3.1 (updated 24 July 2017):

Spoiler

 

:excl: My Renoise 3.1 wishlist (updated 26 September 2017):

Spoiler

#7 danoise

danoise

    Probably More God or Borg Than Human Member

  • Renoise Team
  • PipPipPipPipPipPipPipPipPipPipPipPipPipPipPip
  • 6749 posts
  • Gender:Male
  • Location:Berlin
  • Interests:wildlife + urban trekking

Posted 21 March 2018 - 14:53

I'm now going to claim that there is no "do multiple things at the same time" in a computer.

Everything that gives that impression is simply doing smart stuff, behind the scenes. 

 

And if there was, you'd end up with a lot of complexity anyway - what if method A depends on data which is modified by method B? There are many such pitfalls, which is why multithreaded programming is so complex (and why a modern language like Rust has gained so much popularity in a relatively short amount of time). 

 

But, back to the subject. 

Encryptedmind is on to something when he mentions chunking (splitting a heavy process into smaller/lighter parts). 

The problem is of course, that unless you have a mechanism for handing back time to the main process (in our case, the Renoise GUI), then nothing is gained from splitting up things. Quite the contrary, you just make things more complicated and harder to read.  

 

The solution is slicing. This takes your function and processes it in a lua coroutine (an isolated lua instance) with the ability to give back time via a special method called yield(). There is a nice demonstration of how it works on our github page. 

https://github.com/renoise/xrnx

 

Download the XRNX starter pack and check out the source code for the ExampleSlicedProcess tool

 

And don't worry that multi-core computing power is going to waste. Usually, and especially with a _scripting_ language, it's better that the host (Renoise in this case) is distributing the jobs across the cores. 


Tracking with Stuff. API wishlist | Soundcloud


#8 joule

joule

    Guruh Motha Fakka is Levitating and Knows Everything About Renoise Member

  • Normal Members
  • PipPipPipPipPipPipPipPipPipPipPipPipPip
  • 1672 posts
  • Gender:Not Telling
  • Location:Sweden
  • Interests:music, philosophy, engineering

Posted 21 March 2018 - 15:01

Thanks for confirming! So I eliminate these doubts.
 
With a timer would it be possible? As far as I know, you apply a timer to an "X" function, and everything that runs in this "X" function will execute one thing after another. If you use several timers, these will also go one after the other.
 
...
 
Coroutines? I have never used this. Would it be possible to execute 2 functions at the same time with a coroutine? 
 
I guess you mean that coroutines and timers are special cases of "wait" or pause (run later). Then with LUA (Renoise), there will be no way to execute similar functions in parallel, at the same time. If this is the case, it is best to concentrate on each function so that it is as fast as possible, including the process of "one thing after another".


No, using timers will only make the execution time even less predictable. (And coroutines won't help you either, so I don't know what danoise is talking about. Maybe coroutines at least will utilize multi-threading that 'normal' lua doesn't? I don't know, but it doesn't seem relevant to your question).)

The best thing you can do is do one thing directly after the other + optimimze your code :)

#9 danoise

danoise

    Probably More God or Borg Than Human Member

  • Renoise Team
  • PipPipPipPipPipPipPipPipPipPipPipPipPipPipPip
  • 6749 posts
  • Gender:Male
  • Location:Berlin
  • Interests:wildlife + urban trekking

Posted 21 March 2018 - 15:29

I don't know what danoise is talking about. 

 

We both agree that essentially, the reply to the question is a solid "no". 

 

I was just pointing out that, if you want to start a crazy amount of non-blocking processes, this is in fact possible :-)


Tracking with Stuff. API wishlist | Soundcloud


#10 Raul (ulneiz)

Raul (ulneiz)

    Guruh Motha Fakka is Levitating and Knows Everything About Renoise Member

  • Normal Members
  • PipPipPipPipPipPipPipPipPipPipPipPipPip
  • 1290 posts
  • Gender:Male
  • Location:Spain

Posted 21 March 2018 - 15:40

@Danoise, @Joule. Ok. I think this topic is already clear...

 

I must keep in mind the philosophy of "one thing after another", and try to make functions as optimized as possible to obtain the least possible execution time.

 

I asked this question because, in case of using a large window GUI, with multiple objects with modifiable properties that have to do with appearance, if you make changes to multiple objects, it is possible to cause graphic lags when executing a specific function that changes the GUI . And this is even more unpleasant if the "textures" are enabled (I still do not understand why it is so weighed).

 

The Danoise example would be valid for heavy processes, blocking the GUI for a long time, several seconds, preventing Renoise's famous warning window from appearing, for executing a long process.

 

Although it has its limitations, it is also interesting to know it. Personally, I do not like that the undo (or redo) is also divided according to cuts in the function. I guess there's no other choice. In general, I like that an action corresponds to a single step.


:excl: Development of my tool: GT16-Colors

 

:excl: My API wishlist R3.1 (updated 24 July 2017):

Spoiler

 

:excl: My Renoise 3.1 wishlist (updated 26 September 2017):

Spoiler

#11 encryptedmind

encryptedmind

    Big GrandDaddy Member

  • Normal Members
  • PipPipPipPipPipPipPipPip
  • 493 posts
  • Gender:Male
  • Location:India
  • Interests:Jazz, malware, world travel, women

Posted 21 March 2018 - 21:57

This is mostly possible on native code but I don't think it's possible to write to a memory address of a variable directly and just call refresh for UI code. This saves any sort of iteration as it can be be a singular write operation or a series of write operations depending on how the data is structured in memory. For race conditions and deadlocks, locks and sync primitives are the way to go, infact they can be implemented by hand too. But Lua is mostly linear even on multi-core setups because each object is a separate instance meaning you can't pass data directly without an interface. This also means that any multi threading activity won't work as expected. You can't boiler plate some inline asm as you can in C/C++ so I agree that this is not the solution.
encryptedmind
Victor Marak

uZIK|mAInD|Z0FTwA-RE

marak.bandcamp.com

#12 joule

joule

    Guruh Motha Fakka is Levitating and Knows Everything About Renoise Member

  • Normal Members
  • PipPipPipPipPipPipPipPipPipPipPipPipPip
  • 1672 posts
  • Gender:Not Telling
  • Location:Sweden
  • Interests:music, philosophy, engineering

Posted 22 March 2018 - 10:40

@Danoise, @Joule.

I asked this question because, in case of using a large window GUI, with multiple objects with modifiable properties that have to do with appearance, if you make changes to multiple objects, it is possible to cause graphic lags when executing a specific function that changes the GUI . And this is even more unpleasant if the "textures" are enabled (I still do not understand why it is so weighed).

 

I can't see that a simple one-shot property change would cause any lag, even when done on a lot of vb objects. But for example, maybe you're trying to do more intense gfx stuff - and you're doing it way too "smoothly"? LUA is a scripting language, after all. As an example: if I wanted to fade the color on a lot of buttons simultaneously and noticed it was lagging, an idle timer could be used to make sure fades were done as smooth as possible, without lagging the whole GUI.

 

So far, I've found that the Lua API is "fast enough" in almost any case if you do things the right way.

 

PS. About the texture issue, I've noticed that vb is a bit slow when it comes to dealing with larger bitmap images (vb:bitmap). Maybe it's related. I used bitmaps as background for piano roll stripes, and rebuilding these objects when scrolling was way too heavy, so I had to resort to using rack styles for these stripes.


Edited by joule, 22 March 2018 - 10:43.


#13 Raul (ulneiz)

Raul (ulneiz)

    Guruh Motha Fakka is Levitating and Knows Everything About Renoise Member

  • Normal Members
  • PipPipPipPipPipPipPipPipPipPipPipPipPip
  • 1290 posts
  • Gender:Male
  • Location:Spain

Posted 22 March 2018 - 12:34

I can't see that a simple one-shot property change would cause any lag, even when done on a lot of vb objects. But for example, maybe you're trying to do more intense gfx stuff - and you're doing it way too "smoothly"? LUA is a scripting language, after all. As an example: if I wanted to fade the color on a lot of buttons simultaneously and noticed it was lagging, an idle timer could be used to make sure fades were done as smooth as possible, without lagging the whole GUI.

 

So far, I've found that the Lua API is "fast enough" in almost any case if you do things the right way.

 

PS. About the texture issue, I've noticed that vb is a bit slow when it comes to dealing with larger bitmap images (vb:bitmap). Maybe it's related. I used bitmaps as background for piano roll stripes, and rebuilding these objects when scrolling was way too heavy, so I had to resort to using rack styles for these stripes.

 

I can comment on my exact experience with this matter (taking as starting a CPU i7 and a mid-range graphics card, quite powerful and a big window tool with multiple objects):

  1. (with textures = none ) Modifying a specific property (one color) to multiple objects (> 1000) is very fast (almost instantaneous). The LUA code, using massive iterations, goes very very fast. You are only modifying the color, by pressing a simple button to execute the function in charge of doing it. This works reasonably well.
  2. (with textures = none ) If instead of using a button to execute the function for modify the colors, you use a slider, you not only modify the color, but you are also moving a slider graphically. Here, slight lags can be presented in the GUI.
  3. (with textures = default or other). I have verified that the use of textures in Renoise preferences drastically influence when doing something that involves changing the size of objects. Modify the size of a Rack, modify the size of a button, or whatever, in a specific scenario, defined in a very large window with a multitude of objects (hundreds of buttons). Apparently, textures should be resized each time you modify some dimension (use remove_child of a rack, or visible = false, or modify the size of multiple buttons by massive iteration). A rack has its own texture. A button has its own texture, any object has an associated texture (BMP image), which must be resized. It seems that resizing these textures is a bit heavy, when it should be more fluid (I think). The LUA code goes very very fast. But if you use a thema with the textures enabled, you may have a notorious lag problem in tools with very large windows, with many objects.

If you build tools with small windows, with few objects to modify, you will not notice lag in most cases. I suspect that the resizing of the textures generates lags, it does not work fast, it is much slower than the execution speed of the LUA code, and it will be noticed in the heaviest cases (large windows with hundreds of small objects, each with its texture)).

  • Lua time code                   |----------------> done!
  • textures time ¿to resize?  |--------------------------------------------------------------------------------------------------------------------> done! = graphic lag

It would be nice if Danoise confirmed this. And I have used a 10 ms timer to delay the resizing of a wraparound rack, when you hide (visible = false) another internal rack, and you can get an apparently lower lag behavior. But a programmer should not be playing with strange timers to adjust graphical behaviors.

 

I have reached the point that it is necessary to disable the textures (Textures = none). If you want to control a large and dynamic window (resizable size objects), if you disabling the textures, the tool goes like a bullet.

 

This also makes me think that Renoise can present notorious graphics problems in very large windows (2K or 4K monitors) if you have the textures enabled. If you have used Renoise in a 4k and have strange behavior in the graphics, try to disable the textures, simply.

 

If in the future Taktik wants to improve the Renoise GUI, or improve the load of the textures, or simply delete them and bet on vector images, even if the final aspect is more flat. I do not know what would be the easiest solution, but it seems clear that the textures (BMP images that are resized) are an important graphic ballast.

 

If you want to do tests... Build a one-button class. Iterating, build a window of HD size (~1920x1080 px) with several panels and inside place the same buttons (>1000 for example), to be able to modify its size, or color, using a shutter button, a slider ... Try to use it with textures or disabling the textures, and compare. The difference of the graphical performance is notorious.

 

I point out all this, because I myself have fallen into the error of blaming the LUA code for "slow", when the problem is directly related to the loading / resizing of the textures. Obviously, if you use a static window, you will not notice anything in many cases, because you are not modifying any dimension, nor dragging any object from its place.

 

I do not know exactly what is the cause. But I know it has to do directly with the textures that Renoise uses:

  1. Configure the textures: Renoise... Preferences/Theme: Textures = None (or Default...)
  2. Any type of texture (Default or Dotted or Eddged or Jeans or Metal) will work the same (same performance). All are formed by the same number and dimensions of BMP images. And it does not matter if you redimension these images. The graphic performance will be the same.
  3. Root folder of textures (Windows): C:\Program Files\Renoise 3.1.1\Resources\Skin\Textures

Edited by Raul (ulneiz), 22 March 2018 - 12:46.

:excl: Development of my tool: GT16-Colors

 

:excl: My API wishlist R3.1 (updated 24 July 2017):

Spoiler

 

:excl: My Renoise 3.1 wishlist (updated 26 September 2017):

Spoiler

#14 ffx

ffx

    Composes without Wires burns Directly from Brain to DVD that is already in Store Member

  • Normal Members
  • PipPipPipPipPipPipPipPipPipPipPipPipPipPip
  • 3258 posts
  • Gender:Not Telling

Posted 22 March 2018 - 17:45

I would assume that changing/drawing to the GUI of Renoise can be a bottleneck, but not raw LUA code. If you don't do heavy realtime stream processing like DSP, I wouldn't care too much about minor speed optimizations on syntax level. The Renoise GUI does not seem to have any kind of queue, for sorting out unnecessary multiple actions per frame (I rally have no idea how to design a GUI, I can only draw conclusions about the current design by observation). Instead you will easily block Renoise for seconds, e.g. if you setup a huge context menu content or setting the status line multiple times. I would assume this is also the case while building custom windows. I am pretty sure that the LUA interpreter itself is already fast enough.

 

If you really want to benchmark a raw portion of code, then do not even use "print", because it will access the GUI.