Log in

View Full Version : Runtime variables scope and lifetime


martin53
27th October 2013, 10:55
EDIT 13th November 2013: put discussion result here. The code example is a framework for a versatile and robust RTE usage.

#~ Example needs Gavino's GRunT, GScript and StainlessS' RT_Stats plugins

function RTE(clip c, string ExampleVarFromNonRTE_Parameter) {
#######################################
# Unique Identifier Definition
#######################################
Try { global RTE_InstanceNumber = RTE_InstanceNumber+1 } catch(err) { global RTE_InstanceNumber = 1 }

# Runtime Environment Preparation & Call
GScript(RT_StrReplace(""" #" for use without GScript, use Eval( instead
#######################################
# 1) Unique Global Variables Initialization
#######################################
global RTE_last_frame%%% = -1
# ... more variables as needed

#######################################
# 2) Unique Runtime Function Definition
#######################################
function RTE_%%%(clip c, string JustMyExampleString) {
# ... insert complete runtime code here
# NOTE: Avoid GScript("...") and Eval("...") inside this function, i.e. all functions which expect a script string
Try {
c
Subtitle(string(current_frame - RTE_last_frame%%%))
Subtitle(JustMyExampleString, align=4)
global RTE_last_frame%%% = current_frame
return last
} catch (err) {
assert(false, "RTE_ScriptClip %%%: "+err)
}
}

#######################################
# 3) Unique Runtime Call
#######################################
ARGS = "ExampleVarFromNonRTE_Parameter"
c
#ScriptClip must be a one-liner:
GScriptClip("RTE_%%%(last, "+ARGS+")", local=true, args=ARGS)
""", "%%%", string(RTE_InstanceNumber))) #"
return last
}

ExampleVarFromNonRTE = "hi there"
BlankClip()
StackHorizontal(last.RTE(ExampleVarFromNonRTE), last.RTE("it works"))


(probably this is a reopening of earlier discussions)

Many, especially powerful scripts like 'Srestore()' use the runtime environment to vary processing on frame level.

The variables they use inside the runtime environment are typically 'shared', in that they share one scope. The variables are 'static' in that their lifetime is same as script lifetime. During rendering stage, the value from the previously processed frame is still there when the function (graph node) is called again.

This approach has the advantage, that the function can build the processing on the previous processing, which is crucial for many algorithms. It has the severe disadvantage however, that multiple instances of the function mash up their variables (even worse: no matter if the same runtime function is used in several nodes of the graph, or even a different function uses a variable with the same name). It makes it impossible to use the function for more than one node in the video graph without interference, as shown in the example.

In a proper implementation and environment, both frames should show a '1' when the clip is rendered forward frame by frame, or the distance from the previous frame otherwise:
#~ Example needs Gavino's GRunt plugin!
function RTE(clip c) {
c.GScriptClip(""" # "
Try { last_frame = last_frame } catch(err) { last_frame = -100 }
Subtitle(string(current_frame - last_frame))
last_frame = current_frame
last
""", local=false) #"
}
BlankClip()
StackHorizontal(last.RTE(), last.RTE())
Thanks to and since Gavino's GRunT plugin, the scope can be set to 'local=true'.

This makes the scopes of each RTE instance separate, which is highly desirable in many cases, allowing more than one instance (node) to work properly, and reliefs from problems like 'tunneling x' between different functions.
*But* as second effect, the lifetimes are then restricted to one execution of the function. For the next rendered frame, all previous information is lost. Thus, all algorithms that tie in with the previous frame can not be implemented this way - Except they use global variables, but then the other problem reappears.

For all plugins, it is not a question that it is possible to use them twice ore more in a script, and if they use static variables - no problem.
But for scripted functions, it seems impossible.

Any suggestions?

Gavino
27th October 2013, 11:49
But for scripted functions, it seems impossible.

Any suggestions?
One crude way is for the function to use unique variable names for each instance. For example, using a global counter to keep track of calls and appending the 'instance number' to each variable name. A bit messy since you need to build up the run-time script dynamically inside the function using string concatenation.

StainlessS
27th October 2013, 12:38
As per Gavino method above but using string replacement rather than very messy concatenation, would be my choice.

Gavino
27th October 2013, 14:41
using string replacement rather than very messy concatenation
Yes, that's much neater.

