|
|
|
08-29-2020, 03:55 AM
|
#41
|
Human being with feelings
Join Date: Apr 2011
Posts: 3,458
|
Lua tables: accessing all indexes of a hash table vs an array table
Following from the previous post
Code:
reaper.ClearConsole()
-- Create the tables
local strings = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "100", "101", "102", "103", "104", "105", "106", "107", "108", "109", "110", "111", "112", "113", "114", "115", "116", "117", "118", "119", "120", "121", "122", "123", "124", "125", "126", "127", "128", "129", "130", "131", "132", "133", "134", "135", "136", "137", "138", "139", "140", "141", "142", "143", "144", "145", "146", "147", "148", "149", "150", "151", "152", "153", "154", "155", "156", "157", "158", "159", "160", "161", "162", "163", "164", "165", "166", "167", "168", "169", "170", "171", "172", "173", "174", "175", "176", "177", "178", "179", "180", "181", "182", "183", "184", "185", "186", "187", "188", "189", "190", "191", "192", "193", "194", "195", "196", "197", "198", "199", "200", "201", "202", "203", "204", "205", "206", "207", "208", "209", "210", "211", "212", "213", "214", "215", "216", "217", "218", "219", "220", "221", "222", "223", "224", "225", "226", "227", "228", "229", "230", "231", "232", "233", "234", "235", "236", "237", "238", "239", "240", "241", "242", "243", "244", "245", "246", "247", "248", "249", "250", "251", "252", "253", "254", "255", "256", "257", "258", "259", "260", "261", "262", "263", "264", "265", "266", "267", "268", "269", "270", "271", "272", "273", "274", "275", "276", "277", "278", "279", "280", "281", "282", "283", "284", "285", "286", "287", "288", "289", "290", "291", "292", "293", "294", "295", "296", "297", "298", "299", "300", "301", "302", "303", "304", "305", "306", "307", "308", "309", "310", "311", "312", "313", "314", "315", "316", "317", "318", "319", "320", "321", "322", "323", "324", "325", "326", "327", "328", "329", "330", "331", "332", "333", "334", "335", "336", "337", "338", "339", "340", "341", "342", "343", "344", "345", "346", "347", "348", "349", "350", "351", "352", "353", "354", "355", "356", "357", "358", "359", "360", "361", "362", "363", "364", "365", "366", "367", "368", "369", "370", "371", "372", "373", "374", "375", "376", "377", "378", "379", "380", "381", "382", "383", "384", "385", "386", "387", "388", "389", "390", "391", "392", "393", "394", "395", "396", "397", "398", "399", "400", "401", "402", "403", "404", "405", "406", "407", "408", "409", "410", "411", "412", "413", "414", "415", "416", "417", "418", "419", "420", "421", "422", "423", "424", "425", "426", "427", "428", "429", "430", "431", "432", "433", "434", "435", "436", "437", "438", "439", "440", "441", "442", "443", "444", "445", "446", "447", "448", "449", "450", "451", "452", "453", "454", "455", "456", "457", "458", "459", "460", "461", "462", "463", "464", "465", "466", "467", "468", "469", "470", "471", "472", "473", "474", "475", "476", "477", "478", "479", "480", "481", "482", "483", "484", "485", "486", "487", "488", "489", "490", "491", "492", "493", "494", "495", "496", "497", "498", "499", "500" }
local numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500 }
local t1 = {}
for i = 1, 500 do
t1[strings[i]] = true
end
local t2 = {}
for i = 1, 500 do
t2[numbers[i]] = true
end
-- Measure how fast is to access all their indexes
local start = reaper.time_precise()
for k,v in pairs(t1) do
end
local hash_speed = reaper.time_precise() - start
start = reaper.time_precise()
for i = 1, #t2 do
local v = t2[i]
end
local array_speed = reaper.time_precise() - start
local faster = hash_speed < array_speed and " hash" or "n array"
local perc = faster == " hash" and hash_speed/array_speed or array_speed/hash_speed
perc = string.format( "%.1f", ((1 - perc) * 100))
reaper.ShowConsoleMsg("It is " .. perc .. "% faster to access all indexes of a" .. faster .. " table\n\n\n")--]]
Code:
It is 82% faster to access all indexes of an array table
|
|
|
08-30-2020, 01:15 PM
|
#42
|
Human being with feelings
Join Date: May 2017
Location: Leipzig
Posts: 6,630
|
To my surprise, blitting is slower than gfx.rect or gfx.setpixel.
So the following conclusion:
a) one blit is faster than thousand of drawn objects. So if you need static objects who are built around many pixels/rectangles, you should draw them into on image and blit them again and again.
b) if you need to constantly change the pixels/rectangles, simply drawing them is faster and therefore an option.
So, depending on the amount of changed elements in your gfx-window, static elements should be drawn once into an image and blit, while dynamic ones should be drawn directly using Reapers-gfx-drawing-functions.
That way you get the best performance-flexibility-ratio.
Code:
gfx.init()
-- create image
dest=1
-- make an image with a rectangle that can be blitted
gfx.setimgdim(dest,10,10)
gfx.dest=dest
gfx.rect(1,1,10, 10)
-- make an image with a pixel that can be blitted
gfx.setimgdim(dest+1,1,1)
gfx.dest=dest+1
gfx.setpixel(1,1,1)
gfx.dest=-1
-- check drawing rectangles
A=reaper.time_precise()
for a=0, 100 do
for i=0, 100000 do
gfx.rect(1,1,10,10)
end
end
Result_Rect=reaper.time_precise()-A
-- check blitting rectangles
A=reaper.time_precise()
for a=0, 100 do
for i=0, 100000 do
gfx.blit(dest,1,0)
end
end
Result_RectBlit=reaper.time_precise()-A
-- check drawing pixels
A=reaper.time_precise()
for a=0, 100 do
for i=0, 100000 do
gfx.setpixel(1,1,1)
end
end
Result_Pixel=reaper.time_precise()-A
-- check blitting pixels
A=reaper.time_precise()
for a=0, 100 do
for i=0, 100000 do
gfx.blit(dest+1,1,0)
end
end
Result_PixelBlit=reaper.time_precise()-A
|
|
|
11-17-2020, 09:07 AM
|
#43
|
Human being with feelings
Join Date: Dec 2017
Location: Brazil
Posts: 2,011
|
So what happen in a defer loop when the function need more time than one defer loop? Will it jump defer loops?
|
|
|
11-20-2020, 01:17 AM
|
#44
|
Human being with feelings
Join Date: Apr 2011
Posts: 3,458
|
Quote:
Originally Posted by daniellumertz
So what happen in a defer loop when the function need more time than one defer loop? Will it jump defer loops?
|
Reascripts are run in the GUI thread at about 32-33 times per second rate (therefore, each defer cycle takes about 30-31ms). That speed is the maximum one when the code that is deferred is very light. If the code needs more than this time then the defer speed ratio will fall (this means that the GUI refresh rate will fall too), so that Reaper never misses what the code asked it to do.
Example:
Code:
-- this function takes about 0.5 seconds to complete on my system
local function test()
for i = 1, 149783500 do
end
end
-- measure function time on your system
local start, time = reaper.time_precise()
test()
time = reaper.time_precise() - start
reaper.ShowConsoleMsg("the function took " .. time .. " seconds\n")
-- let's run the slow function in a defer loop
local function main()
test()
reaper.defer(main)
end
main()
Running the slow function in a deferred loop you will see that the speed at which Reaper refreshes the GUI will drop, because it waits for the code in the defer loop to finish. In my system, this will make Reaper refresh the GUI every 0.5 seconds.
Last edited by amagalma; 11-20-2020 at 05:46 AM.
|
|
|
11-20-2020, 05:45 AM
|
#45
|
Human being with feelings
Join Date: Dec 2017
Location: Brazil
Posts: 2,011
|
amagalma Thanks for the explanation very complete and with exemples!! will try to benchmark some of my GUI at least to know how they are running.
|
|
|
11-20-2020, 02:15 PM
|
#46
|
Human being with feelings
Join Date: Apr 2011
Posts: 3,458
|
Here is an example code that gives the median time of 100 defer cycles:
Code:
local times = {}
for i=1, 100 do
times[i]= true
end
local cycles = 0
local function main()
cycles = cycles + 1
times[cycles] = reaper.time_precise()
if cycles == 100 then
local sum = 0
for i = #times, 2, -1 do
local diff = times[i] - times[i-1]
sum = sum + diff
end
local median = (sum/(#times-1))*1000
reaper.ShowConsoleMsg("Median defer cycle time: " .. median .. " ms\n")
return
else
reaper.defer(main)
end
end
main()
Last edited by amagalma; 11-20-2020 at 11:38 PM.
|
|
|
11-23-2020, 09:23 AM
|
#47
|
Human being with feelings
Join Date: Dec 2017
Location: Sunny Siberian Islands
Posts: 962
|
Not a benchmark, but just an interesting find for optimizing the speed of MIDI scripts. I think it will be useful to everyone who is interested in this topic. Disabling MIDI sorting greatly improves processing performance.
This does not work everywhere (as I understand it, this works in scripts that use the MIDI_SetNote and MIDI_SetCC functions), but if it work, it increases the speed of work at times! It's about a simple trick with MIDI_DisableSort.
It is enough just to put the
Code:
reaper.MIDI_DisableSort(take)
at the beginning of the MIDI script and the
Code:
reaper.MIDI_Sort(take)
at the end.
Voila
|
|
|
11-23-2020, 06:20 PM
|
#49
|
Human being with feelings
Join Date: May 2017
Location: Leipzig
Posts: 6,630
|
Quote:
Originally Posted by daniellumertz
So what happen in a defer loop when the function need more time than one defer loop? Will it jump defer loops?
|
To add to Amagalma's notes:
You can basically see Reaper's scheduling in the gui-thread(which includes scripts as well) as
run script/defer-loop
GUI-refresh
run script/defer-loop
GUI-refresh
run script/defer-loop
GUI-refresh
etc.
That means, if anything of it takes longer, it will block the next thing until it's finished(so if you wrote an endless loop in a script, the next thing in the list never happens).
So if the GUI-refresh takes longer, the next script/defer-loop will be run later. And if a defer-loop takes longer, the next GUI-refresh will happen later.
You can try this by making a defer-loop running several seconds. If you click on Reaper's window, it will not react to your mouseclicks.
The only thing still running is the audiothread(for playback/recording).
Having said that:
if you have a defer-loop that potentially runs too long, you can actually split it into two functions:
Code:
Counter=0
function firstpart()
Counter=Counter+1
reaper.ShowConsoleMsg(Counter.." - First Part!\n")
reaper.defer(secondpart)
end
function secondpart()
Counter=Counter+1
reaper.ShowConsoleMsg(Counter.." Second Part!\n")
reaper.defer(firstpart)
end
firstpart()
So you jump back and forth between the two functions with each next defer-call, where firstpart () does the first part of the calculation and secondpart() does the second part of the calculation.
Between these two defer-calls, Reaper can update GUI, run other deferred scripts and return to your next deferred-function, so you can prevent laggy UI-refresh if you have a script that is more ressourcehungry.
PS: Depending on the nature and structure of your code, you can run firstpart() multiple times, so it will be run more often per defer-cycle-round.
Code:
Counter=0
function firstpart()
Counter=Counter+1
reaper.ShowConsoleMsg(Counter.." - First Part!\n")
reaper.defer(secondpart)
end
function secondpart()
Counter=Counter+1
reaper.ShowConsoleMsg(Counter.." Second Part!\n")
reaper.defer(firstpart)
end
firstpart()
firstpart()
That way you can speed up code to higher update-rate. One thing though: this mostly works only with pure Lua-code as well with reaper.time_precise(). Every other function of Reaper seems to be only callable 30 times per second.
So you can calculate pure mathematical stuff faster with this, access files, etc, but you cannot get i.e. the position of the playcursor more often than 30 times per second using reaper.GetPlayCursorPosition(), no matter how much you try to speed it up by running the deferred function multiple times.
How fast you can go depends on the actual calculation.
If it's a fast calculation, you can run firstpart() up to 1024 times without blocking Reaper's GUI or other background-scripts.
If it's resource-hungry, it's probably less than that.
You need to experiment with it a little to get the right speed-balance that doesn't make other scripts/Reaper's GUI laggy.
Last edited by Meo-Ada Mespotine; 11-23-2020 at 06:31 PM.
|
|
|
11-25-2020, 03:07 PM
|
#50
|
Human being with feelings
Join Date: May 2017
Location: Leipzig
Posts: 6,630
|
Another check I made, as I'm in the process of speeding up anything in loading Ultraschall-API I can find, was:
Does Lua's stripping my script of comments while compiling it into bytecode affect the startup of the script?
Answer is: not at all.
I made a script full of
almost 7 MB, as well as another one with
Code:
--[[
SHDUIHFIUODHF
SHDUIHFIUODHF
SHDUIHFIUODHF
...
SHDUIHFIUODHF
SHDUIHFIUODHF
SHDUIHFIUODHF
--]]
where I repeated SHDUIHFIUODHF time and again(also for about 7 MB scriptsize).
I also checked an empty script.
Here's the code I tested it with:
Code:
cmd_id=reaper.NamedCommandLookup("_AID_of_testscript")
A=reaper.time_precise()
for i=0, 10000 do
reaper.Main_OnCommand(cmd_id,0)
end
B=reaper.time_precise()
Time_Needed=B-A
It always was around 0.98 or 0.97 seconds for 10000 starts of the script, no matter if it included the comments or was an empty script.
So I guess, comments don't affect startup-time of your script.
Another excuse gone for not writing comments
|
|
|
11-25-2020, 05:03 PM
|
#51
|
Human being with feelings
Join Date: Dec 2017
Location: Brazil
Posts: 2,011
|
Oh I was thinking About that mespotine thanks for the INFO, very good.
In that path: bigger variables names will slowdown in a significant way?
|
|
|
11-26-2020, 12:22 AM
|
#52
|
Human being with feelings
Join Date: Apr 2011
Posts: 3,458
|
Thanks for the tests mespotine!
I guess, but haven't tested, that a precompiled to bytecode script will have a slight advantage when loading in comparison to a raw one. But this will of course affect only the loading time. It won't affect the speed while running. And I would expect said advantage to be relative to the size and complexity of the script. But I have to test my theory...
|
|
|
11-26-2020, 02:57 AM
|
#53
|
Human being with feelings
Join Date: May 2017
Location: Leipzig
Posts: 6,630
|
Yes, precompiled is faster. It sped up loading of my Api by 30%.
The files tend to be bigger than the sourcefile in my case.
|
|
|
11-27-2020, 12:57 AM
|
#54
|
Human being with feelings
Join Date: Apr 2011
Posts: 3,458
|
Quote:
Originally Posted by Meo-Ada Mespotine
Yes, precompiled is faster. It sped up loading of my Api by 30%.
The files tend to be bigger than the sourcefile in my case.
|
Useful info, thanks!
|
|
|
11-27-2020, 01:02 AM
|
#55
|
Human being with feelings
Join Date: Apr 2011
Posts: 3,458
|
while loops
I haven't tested other cases, but in case you want to exit a while loop when a certain variable reaches a certain number (that you know beforehand), it is faster to use "while true" and do the exit code inside the loop (if i == number then break end) than make the comparison in the while line (while i < number do).
Test code:
Code:
local runs = 100
local total_time1, total_time2 = 0, 0
for i = 1, runs do
local i1 = 0
local start1 = reaper.time_precise()
while true do
i1 = i1 + 1
if i1 == 30000000 then break end
end
local time1 = reaper.time_precise() - start1
total_time1 = total_time1 + time1
local i2 = 0
local start2 = reaper.time_precise()
while i2 < 30000000 do
i2 = i2 + 1
end
local time2 = reaper.time_precise() - start2
total_time2 = total_time2 + time2
end
total_time1 = total_time1/runs
total_time2 = total_time2/runs
local method1, method2 = '"while true"', '"while i2 <"'
local faster = total_time1 < total_time2 and method1 or method2
local perc = (1 - math.min(total_time1,total_time2)/math.max(total_time1,total_time2))*100
reaper.ShowConsoleMsg(
string.format("%s median time :\t %f\n", method1, total_time1) ..
string.format("%s median time :\t %f\n\n", method2, total_time2) ..
faster .. " method is " .. perc .. "% faster\n\n\n"
)
Result:
Code:
"while true" median time : 0.277607
"while i2 <" median time : 0.286703
"while true" method is 3.1725846544927% faster
Last edited by amagalma; 11-27-2020 at 01:12 AM.
|
|
|
12-25-2020, 01:00 PM
|
#56
|
Human being with feelings
Join Date: Dec 2017
Location: Brazil
Posts: 2,011
|
Quote:
Originally Posted by amagalma
It is 11% to 39% faster to store numbers in a table than to store strings.
It is 60% to 67% faster to create an array table (indexed by numbers) than a hash table (indexed by strings).
[/code]
So, if you want to create a big and fast table, and are willing to sacrifice the convenience that hash tables give when programming, and your program could really work without hash tables, then you should stick to tables indexed by numbers. They are about 63% faster (which means they take about 1/3 of the time to be created)
|
Wow 60+% difference this is preaty impressive thanks for testing it out!
I think in a code I am doing I will try to substitute all tables indexed with strings using change all occurrences when I end hahaha
|
|
|
12-25-2020, 04:19 PM
|
#57
|
Human being with feelings
Join Date: May 2017
Location: Leipzig
Posts: 6,630
|
Quote:
Originally Posted by amagalma
I haven't tested other cases, but in case you want to exit a while loop when a certain variable reaches a certain number (that you know beforehand), it is faster to use "while true" and do the exit code inside the loop (if i == number then break end) than make the comparison in the while line (while i < number do).
Test code:
Code:
local runs = 100
local total_time1, total_time2 = 0, 0
for i = 1, runs do
local i1 = 0
local start1 = reaper.time_precise()
while true do
i1 = i1 + 1
if i1 == 30000000 then break end
end
local time1 = reaper.time_precise() - start1
total_time1 = total_time1 + time1
local i2 = 0
local start2 = reaper.time_precise()
while i2 < 30000000 do
i2 = i2 + 1
end
local time2 = reaper.time_precise() - start2
total_time2 = total_time2 + time2
end
total_time1 = total_time1/runs
total_time2 = total_time2/runs
local method1, method2 = '"while true"', '"while i2 <"'
local faster = total_time1 < total_time2 and method1 or method2
local perc = (1 - math.min(total_time1,total_time2)/math.max(total_time1,total_time2))*100
reaper.ShowConsoleMsg(
string.format("%s median time :\t %f\n", method1, total_time1) ..
string.format("%s median time :\t %f\n\n", method2, total_time2) ..
faster .. " method is " .. perc .. "% faster\n\n\n"
)
Result:
Code:
"while true" median time : 0.277607
"while i2 <" median time : 0.286703
"while true" method is 3.1725846544927% faster
|
Hmm, you check in the second while loop for less than
i2 < 30000000
while in the first you check for equal to
i1 == 30000000
You should make them identical to exclude the potential of "less than" needing more computing ressources than "equal to".
|
|
|
12-25-2020, 04:21 PM
|
#58
|
Human being with feelings
Join Date: May 2017
Location: Leipzig
Posts: 6,630
|
Just a thought: I wonder, if the used compilers and platforms for Reaper's code and Lua-embedding have an effect on how the code behaves too.
For instance, Lokasenna mentioned numerous times, that using localized variables is faster than global ones.
I never saw this on my machine no matter how hard I tried and I wonder, if this is due using a different platform than him(he used the linux-version afair, while I use the windows-version) and therefore some optimizations, that can't be achieved on all platforms.
|
|
|
12-28-2020, 04:10 AM
|
#59
|
Human being with feelings
Join Date: Apr 2011
Posts: 3,458
|
Quote:
Originally Posted by Meo-Ada Mespotine
Hmm, you check in the second while loop for less than
i2 < 30000000
while in the first you check for equal to
i1 == 30000000
You should make them identical to exclude the potential of "less than" needing more computing ressources than "equal to".
|
But that is exactly why it is faster! You are missing the point
|
|
|
12-28-2020, 04:15 AM
|
#60
|
Human being with feelings
Join Date: Apr 2011
Posts: 3,458
|
Quote:
Originally Posted by Meo-Ada Mespotine
For instance, Lokasenna mentioned numerous times, that using localized variables is faster than global ones.
I never saw this on my machine no matter how hard I tried and I wonder, if this is due using a different platform than him(he used the linux-version afair, while I use the windows-version) and therefore some optimizations, that can't be achieved on all platforms.
|
It doesn't have to do with platform but with how Lua is made. There is a big difference in speed when using local variables, especially with big tables. Actually, always use local variables, try to avoid global ones
http://lua-users.org/wiki/OptimisingUsingLocalVariables
https://www.lua.org/gems/sample.pdf
https://topic.alibabacloud.com/a/use..._31401147.html
etc..
|
|
|
04-05-2021, 03:04 PM
|
#61
|
Human being with feelings
Join Date: Dec 2017
Location: Brazil
Posts: 2,011
|
I have been bench some code to see what gets selected notes faster looping through all notes or just selected with EnumSelNotes.
https://forum.cockos.com/showpost.ph...6&postcount=10
The result is that looping through all is faster, in my tests
|
|
|
07-12-2022, 08:38 AM
|
#62
|
Human being with feelings
Join Date: Dec 2017
Location: Sunny Siberian Islands
Posts: 962
|
Regarding the test with the replacement of mathematical functions with simple calculations: https://forum.cockos.com/showpost.ph...0&postcount=49
I got curious and tested some simplifications. I'm bad at math and programming, there are doubts about the objectivity of the tests, but I think you will correct me.
I tested groups by comparing simple LUA optimization and common usage.
"//" gives a significant increase compared to math.floor,
but the analogues of the square root and ABS, although they give an increase of ~ 15% in a real project, did not perform so well in this test. Here, the ABS test result is very unstable and shows different results from test to test (sometimes better, sometimes worse).
Maybe you know more fast simplifications? For example with bit shifting or hacks, as in the famous "fast inverse square root"? It would be interesting to test this.
p.s. I will supplement the test with a simplified exponent. When using a constant, the increase in speed is approximately 20% compared to the optimized function!
Code:
reaper.ClearConsole()
local abs = math.abs
local sqrt = math.sqrt
local floor = math.floor
local exp = math.exp
local time = reaper.time_precise()
for i = 1, 100000 do
a1 = math.floor(i/2.153)
end
reaper.ShowConsoleMsg(reaper.time_precise() - time .. " math.floor" .. '\n')
local time = reaper.time_precise()
for i = 1, 100000 do
a2 = floor(i/2.153)
end
reaper.ShowConsoleMsg(reaper.time_precise() - time .. " floor" .. '\n')
local time = reaper.time_precise()
for i = 1, 100000 do
a3 = (i/2.153)//1
end
reaper.ShowConsoleMsg(reaper.time_precise() - time .. " //1" .. '\n\n')
local time = reaper.time_precise()
for i = 1, 100000 do
b1 = math.sqrt(i/2.153)
end
reaper.ShowConsoleMsg(reaper.time_precise() - time .. " math.sqrt" .. '\n')
local time = reaper.time_precise()
for i = 1, 100000 do
b2 = sqrt(i/2.153)
end
reaper.ShowConsoleMsg(reaper.time_precise() - time .. " sqrt" .. '\n')
local time = reaper.time_precise()
for i = 1, 100000 do
b3 = (i/2.153)^0.5
end
reaper.ShowConsoleMsg(reaper.time_precise() - time .. " ^0.5" .. '\n\n')
local time = reaper.time_precise()
for i = -100000, 100000 do
c1 = math.abs(i/2.153)
end
reaper.ShowConsoleMsg(reaper.time_precise() - time .. " math.abs" .. '\n')
local time = reaper.time_precise()
for i = -100000, 100000 do
c2 = abs(i/2.153)
end
reaper.ShowConsoleMsg(reaper.time_precise() - time .. " abs" .. '\n')
local time = reaper.time_precise()
for i = -100000, 100000 do
c3 = (i/2.153)
if c3 < 0 then c3 = c3*-1 end
end
reaper.ShowConsoleMsg(reaper.time_precise() - time .. " if_then" .. '\n\n')
local time = reaper.time_precise()
for i = 0, 1, 0.000001 do
d1 = math.exp(i)
end
reaper.ShowConsoleMsg(reaper.time_precise() - time .. " math.exp" .. '\n')
local time = reaper.time_precise()
for i = 0, 1, 0.000001 do
d2 = exp(i)
end
reaper.ShowConsoleMsg(reaper.time_precise() - time .. " exp" .. '\n')
local time = reaper.time_precise()
for i = 0, 1, 0.000001 do
d3 = 2.7182818284590^(i)
end
reaper.ShowConsoleMsg(reaper.time_precise() - time .. " 2.718^" .. '\n')
Results:
Quote:
0.0049 math.floor
0.0038 floor
0.0021 //1
0.0040 math.sqrt
0.0028 sqrt
0.0033 ^0.5
0.0089 math.abs
0.0068 abs
0.0066 if_then
0.041 math.exp
0.033 exp
0.027 2.718^
|
Last edited by cool; 07-12-2022 at 08:58 AM.
|
|
|
09-11-2023, 01:10 PM
|
#63
|
Human being with feelings
Join Date: Apr 2011
Posts: 3,458
|
Working with Audio
|
|
|
10-14-2023, 04:26 AM
|
#64
|
Human being with feelings
Join Date: Apr 2011
Posts: 3,458
|
And here is another test comparing the various methods of working with reaper arrays in Lua, using cfillion's Lua Profiler:
Test code:
Code:
local profiler = dofile(reaper.GetResourcePath() ..
'/Scripts/ReaTeam Scripts/Development/cfillion_Lua profiler.lua')
reaper.defer = profiler.defer
profiler.attachToWorld()
profiler.run()
local size = 32768
profiler.start()
--------------------------------
for times = 1, 1000 do -- compare 1000 times
profiler.enter('-C1- Create array')
local arr = reaper.new_array(size)
for i = 1, size do
arr[i] = i + 0.3456
end
profiler.leave()
--------
profiler.enter('-C2- Create array from Lua table')
local d = {}
for i = 1, size do
d[i] = i + 0.3456
end
local arr = reaper.new_array(d)
profiler.leave()
--------
profiler.enter('-C3- Create array with FunctionFromEEL')
local EELcreate = reaper.ImGui_CreateFunctionFromEEL([[
arr = 0;
i = 0;
loop(size,
arr[i]=i+1.3456;
i+=1
);
]])
reaper.ImGui_Function_SetValue(EELcreate, 'size', size)
reaper.ImGui_Function_Execute(EELcreate)
local arr = reaper.new_array(size)
reaper.ImGui_Function_GetValue_Array(EELcreate, 'arr', arr)
profiler.leave()
--------
profiler.enter('-R1- Read directly from array')
for i = 1, size do
local z = arr[i]
end
profiler.leave()
--------
profiler.enter('-R2- Read from conv Lua table')
local t = arr.table()
for i = 1, size do
local z = t[i]
end
profiler.leave()
--------
profiler.enter('-R3- Read with FunctionFromEEL')
local EELread = reaper.ImGui_CreateFunctionFromEEL([[
i = 0;
loop(size,
z=arr[i];
i+=1
);
]])
reaper.ImGui_Function_SetValue(EELread, 'size', size)
reaper.ImGui_Function_SetValue_Array(EELread, 'arr', arr)
reaper.ImGui_Function_Execute(EELread)
local z2 = reaper.ImGui_Function_GetValue(EELread, "z")
profiler.leave()
end
--------------------------------
profiler.stop()
Results:
Remarks
As you can see above:
1) If you are not getting the array pre-filled by Reaper but you create it yourself, then it is more than 1.5 times faster (20.05/32.88) to create it by first creating a Lua table and then converting it to an array.
2) If you do the array creation using cfillion's CreateFunctionfromEEL, then it is almost 7 times faster (4.91/32.88)!
3) Reading from an array-converted-to-a-Lua-table is more than 4 times faster (7.21/31.12) than reading directly from the array.
4) Reading the array using cfillion's CreateFunctionfromEEL is almost 8.5 times faster (3.77/31.12) than reading directly from the array, and almost as fast as using EEL per se!
Last edited by amagalma; 10-14-2023 at 05:33 AM.
|
|
|
12-04-2023, 09:57 AM
|
#65
|
Human being with feelings
Join Date: Mar 2016
Location: Italy
Posts: 332
|
math functions vs. scripted math functions
Not sure if this was already discussed somewhere in this thread but math functions like math.abs or math.max are very slow compared to a scripted version of the function
EDIT: literally 3 posts before this...
Using a local variable to store a math function works bit faster but not significantly
Making a function and recalling it is a bit slower than writing the same thing in the main thread but it's not that significant
Code:
local profiler = dofile(reaper.GetResourcePath() ..
'/Scripts/ReaTeam Scripts/Development/cfillion_Lua profiler.lua')
reaper.defer = profiler.defer
profiler.attachToWorld()
profiler.run()
local arr = {}
local arr2 = {}
local arr3 = {}
local arr4 = {}
local arr5 = {}
local arr6 = {}
local size = 32768
local abs = math.abs
profiler.start()
function my_abs()
for i = 1, size do
local number = i-size
if number>=0 then
arr4[i] = number
else
arr4[i] = number*-1
end
end
end
--------------------------------
for times = 1, 100 do -- compare 1000 times
profiler.enter('-C1- math.abs ')
for i = 1, size do
arr[i] = math.abs(i-size)
end
profiler.leave()
--------
profiler.enter('-C2- local abs = math.abs')
for i = 1, size do
arr2[i] = abs(i-size)
end
profiler.leave()
--------
profiler.enter('-C3- scripted abs ')
for i = 1, size do
local number = i-size
if number>=0 then
arr3[i] = number
else
arr3[i] = number*-1
end
end
profiler.leave()
profiler.enter('-C4- recalling scripted abs function()')
my_abs()
profiler.leave()
profiler.enter('-C5- math.max')
for i = 1, size do
arr5[i] = math.max(i-size, i-size-1)
end
profiler.leave()
profiler.enter('-C5- scripted max')
for i = 1, size do
local number = i-size
local number2 = i-size-1
if number>number2 then
arr6[i] = number
else
arr6[i] = number2
end
end
profiler.leave()
end
--------------------------------
profiler.stop()
Last edited by 80icio; 12-04-2023 at 12:30 PM.
|
|
|
12-04-2023, 12:16 PM
|
#66
|
Human being with feelings
Join Date: Dec 2017
Location: Sunny Siberian Islands
Posts: 962
|
Quote:
Originally Posted by 80icio
Not sure if this was already discussed somewhere in this thread but math functions like math.abs or math.max are very slow compared to a scripted version of the function
Using a local variable to store a math function works bit faster but not significantly
Making a function and recalling it is a bit slower than writing the same thing in the main thread but it's not that significant
|
Thanks for the test!
A little higher in the topic, I also compared math functions and their script analogues. But I got unstable results, although almost always in favor of the script.
|
|
|
12-04-2023, 01:23 PM
|
#67
|
Human being with feelings
Join Date: Mar 2016
Location: Italy
Posts: 332
|
Quote:
Originally Posted by cool
Thanks for the test!
A little higher in the topic, I also compared math functions and their script analogues. But I got unstable results, although almost always in favor of the script.
|
I'm guessing the previous test has something wrong, because if I try the code below I'm getting different results
This is a revised version of the prev code snippet with 2 math functions vs 2 scripted functions
If I use profiler.attachToWorld() after I declared all the functions results are closer between them, and (as I would usually guess) the built in math functions perform a bit better.
Either the previous code snippet is not correct or I'm not trying this correctly.
Code:
local profiler = dofile(reaper.GetResourcePath() ..
'/Scripts/ReaTeam Scripts/Development/cfillion_Lua profiler.lua')
reaper.defer = profiler.defer
local arr = {}
local arr2 = {}
local arr3 = {}
local arr4 = {}
local size = 32768
local abs = math.abs
local max = math.max
local function my_abs(input)
local output
if input>=0 then
output = input
else
output = input*-1
end
return output
end
local function my_max(input1, input2)
local output
if input1>=input2 then
output = input1
else
output = input2
end
return output
end
function test()
profiler.enter('-C1- local abs = math.abs')
for i = 1, size do
arr[i] = abs(i-size)
end
profiler.leave()
--------
profiler.enter('-C2- my_abs function()')
for i = 1, size do
local number = i-size
arr2[i] = my_abs(number)
end
profiler.leave()
profiler.enter('-C3- local max = math.max')
for i = 1, size do
arr3[i] = max(i-size, i-size-1)
end
profiler.leave()
profiler.enter('-C4- my_max function()')
for i = 1, size do
local number = i-size
local number2 = i-size-1
arr4[i] = my_max(number,number2)
end
profiler.leave()
end
profiler.attachToWorld()
profiler.run()
profiler.start()
--------------------------------
for times = 1, 100 do -- compare 100 times
test()
end
--------------------------------
profiler.stop()
|
|
|
12-04-2023, 07:49 PM
|
#68
|
Human being with feelings
Join Date: May 2015
Location: Québec, Canada
Posts: 4,967
|
Remove profiler.attachToWorld to measure only the time between enter and leave without paying the instrumentation overhead cost at each function call. It's not much, but at 3 million calls, it adds up.
In the first snippet my_abs is not instrumented while math.abs is. In the second snippet they all are.
If the function itself is tiny (like a simple min/max) the measurement work can easily take longer than the function's own work.
Code:
local size = 32768
local function max1(a, b)
return a < b and b or a
end
local function max2(a, b)
return a < b and b or a
end
local profiler = dofile(reaper.GetResourcePath() ..
'/Scripts/ReaTeam Scripts/Development/cfillion_Lua profiler.lua')
profiler.attachTo('max2')
profiler.run()
local rv
profiler.start()
for times = 1, 100 do -- compare 100 times
profiler.enter('not instrumented')
for i = 1, size do
rv = max1(i-size, i-size-1)
end
profiler.leave()
profiler.enter('instrumented')
for i = 1, size do
rv = max2(i-size, i-size-1)
end
profiler.leave()
end
profiler.stop()
For best results, instrumentation should be removed (or not added in the first place) from very fast & frequently called functions:
Code:
profiler.attachToWorld() -- inject instrumentation into all functions accessible from this scope at this point
profiler.detachFrom('math.max') -- not worth measuring this one
profiler.detachFrom('math') -- de-instrument all of these at once
Last edited by cfillion; 12-04-2023 at 08:12 PM.
|
|
|
12-04-2023, 11:35 PM
|
#69
|
Human being with feelings
Join Date: Mar 2016
Location: Italy
Posts: 332
|
Quote:
Originally Posted by cfillion
Remove profiler.attachToWorld to measure only the time between enter and leave without paying the instrumentation overhead cost at each function call. It's not much, but at 3 million calls, it adds up.
In the first snippet my_abs is not instrumented while math.abs is. In the second snippet they all are.
If the function itself is tiny (like a simple min/max) the measurement work can easily take longer than the function's own work.
Code:
local size = 32768
local function max1(a, b)
return a < b and b or a
end
local function max2(a, b)
return a < b and b or a
end
local profiler = dofile(reaper.GetResourcePath() ..
'/Scripts/ReaTeam Scripts/Development/cfillion_Lua profiler.lua')
profiler.attachTo('max2')
profiler.run()
local rv
profiler.start()
for times = 1, 100 do -- compare 100 times
profiler.enter('not instrumented')
for i = 1, size do
rv = max1(i-size, i-size-1)
end
profiler.leave()
profiler.enter('instrumented')
for i = 1, size do
rv = max2(i-size, i-size-1)
end
profiler.leave()
end
profiler.stop()
For best results, instrumentation should be removed (or not added in the first place) from very fast & frequently called functions:
Code:
profiler.attachToWorld() -- inject instrumentation into all functions accessible from this scope at this point
profiler.detachFrom('math.max') -- not worth measuring this one
profiler.detachFrom('math') -- de-instrument all of these at once
|
|
|
|
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:54 AM.
|