Welcome to Doom9's Forum, THE in-place to be for everyone interested in DVD conversion. Before you start posting please read the forum rules. By posting to this forum you agree to abide by the rules. |
![]() |
#181 | Link |
AVS+ Dev
Join Date: Aug 2013
Posts: 359
|
Avisynth Plugin Writing Tips #2: Parallel execution
Avisynth-MT and VapourSynth both support multithreading, and it is being implemented in Avisynth+ too. All of them require the same things from your plugin. Here is a list of what you as a plugin author can do to support execution on multiple threads. If you are the author of any Avisynth plugin, please update your filter according to these rules if needed. Doing so will make sure your plugin can execute seamlessly when multithreaded. Furthermore, following these rules will not only guarantee correct execution in multihtreaded environments, it will also provide optimal mulithreaded performance. Short list for those on the run: - Unless you have the slowest filter in the world, don't start threads in your plugin. - Never use global or static variables. In addition, your filter class should only have read-only members which are initialized during construction. - Don't reuse the IScriptEnvionment pointer between method executions. And again, the same points with a bit of more explanation: - In general, do not slice up your frame and start multiple threads on your own. Threading has its own performance overhead, and it is only worth doing it manually if your filter takes a lot of time to execute. And even if your filter is extremely slow (like fft3dfilter), you should try to optimize its single-threaded performance (by using SIMD instructions or choosing a more efficient algorithm) rather then manually threading it. Optimize for single-threaded performance, and as long as you follow the other rules below, you will get automatic and correct multithreading from Avisynth. - Do not cache frames yourself. You might think it is efficient because you won't have to request/compute them in the next frame, but you are wrong, and there are several reasons why. First, if you write your own cache, you will have to introduce a global state to your filter, which means you will have to take care of synchronization between multiple threads too, which is not easy to do efficiently with caches. Second, keeping copies of past frames also means there will always be multiple references to them, thus Avisynth cannot pass them as write pointers to other filters, and will have to do an extra copy of it more often. And last but not least, Avisynth has a very extensive caching mechanism, and if you request the same frame multiple times (even when you need it for different frame requests), chances are you will get it for free anyway, so your own caching is just pure overhead. - As a general extension to the previous rule, try not to keep any state between frames. In the optimal case your filter class should only have read-only members which are initialized during construction. Surely this is not always possible with every algorithm, but most times it is, and this is what you should strive for. - As stated before, for best multithreading always try to implement algorithms which require no state between frames. Whenever this is violated, be sure to group reads and writes to the state (do not spread them), and guard them in critical sections (as few and as short as possible). For example, it is a good practice to copy all your writable class variables (in a critical section) at the start of each frame into local stack variables, compute the whole frame outside of the critical section (updating the local variables that captured the global state as needed), then write them back together at the end of your frame in another critical section. Do not request an automatic lock around your whole filter from Avisynth, because it will serialize your filter's execution. - If you have class variables that must be writable in every frame, you will also have to keep in mind that Avisynth does not guarantee that frames will be processed in their natural order. Just a reminder. - Only store variables in classes and in method stacks. Per-frame heap allocations should be avoided, because they can act as implicit synchronization points between threads. And most importantly, never store anything in static variables or in the (global) namespace scope. Read the previous sentence a few more times. - Do not store the IScriptEnvironment pointer anywhere yourself (except locally on the stack), and never reuse those pointers outside of the methods where they were supplied to you. Not even between different executions of the same method! There is a reason why you get that pointer separately for each method, which is that it may be different every time, especially in multithreaded scenarios. If you reuse it, the consequences will be different between every implementation, but you can get anything from race conditions to program crashes. Last edited by ultim; 25th October 2013 at 20:57. |
![]() |
![]() |
#182 | Link | |
Registered User
Join Date: Feb 2010
Location: New York
Posts: 116
|
Quote:
Only the slightest bit more consideration put into my problem and I've eliminated what I thought was the need to store the pointer, so it's no big deal for me to bring my code into compliance (luckily I work so slowly I haven't released a version with this problem included), but for the sake of curiosity I thought I'd ask. |
|
![]() |
![]() |
#183 | Link | |
Professional Code Monkey
Join Date: Jun 2003
Location: Kinnarps Chair
Posts: 2,497
|
Quote:
Note that this issue affects everyone trying to use Avisynth plugins. For example RemoveGrain commits the biggest sin of all. It stores the environment pointer as a global variable when AvisynthPluginInit2 is called. This makes VapourSynth, which creates fake Avisynth environments only when needed, crash. A lot. Since the environment pointer will be different between plugin instances and registering functions. I'd like to add another filter writing tip myself. ONLY call AddFunction in AvisynthPluginInit2. Do not throw errors. DON'T DO ANYTHING ELSE THERE. Do initialization when one of your functions is called the first time. It's the biggest reason for autoloading "breaking" in avisynth and a pile of other issues.
__________________
VapourSynth - proving that scripting languages and video processing isn't dead yet |
|
![]() |
![]() |
#184 | Link | ||
AVS+ Dev
Join Date: Aug 2013
Posts: 359
|
Quote:
Quote:
A little bit off-topic (as far as multithreading issues go), but for best practices about exceptions, look at this tip. Last edited by ultim; 25th October 2013 at 22:02. |
||
![]() |
![]() |
#185 | Link |
Registered User
Join Date: Feb 2010
Location: New York
Posts: 116
|
Thanks for the detail, that clears everything up! I've already eliminated the broken code and pushed the fix, now I just need to sort out one spot in my test executable where I'm catching an AvisynthError to grab its error message and I should be done with your list of Don'ts for the time being.
|
![]() |
![]() |
#187 | Link |
Registered User
Join Date: Aug 2006
Location: Stockholm/Helsinki
Posts: 805
|
If there's room for feature requests, can we skip some arguments when calling functions like this:
some_function(a,b,,d,e) if I don't want to change the default value for the third argument, and don't want to explicitly use the names of the 4th and 5th arguments. |
![]() |
![]() |
#188 | Link | |
Avisynth language lover
Join Date: Dec 2007
Location: Spain
Posts: 3,423
|
Quote:
Code:
some_function(a,b,Undefined(),d,e) Code:
global _ = Undefined() ... some_function(a,b,_,d,e) |
|
![]() |
![]() |
#189 | Link |
Registered User
Join Date: Jun 2007
Posts: 401
|
Came across something interesting when testing things....if you have fft3dfilter (version 2.1.1) in a script twice and the bw & bh settings in one is 3x or more than the other then there is a green and red bar at the top. This odd glitch only happens in AviSynth+ and does not show up when using AviSynth 2.6 a5. Figured this was worth mentioning, I haven't tested this on another machine yet.
|
![]() |
![]() |
#190 | Link |
Registered User
Join Date: Jan 2004
Location: earth, barely
Posts: 96
|
I also get the green bar when using the fft3dfilter, although I see it with just a single placement of the tool within a script. I am working around it for the time being by merging chroma back in at the end. I'll admit this means you can't use fft to filter your chroma.
|
![]() |
![]() |
#192 | Link |
Registered User
Join Date: Jan 2004
Location: earth, barely
Posts: 96
|
I can see it with the following script
Code:
avisource("1_fields.avi") converttoyv12() fft3dfilter() |
![]() |
![]() |
#193 | Link |
Registered User
Join Date: Jan 2010
Posts: 270
|
Reproduced, thank you.
---- Okay, it's a bug in env->BitBlt. fft3d calls it in CoverbufToPlanarPlane Code:
for (h=0; h<dst_height; h++) { env->BitBlt(dstp, dst_pitch, coverbuf1, coverpitch, dst_width, 1); // copy pure frame size only dstp += dst_pitch; coverbuf1 += coverpitch; } Code:
#ifdef X86_32 if (GetCPUFlags() & CPUF_INTEGER_SSE) memcpy_amd(dstp, srcp, src_pitch*height); // SSE else #endif memcpy(dstp, srcp, src_pitch*height); // fallback } So either we could remove the height == 1 check, replace src_pitch with row_size (they're equal when height != 1) or fix fft3d to call BitBlt once for the whole frame instead of every line. Original Avisynth actually does it right: Code:
if (height == 1 || (src_pitch == dst_pitch && dst_pitch == row_size)) { memcpy_amd(dstp, srcp, row_size*height); } else { asm_BitBlt_ISSE(dstp,dst_pitch,srcp,src_pitch,row_size,height); } Last edited by TurboPascal7; 31st October 2013 at 00:08. Reason: Merged the bug explanation from the (removed) post below |
![]() |
![]() |
#194 | Link |
AVS+ Dev
Join Date: Aug 2013
Posts: 359
|
You might be right. Actually I'm greatly annoyed by Avisynth's BitBlt, IMHO it is a messed up implementation, but when I "fixed" it last time, it broke some plugins doing interleaved copy of non-interleaved frames. Even today I still don't know if the ability to do such copies is intentional, or is it an accident that plugins started to rely upon. Anyway, I changed "row_size*height" to "src_pitch*height" because I thought it is much more logical, thinking they are the same anyway because of the condition row_size==dst_pitch==src_pitch that guards it. Here is where I made the error: I accidently overlooked that this last condition need not be true if height==1. Fix in next release. And thx for looking into this for me.
edit: added clarification of what plugins got broken. Last edited by ultim; 1st November 2013 at 12:05. Reason: rule 4: no profanity |
![]() |
![]() |
#196 | Link | |
Registered User
Join Date: Jan 2010
Posts: 270
|
Quote:
|
|
![]() |
![]() |
#198 | Link | |
AVS+ Dev
Join Date: Aug 2013
Posts: 359
|
Quote:
Edit: I do find it a nonsense that the code is therefor optimized for the minority of the cases, which is (only) part of the reason I hate the BitBlt implementation. Fixing that though is not trivial due to existing plugins relying on the current implementation. Last edited by ultim; 1st November 2013 at 00:43. |
|
![]() |
![]() |
#199 | Link |
Registered User
Join Date: Jan 2010
Posts: 270
|
neuron2, no problems, don't hesitate to ask. By the way, this condition is also present in the original avs core. So you're a bit late with the nonsense part.
|
![]() |
![]() |
Thread Tools | Search this Thread |
Display Modes | |
|
|