StainlessS
27th October 2013, 14:57
M53, how would a suite of eg
RT_ArrayAlloc, RT_ArrayGetInt, RT_ArrayGetFloat, RT_ArraySetInt, RT_ArraySetFloat, RT_ArrayAddInt, RT_ArrayAddFloat, RT_ArrayGetSize,
style funcs grab you, using a file to store 4 byte int or 4 byte floats which MUST be user controlled as far as contents is concerned, ie an
int would produce rubbish if accessed as float ?

It would provide fast random access to read array elements.

EDIT: Change of mind, would be of fixed type on RT_ArrayAlloc, int, float or string, with fixed maximum length strings if string (default 256).

Bit OT, hope you dont mind, guess I should have posted in RT thread.

martin53
27th October 2013, 15:37
For example, using a global counter to keep track of calls and appending the 'instance number' to each variable name. A bit messy ...
I omitted this concept from my 1st post for its imagined clumsiness...

Honestly, I thought it was near impossible. But in the run to demonstrate how messy an example would look like, I figured out something not *that* bad. :) The impact on RTE script readability is small, and it might even be seen as obvious marks for variables that persist between calls.

EDIT 13th Nov 2013: Extracted+assembled the runtime commands into a function definition. This is important because the string inside ScriptClip("") is re-evaluated for every frame and will make the AviSynth string heap overflow. AviSynth does not clean up the heap until script end.

Moved example to 1st post


I can't say I love it. But I think this will replace my previous ScriptClip() template. And of course I hope it may be useful for other RTE writers, too.
The marker %%% can be replaced with any personal favourite.

EDIT: included init part for global variables according to Gavino's proposal some posts below.
Note: The Try ... catch ... function inside the GScriptClip() is typically not needed for a RTE function which processes the output clip. But it is highly useful for RTE-made 'helper' clips, which themselves are used by another RTE function, because a potential error message in such a clip will not be visible in the output clip ;)

martin53
27th October 2013, 15:56
EDIT: This post is outdated because the initialization is now included in the full example above.

And don't forget: It is dangerous/buggy to define global vars for RTE functions at global script level like it was usual all the time:

...
global RTE_myVar = 0
...

since no such variable without appended index will exist any more, and the really existing ones remain undefined.

To stay indeppendent from number of instances, maybe better style to define inside RTE, after check that they're undefined:
GScriptClip("""
...
try { __ = RTE_myVar%%%%%% } catch(err) { global RTE_myVar%%%%%% = 0 }
...
first use of RTE_myVar%%%%%% in RTE
...
""", local=true)
If you know what you do, you may define a whole bunch in one catch block (faster), or you may check each var separately.

martin53
27th October 2013, 15:59
how would a suite ...
Glad you ask! performant arrays (i.e. not! abused strings!!! ;) ) are needed desperately, but let's discuss in RT thread, and maybe not instantly.

StainlessS
27th October 2013, 16:20
M53, while i'm doing Array thing, I'll do an RT_IncrGlobal(String GlobalName,int "default"=1) style func.

