Post by dragonjim on Jul 19, 2015 11:35:57 GMT 1
Here is the final effort - which isn't much of an improvement if truth be told...
ThreadTest1b.G32 (2.66 KB)
This version tries to use messaging to force the main thread to wait inactive while the x number of threads specified are doing their stuff; it also tries to cut down time lag between one thread ending and another starting by creating all the threads first in suspended mode, and then, when a space becomes available, resuming them: this does cut down the lag marginally but, as the threads have to be created in the first place, it does nothing to reduce the overall time.
In other words, this is about as streamlined as it is possible to get with the idea of using only those cores that are available. Rather than being depressing, this shows the superiority of GFA in linear operation - it has optimised string handling routines which process those 64 strings extremely quickly - BUT linear only beats the multithreading hands down because it only has to create the string in memory once (when it is created as the same length as buf$(n)); in ThreadTest2, of which your copy with only a few 'DoEvents' statements commented is below...
ThreadTest2.G32 (2.82 KB)
...GFA is forced to increment the length of the string every time it adds another character which means that there is a lot of memory swapping and which explains why, in that example, multithreading is more efficient.
Finally, all that can be said about ThreadTest3 has been said about ThreadTest1 (I think)
If you want to know which versions I am referring to, they are preserved in your post from which I got them.
I wouldn't give up on multithreading as you seem to have the basics right - what seems to be tripping you up are some of the peculiarities of GFA data handling and address management. When working in a linear format, GFA is generally so efficient (and extremely good at clearing up our rubbish for us), that it outperforms most other programming languages run through the IDE and, sometimes, when compiled - not bad for a 20 year old program; sadly, I don't think its internal routines were ever optimised for multithreading, which is illustrated by the fact that there is only one command for it, hence the reliance on the less-optimised API functions and need for us to be more careful in clearing up. This would fit in with your original usage of multithreading of having two or more threads doing different tasks running side-by-side as being the most efficient, although as ThreadTest2 shows, multithreading using similar threads can also show some speed advantages, although whether they are great enough for the hassle involved in creating them is another matter...
Keep in mind that multithreading using GFABasic is relatively new and unknown territory - or it is for those left of us still using the language; what we've been doing over the passed few weeks is the classic experimentation technique - as you stated in your last post - of trial and error which can be extremely frustrating (you know that already) and can hinge on the smallest factor: a quick example is, when I was rewriting ThreadTest1, I entered the parameter for OtherThread as 'n'; all of a sudden, nothing worked, errors were thrown and I became extremely proficient in closing down and restarting GFA; then I remembered that the parameters for procedures default to variants (and CreateThread doesn't pass variants well) and that I should have entered n% instead and all worked fine; in a linear routine, GFA would have thrown an error message - sadly, GFA error management breaks down in multithreading which is actually, I believe, the main cause of the 'GFABasic has stopped working' error. This just makes things harder and forces us to work out our mistakes for ourselves.
Anyway, enough rambling on. Keep up the good work.
ThreadTest1b.G32 (2.66 KB)
This version tries to use messaging to force the main thread to wait inactive while the x number of threads specified are doing their stuff; it also tries to cut down time lag between one thread ending and another starting by creating all the threads first in suspended mode, and then, when a space becomes available, resuming them: this does cut down the lag marginally but, as the threads have to be created in the first place, it does nothing to reduce the overall time.
In other words, this is about as streamlined as it is possible to get with the idea of using only those cores that are available. Rather than being depressing, this shows the superiority of GFA in linear operation - it has optimised string handling routines which process those 64 strings extremely quickly - BUT linear only beats the multithreading hands down because it only has to create the string in memory once (when it is created as the same length as buf$(n)); in ThreadTest2, of which your copy with only a few 'DoEvents' statements commented is below...
ThreadTest2.G32 (2.82 KB)
...GFA is forced to increment the length of the string every time it adds another character which means that there is a lot of memory swapping and which explains why, in that example, multithreading is more efficient.
Finally, all that can be said about ThreadTest3 has been said about ThreadTest1 (I think)
If you want to know which versions I am referring to, they are preserved in your post from which I got them.
I wouldn't give up on multithreading as you seem to have the basics right - what seems to be tripping you up are some of the peculiarities of GFA data handling and address management. When working in a linear format, GFA is generally so efficient (and extremely good at clearing up our rubbish for us), that it outperforms most other programming languages run through the IDE and, sometimes, when compiled - not bad for a 20 year old program; sadly, I don't think its internal routines were ever optimised for multithreading, which is illustrated by the fact that there is only one command for it, hence the reliance on the less-optimised API functions and need for us to be more careful in clearing up. This would fit in with your original usage of multithreading of having two or more threads doing different tasks running side-by-side as being the most efficient, although as ThreadTest2 shows, multithreading using similar threads can also show some speed advantages, although whether they are great enough for the hassle involved in creating them is another matter...
Keep in mind that multithreading using GFABasic is relatively new and unknown territory - or it is for those left of us still using the language; what we've been doing over the passed few weeks is the classic experimentation technique - as you stated in your last post - of trial and error which can be extremely frustrating (you know that already) and can hinge on the smallest factor: a quick example is, when I was rewriting ThreadTest1, I entered the parameter for OtherThread as 'n'; all of a sudden, nothing worked, errors were thrown and I became extremely proficient in closing down and restarting GFA; then I remembered that the parameters for procedures default to variants (and CreateThread doesn't pass variants well) and that I should have entered n% instead and all worked fine; in a linear routine, GFA would have thrown an error message - sadly, GFA error management breaks down in multithreading which is actually, I believe, the main cause of the 'GFABasic has stopped working' error. This just makes things harder and forces us to work out our mistakes for ourselves.
Anyway, enough rambling on. Keep up the good work.