martin53
27th October 2013, 16:55
Just applied the described concept on the AWB() script (http://forum.doom9.org/showthread.php?p=1632812#post1632812).

- It's great! :) For the first time, it is possible to compare settings side by side! That also would enrich Srestore() and others.

Gavino
27th October 2013, 18:03
To stay indeppendent from number of instances, maybe better style to define inside RTE, after check that they're undefined:
GScriptClip("""
...
try { __ = RTE_myVar%%%%%% } catch(err) { global RTE_myVar%%%%%% = 0 }
...
first use of RTE_myVar%%%%%% in RTE
...
""", local=true)
An alternative would be to initialise in the function, but outside the RTE, avoiding a try/catch on each frame.
Eval("global RTE_myVar"+string(RTE_InstanceNumber)+"=0")
Both approaches are a bit ugly!

If staying with the try/catch approach, note the assignment to __ is unnecessary:
try { RTE_myVar%%%%%% } catch(err) { global RTE_myVar%%%%%% = 0 }

martin53
27th October 2013, 19:34
An alternative would be to initialise in the function, but outside the RTE, avoiding a try/catch on each frame.
Eval("global RTE_myVar"+string(RTE_InstanceNumber)+"=0")
Both approaches are a bit ugly!

If staying with the try/catch approach, note the assignment to __ is unnecessary:
try { RTE_myVar%%%%%% } catch(err) { global RTE_myVar%%%%%% = 0 }

:thanks: Thank you very much for your interest in my question and the participation!
The first suggestion is good. I think I'd use the RT_StrReplace again instead of the concatenation, so a whole bunch of variables can be processed in one script block and it still looks readable.

I think I thoroughly tried the elimination of the assignment, but I failed. I remember you gave me that same hint already, and tried to use it - will try again, maybe I just almost used the right syntax.

EDIT: checked both positive. Will edit the above post.

StainlessS
28th June 2014, 11:10
That is a very long Safari you are on M53, when are you coming back ?

I've just been playing with this to good effect (great multi-instance scene change detector coming).

Below my preferred usage of your example framework.
I've used '@@@' rather than '%%%' as '%' can be used in eg String() func and some RT_Stats formatted output funcs.
I've also re-ordered some things and implemented Debug output so you can verify conformity with intent.

EDIT: Also, I think eg ImageSource() uses '%%' as an Escape for '%' where you want a single percent char
in a filename, and I think it is also possible that eg '%%%' could be legally used in ImageSource, to mean literal
single percent, followed by inserted frame number.

Here tis:

Function RTE(clip c, string "ExampleVarFromNonRTE_Parameter") {
FuncS="""
#######################################
# 1) Unique Runtime Function Definition
#######################################
Function RTE_@@@(clip c, string JustMyExampleString) { # No Optional Arguments in unique RTE func
# ... insert complete runtime code here
# NOTE: Avoid GScript("...") and Eval("...") inside this function, i.e. all functions which expect a script string
# Can use Gscript if/else etc in here if called via GScript(InstS) rather than Eval(InstS)
Try {
c
Subtitle(string(current_frame - RTE_last_frame@@@))
Subtitle(JustMyExampleString, align=4)
global RTE_last_frame@@@ = current_frame
return last
} catch (err) {
assert(false, "RTE_ScriptClip @@@: "+err)
}
}
#######################################
# 2) Unique Global Variables Initialization
#######################################
global RTE_last_frame@@@ = -1
# ... more variables as needed
#######################################
# 3) Unique Runtime Call
#######################################
ARGS = "ExampleVarFromNonRTE_Parameter"
#ScriptClip must be a one-liner:
Last.GScriptClip("RTE_@@@(last, "+ARGS+")", local=true, args=ARGS) # Grunt needed for Args
"""
# Optional args must be handled in main function
ExampleVarFromNonRTE_Parameter=Default(ExampleVarFromNonRTE_Parameter,"No Example String given")
#######################################
# 4) Unique Identifier Definition
#######################################
RT_IncrGlobal("RTE_InstanceNumber") # Instead of Try/Catch
InstS = RT_StrReplace(FuncS,"@@@",String(RTE_InstanceNumber)) # Unique Instance function string
RT_TxtWriteFile(InstS,"DEBUG_RTE_"+String(RTE_InstanceNumber)+".TXT")
c
# Runtime Environment Call
GScript(InstS) # for use without GScript, use Eval( instead
return last
}


ExampleVarFromNonRTE = "hi there"
#ExampleVarFromNonRTE = RT_Undefined
BlankClip()
StackHorizontal(last.RTE(ExampleVarFromNonRTE), last.RTE("it works"))


Debug output from Instance 2


#######################################
# 1) Unique Runtime Function Definition
#######################################
Function RTE_2(clip c, string JustMyExampleString) { # No Optional Arguments in unique RTE func
# ... insert complete runtime code here
# NOTE: Avoid GScript("...") and Eval("...") inside this function, i.e. all functions which expect a script string
# Can use Gscript if/else etc in here if called via GScript(InstS) rather than Eval(InstS)
Try {
c
Subtitle(string(current_frame - RTE_last_frame2))
Subtitle(JustMyExampleString, align=4)
global RTE_last_frame2 = current_frame
return last
} catch (err) {
assert(false, "RTE_ScriptClip 2: "+err)
}
}
#######################################
# 2) Unique Global Variables Initialization
#######################################
global RTE_last_frame2 = -1
# ... more variables as needed
#######################################
# 3) Unique Runtime Call
#######################################
ARGS = "ExampleVarFromNonRTE_Parameter"
#ScriptClip must be a one-liner:
Last.GScriptClip("RTE_2(last, "+ARGS+")", local=true, args=ARGS) # Grunt needed for Args


EDIT: Oops, RT_WriteFile not as yet available in RT_Stats, (fixed above).

fvisagie
29th June 2014, 13:51
EDIT 13th Nov 2013: Extracted+assembled the runtime commands into a function definition. This is important because the string inside ScriptClip("") is re-evaluated for every frame and will make the AviSynth string heap overflow. AviSynth does not clean up the heap until script end.

Moved example to 1st post


Unfortunately for someone without much experience like myself the original example no longer exists to illustrate the context of the string inside ScriptClip() that could cause this problem. Are you saying string declarations inside ScriptClip() could cause this - care to elaborate?

In martin53's presumed absence, feel free to pipe up with an explanation, StainlessS or Gavino :).

StainlessS
29th June 2014, 15:09
I'm not sure exactly what M53 was referring to there, I have had a bit of a look for another thread that
resulted in this thread, but could not find it.

But,

Whenever you do something like Subtitle("AveLuma="+String(AverageLuma)) inside ScriptClip, it results in a new string
(the concatenated string, and at every frame, perhaps even 3 strings, the string for "AveLuma=", the String(AverageLuma)
and the concatenated one) that occupies heap memory until Avisynth closes down (I was personally
quite shocked when I discovered that there was no memory garbage collection, not even strings created in script local
functions [I think]).

I'm not alogether sure but think that, if you call scriptclip with ScriptClip("""Subtitle("AveLuma="+String(AverageLuma))""")
then the ScriptClip string arg is instantiated only once, but the SubTitle string arg at every frame.
EDIT: Same with Eval(), strings created at every frame if used inside of Scriptclip, but if you eg
use Grunt()'s 'args' arg, then you can pass in a named argument string variable from outside, and could do an Eval on this
arg string variable that was instantiated only once at main script level.
EDIT: Just had a think about it, ScriptClip does NOT instantiate string mem for its arg at every frame, it is a filter that is linked
into the filter graph, and so the string would only be created when calling the Scriptclip constructor, before frame serving starts.
Inside of ScriptClip, ScriptClip parses the string given to its constructor and at every frame, and every function and filter
is called as if for the first time, all quoted strings inside of Scriptclip are instantiated and on each frame.
If a GScript function is instantiated, eg GSCript(""" Function fn() { Return "Hello"} """) , I dont know if it is in some way tokenized,
and the "Hello" string allocated memory just the once, or every time the function is called.
Perhaps even ScriptClip tokenizes its string arg and so string literals may only be allocated once, perhaps the chosen one will
enlighten us (although he has probably enlightened me on same subject several gazillon times already).



Oh, just found this in developer forum, perhaps of some interest.
http://forum.doom9.org/showthread.php?p=1587841

Gavino
30th June 2014, 00:19
As StainlessS says, the string argument to ScriptClip() is instantiated once only, when the outer script is parsed.

However, at run-time, that string is itself parsed afresh by ScriptClip() for every frame, each time using up space in Avisynth's string table for variable names, etc. Most of the time, this is not significant, but if ScriptClip is called with a very long run-time script or called recursively to a great depth, coupled with lots of source frames, it could eventually eat up a lot of memory.

To avoid this, it is recommended to use functions to cut down the size of a long run-time script. See this post.

fvisagie
30th June 2014, 09:40
That's very helpful, thanks guys.

More on topic, what about nested variables and arguments of functions pre-defined at the top-level script scope but called at runtime? According to the documentation
The lifetime of variables defined at nested local scopes spans from the time of the definition in the nested local scope to the end of the nested local scope's lifetime.

It's well understood that such variables and arguments - not that there's much distinction in this regard - are not usable beyond the lifetime of the nested scope. But what about the memory occupied by them - when does that get released, specifically when called from runtime? If only when the script is closed, I would imagine the best way to limit per-frame memory growth from calling functions at runtime would be to use 'static' variables, i.e. globals and top-level script variables, wherever sensible? If so and assuming no calls to runtime functions from top-level code, are there any clear pros and cons to using globals vs. top-level script variables?

When is memory used by variables declared in "top-level" runtime code released? And, for the sake of completeness, that of variables and arguments of functions declared in runtime code (not that I can think of any good reason for that)?

EDIT: I suspect some or most of this is probably addressed by StainlessS' post above, in which case I humbly ask your indulgence for my limited understanding!

Gavino
30th June 2014, 10:25
It's well understood that such variables and arguments - not that there's much distinction in this regard - are not usable beyond the lifetime of the nested scope. But what about the memory occupied by them - when does that get released, specifically when called from runtime?
All values (of all types) in Avisynth are structures referenced via so-called 'smart' pointers, and are freed the moment there is no longer anything pointing at them. In general then, when a variable goes out of scope, its value will be freed, unless there is another reference to it somewhere (eg via 'last').

However, string values themselves just contain pointers to the actual characters which (for most strings, and certainly those created by script operations) are stored in the string heap. As StainlessS pointed out above, the contents of this heap are not freed until the script is unloaded, even though the string values pointing to them may have been deleted.

StainlessS
30th June 2014, 11:55
Even if desired by Avisynth developers, I dont think strings (the actual string characters) could be freed, as it is
in docs that plugin devs can return a string to Avisynth using env->SaveString to make a copy of a plugin's
string (mem allocation is quite separate between plugins and Avisynth, string memory is copied from plugin memory
heap to Avisynth memory heap, giving Avisynth total ownership and duty to later free mem), and if they
(plug devs) keep a pointer to that memory block they can re-use it so long as they do not overwrite the bounds
of that memory block which is owned by Avisynth.
One thing I have found though, (and I dont think it was mentioned in docs anywhere), if setting a local or global variable
from within a plugin, it is unnecessary to env->SaveString() the name of the variable, if it already exists, Avisynth will
not re-create another variable (with identical name) in the string table, the old one will be re-used. (kinda makes sense really).
The only way that some script local strings could be freed would be if future versions of avisynth kept separate track of strings
created locally by the parser, and were in no way returned by - or created by a plugin.
[Although perhaps a new env-SaveString2() or whatever could be added which plugin writers could not later access contents of.]

EDIT: I'm not actually aware of any plugin that later uses string memory now belonging to Avisynth, although I would not
be surprised if AVSLib did so.

fvisagie
30th June 2014, 13:45
Thanks again, guys.

Just to cross the Ts and dot the Is,


# Runtime Environment Call
GScript(InstS) # for use without GScript, use Eval( instead



Is it true to say that were Eval() to be used instead of GScript(), current_frame would have to be passed as a parameter to RTE_@@@()?

StainlessS
30th June 2014, 14:27
As linked in #post 16 by Gavino
Yes, the string table does accumulate entries for each frame rendered by a run-time filter.
I can think of a number of ways this could be improved/fixed, including changes to ScriptClip itself, but normally there is a simple workaround - use functions to cut down the size of the run-time script. Taking this approach to its limit, you can even replace the entire run-time script by a function call. Instead of writing
ScriptClip("""
... # very long script
""")
do this
function f(clip c, int current_frame) {
c
... # code from run-time script
}
...
ScriptClip("f(current_frame)")

See here for an example which fixed a previously mysterious memory leak in SRestore.
(BTW The current_frame parameter is not needed if using GRunT, since it makes current_frame global).

In your case, the recursive use of ScriptClip adds a further complication, but I think you could restructure your code to call ScriptClip once and do the recursion inside it. In fact, you said in post #4 that you were able to do this with your earlier code.

So would not need to be, it is Grunt that does the clever stuff there.
In your other thread I think I passed current_frame in at least one snippet that did not need an explicit current_frame (If Grunt is installed).

fvisagie
1st July 2014, 10:01
I was confusing GScript and GRunT, thanks!

StainlessS
1st July 2014, 17:44
Yep, I do that myself all of the time, perhaps a single plug called eg GruntScript() is called for.

fvisagie
6th July 2014, 15:33
As StainlessS says, the string argument to ScriptClip() is instantiated once only, when the outer script is parsed.

However, at run-time, that string is itself parsed afresh by ScriptClip() for every frame, each time using up space in Avisynth's string table for variable names, etc.

Somewhat naïve testing seems to indicate that this applies equally much to FrameEvaluate() as might be expected; does the theory agree?

scriptclip.avs
fps = 25.0
BlankClip(length=Int(3600*fps), fps=fps)
evens = 0
ScriptClip("""
evens = current_frame / 2 * 2 == current_frame \
? evens + 1 \
: evens
last
""", show=false, after_frame=false)
scriptclip2.avs
fps = 25.0
BlankClip(length=2*Int(3600*fps), fps=fps)
evens = 0
ScriptClip("""
evens = current_frame / 2 * 2 == current_frame \
? evens + 1 \
: evens
last
""", show=false, after_frame=false)
frameevaluate.avs
fps = 25.0
BlankClip(length=Int(3600*fps), fps=fps)
evens = 0
FrameEvaluate("""
evens = current_frame / 2 * 2 == current_frame \
? evens + 1 \
: evens
# last
""", show=false, after_frame=false)
frameevaluate2.avs
fps = 25.0
BlankClip(length=2*Int(3600*fps), fps=fps)
evens = 0
FrameEvaluate("""
evens = current_frame / 2 * 2 == current_frame \
? evens + 1 \
: evens
# last
""", show=false, after_frame=false)

>avsmeter scriptclip.avs

AVSMeter 1.7.9 (AVS 2.6, x86) by Groucho2004
AviSynth 2.60, build:Jan 14 2013 [16:50:35]

Number of frames: 90000
Length (hhh:mm:ss.ms): 001:00:00.000
Frame width: 640
Frame height: 480
Framerate: 25.000 (25/1)
Colorspace: RGB32

Frames processed: 90000 (0 - 89999)
FPS (min | max | average): 66717 | 97485 | 91832
CPU usage (average): 23%
Thread count: 1
Physical Memory usage (peak): 15 MB
Virtual Memory usage (peak): 12 MB
Time (elapsed): 000:00:00.980

>avsmeter scriptclip2.avs

AVSMeter 1.7.9 (AVS 2.6, x86) by Groucho2004
AviSynth 2.60, build:Jan 14 2013 [16:50:35]

Number of frames: 180000
Length (hhh:mm:ss.ms): 002:00:00.000
Frame width: 640
Frame height: 480
Framerate: 25.000 (25/1)
Colorspace: RGB32

Frames processed: 180000 (0 - 179999)
FPS (min | max | average): 69540 | 97453 | 92068
CPU usage (average): 24%
Thread count: 1
Physical Memory usage (peak): 20 MB
Virtual Memory usage (peak): 18 MB
Time (elapsed): 000:00:01.955

>avsmeter frameevaluate.avs

AVSMeter 1.7.9 (AVS 2.6, x86) by Groucho2004
AviSynth 2.60, build:Jan 14 2013 [16:50:35]

Number of frames: 90000
Length (hhh:mm:ss.ms): 001:00:00.000
Frame width: 640
Frame height: 480
Framerate: 25.000 (25/1)
Colorspace: RGB32

Frames processed: 90000 (0 - 89999)
FPS (min | max | average): 94501 | 115677 | 110254
CPU usage (average): 22%
Thread count: 1
Physical Memory usage (peak): 14 MB
Virtual Memory usage (peak): 12 MB
Time (elapsed): 000:00:00.816

>avsmeter frameevaluate2.avs

AVSMeter 1.7.9 (AVS 2.6, x86) by Groucho2004
AviSynth 2.60, build:Jan 14 2013 [16:50:35]

Number of frames: 180000
Length (hhh:mm:ss.ms): 002:00:00.000
Frame width: 640
Frame height: 480
Framerate: 25.000 (25/1)
Colorspace: RGB32

Frames processed: 180000 (0 - 179999)
FPS (min | max | average): 96123 | 114681 | 110382
CPU usage (average): 23%
Thread count: 1
Physical Memory usage (peak): 19 MB
Virtual Memory usage (peak): 17 MB
Time (elapsed): 000:00:01.631

Gavino
6th July 2014, 15:41
Somewhat naïve testing seems to indicate that this applies equally much to FrameEvaluate() as might be expected; does the theory agree?
Yes. All the run-time filters (ScriptClip(), FrameEvaluate(), ConditionalFilter(), WriteFile[If]()) basically work the same way as far as string parsing is concerned.

fvisagie
7th July 2014, 06:27
Thanks.