Author Topic: Homebrew scripts vs Internal Function calls  (Read 1377 times)

Legacy_Failed.Bard

  • Hero Member
  • *****
  • Posts: 1409
  • Karma: +0/-0
Homebrew scripts vs Internal Function calls
« Reply #30 on: July 21, 2012, 01:21:38 pm »


               Locals are slow.  It's why I figured a struct, though that's based on a potentially incorrect assumption you can use a variable to represent the numeric portion of a struct.  gnobt.i in place of gnobt.1, gnobt.2, etc... .   If you can't assign them that way, then it'd be too slow because you'd need an if/else chained for each entry to manipulate.
               
               

               
            

Legacy_Zarathustra217

  • Sr. Member
  • ****
  • Posts: 322
  • Karma: +0/-0
Homebrew scripts vs Internal Function calls
« Reply #31 on: July 21, 2012, 01:48:27 pm »


               I know locals aren't ideal compared to keeping it in function variables, but in all the tests I've conducted, locals don't seem slow at all. Of course, it depends on what you define slow as, but have you conducted any tests on this?
               
               

               


                     Modifié par Zarathustra217, 21 juillet 2012 - 12:48 .
                     
                  


            

Legacy_Failed.Bard

  • Hero Member
  • *****
  • Posts: 1409
  • Karma: +0/-0
Homebrew scripts vs Internal Function calls
« Reply #32 on: July 21, 2012, 02:25:26 pm »


                 Locals were compared in the post I linked to in the first page as well.  

  With 10 variables on an object, it was about twice as long to search them as to use GOBT itself.  An 80ms average at 10000 loops isn't a huge amount of overhead per call, admittedly, but it's still adding some. 
               
               

               
            

Legacy_Zarathustra217

  • Sr. Member
  • ****
  • Posts: 322
  • Karma: +0/-0
Homebrew scripts vs Internal Function calls
« Reply #33 on: July 21, 2012, 02:47:30 pm »


               I imagine the 80 ms you see there with 10-20 variables is more or less exclusively from the CreateObject command - would have to be tested though.

Nonetheless, for good measure, I managed to write a script that avoids local variables:


object GetNearestObjectByTag_Opt(string sTag, object oSource=OBJECT_SELF, int nNth=1)
{
    //We need to track this to help if nNth>1
    object oPrior=OBJECT_INVALID;
    float fPriorDist=-1.0;
    while(nNth>0)
    {
        int nCount=0;
        float fNearest=999999.0;
        float fTest=0.0;
        object oArea=GetArea (oSource);
        object oTemp;
        object oNearest=OBJECT_INVALID;
        int nPriorPassed=FALSE;
        while((oTemp=GetObjectByTag(sTag, nCount))!=OBJECT_INVALID)
        {
            if(GetArea(oTemp)==oArea)
            {
                //If we are passed the prior object we found, skip it.
                //But change the variable so we know it's OK to pick the next object even if distance equals prior.
                if(oTemp==oPrior)
                {
                    nPriorPassed=TRUE;
                }
                else if((fTest=GetDistanceBetween(oSource, oTemp))<fNearest&&fTest>=fPriorDist)
                {
                    //This test is needed if two objects match distance.
                    if(nPriorPassed||fTest>fPriorDist)
                    {
                        fNearest=fTest;
                        oNearest=oTemp;
                    }
                }
            }
            nCount++;
        }
        if(oNearest==OBJECT_INVALID)
        {
            return oNearest;
        }
        else if(nNth>1)
        {
            oPrior=oNearest;
            fPriorDist=fNearest;
            nNth--;
        }
        else
        {
            return oNearest;
        }

    }
    return OBJECT_INVALID;
}


No tests on that yet either though, will wait to I get home.

The reason I am interested in doing it is because we actually use GNOBT with nNth>1 in several places.
               
               

               


                     Modifié par Zarathustra217, 21 juillet 2012 - 01:48 .
                     
                  


            

Legacy_Zarathustra217

  • Sr. Member
  • ****
  • Posts: 322
  • Karma: +0/-0
Homebrew scripts vs Internal Function calls
« Reply #34 on: July 22, 2012, 12:15:39 am »


               Tested the two versions against one very similar to the one you posted above:

Bioware version:              355 msec   4000 calls
No nNth>1 version:            119 msec   4000 calls
Using object variables:       164 msec   4000 calls
Using local script variables: 127 msec   4000 calls

So will go with the last version and not use the local variables.

In all, I figure it serves as a satisfying tradeoff (7% slower) for the additional feature of allowing searches for next nearest etc. - but that is of course up to the individual to assess.
               
               

               


                     Modifié par Zarathustra217, 21 juillet 2012 - 11:17 .
                     
                  


            

Legacy_Zarathustra217

  • Sr. Member
  • ****
  • Posts: 322
  • Karma: +0/-0
Homebrew scripts vs Internal Function calls
« Reply #35 on: July 22, 2012, 12:16:24 am »


               //double post
               
               

               


                     Modifié par Zarathustra217, 21 juillet 2012 - 11:16 .
                     
                  


            

Legacy_Failed.Bard

  • Hero Member
  • *****
  • Posts: 1409
  • Karma: +0/-0
Homebrew scripts vs Internal Function calls
« Reply #36 on: July 23, 2012, 06:05:16 pm »


                

Zarathustra217 wrote... 


Inspired by various other topics (here and here) I started more systematically profiling functions to get a better idea of how they actually performed. I'll share it here in the hope it may help others - and that they perhaps will contribute with similar too.

Testing setup

The testing setup was our main PW module (~900 areas, ~3000 scripts) using NWNx and the profiling plugin. The testing computer was a Intel i7 920 running Windows 7.

Functions were run in their own scripts, generally running only them where possible. The scripts were tested by running the script 1000 times in a loop (using the TMI plugin to allow for that), and this process was repeated 10 times in small intervals. This was done by the following script:

void main()
{
    int nCount=0;
    while(nCount<1000)
    {
        ExecuteScript(scriptname,OBJECT_SELF);
        nCount++;
    }
}
           

Of course, the profiling isn't always accurate, meaning that the results shouldn't be considered objective, but rather indicative. If you have comments on how to improve the testing setup, I would be very interested in hearing your ideas.

Anyway, here goes:

GetModule()

I used this function to have a sort of baseline, assuming this function was very fast. The script looked as follows:

'test_getmodule'

void main()
{
    GetModule();
}

Results:

test_getmodule - 3 msec for 10000 calls =
0.0003 ms/call

Remarks:

Safe to say, this function is fast - to the point that the performance impact can barely be measured.

ExecuteScript()

Tested this functing using two scripts, the first running the second, being the test_getmodule above. The other script looked as follows

'test_exec'

void main()
{
    ExecuteScript("test_exec_2", OBJECT_SELF);
}

Results:

test_exec - 3049 msec for 10000 calls =
0.3 ms/call

Remarks: I was somewhat surprised to see the performance impact here given that searching for a file is usually quite fast. However, the performance impact is likely affected negatively to a significant degree by the (all too) large number of scripts in the module that was used as testing setup. It may be that the performance impact is much less in smaller modules.

GetHasSpellEffect()

Tested this to compare cycling effects and checking for a spell effect with GetEffectSpellType. The scripts were run on a creature that had the spell applied as well as a few others.

'test_spellfx_1'

void main()
{
    if(GetHasSpellEffect(SPELL_AID))
    {

    }
}

'test_spellfx_2'

void main()
{
    effect eTemp=GetFirstEffect(OBJECT_SELF);
    while(GetIsEffectValid(eTemp))
    {
        if(GetEffectSpellId(eTemp)==SPELL_AID)
        {
            return;
        }
        eTemp=GetNextEffect(OBJECT_SELF);
    }
}

Results:

test_spellfx_1 1 msec for 10000 calls =
0.00001 ms/call
test_spellfx_2 139 msec for 10000 calls = 0.00139 ms/call

Remarks:

GetHasSpellEffect is very fast, apparently faster than GetModule(). Consequently, there's no reason to cycle effects even if you are looking for several spell effects at once. You might even consider making custom spell IDs to apply effects you use and test for frequently.


Based on this observation in the thread here , it looks as though there are some things that could be routed through GetHasSpellEffect that might be worked around now.  Anything that is commonly used, and generally needeing to be checked by looping through all effects.

  Making a custom Knockdown spell, for instance, and casting it as an instant spell onto the target instead of applying EffectKnockdown() directly.  Then you can check directly for the knockdown spell effect instead of other means to search for it.

  KD is the one I'd had the most issue trying to track, but I'm sure there are other effects that are constantly checked that might benefit from being moved into custom single purpose spells.  The slightly increased application time would be more than offset by the increased speed in checking for it.
 
               
               

               
            

Legacy_Shadooow

  • Hero Member
  • *****
  • Posts: 7698
  • Karma: +0/-0
Homebrew scripts vs Internal Function calls
« Reply #37 on: July 23, 2012, 06:18:57 pm »


               

Failed.Bard wrote...

  Making a custom Knockdown spell, for instance, and casting it as an instant spell onto the target instead of applying EffectKnockdown() directly.  Then you can check directly for the knockdown spell effect instead of other means to search for it.

  KD is the one I'd had the most issue trying to track, but I'm sure there are other effects that are constantly checked that might benefit from being moved into custom single purpose spells.  The slightly increased application time would be more than offset by the increased speed in checking for it.
 

this is nonsense

in the situatione where you dont need the effect had certain spell ID, you can use the effect creator techniquw to later find out what is the effect, if you need spell ID (basically kd applied from spells) you cannot use this workaroud as you would lost the spell ID, involving also issues with dispells etc.
               
               

               
            

Legacy_Zarathustra217

  • Sr. Member
  • ****
  • Posts: 322
  • Karma: +0/-0
Homebrew scripts vs Internal Function calls
« Reply #38 on: July 23, 2012, 06:33:04 pm »


               The point is that it can be an advantage that an effect is tied to a specific spell ID, because you can then use GetHasSpellEffect() to test if the effect is applied rather than searching through the effects on the object with GetFirst/NextEffect. The tests indicate that former solution is much faster.
               
               

               
            

Legacy_Zarathustra217

  • Sr. Member
  • ****
  • Posts: 322
  • Karma: +0/-0
Homebrew scripts vs Internal Function calls
« Reply #39 on: July 23, 2012, 06:34:46 pm »


               Besides that, I seem to recall that the normal knockdown that isn't applied by script is actually not even showing up as an effect of the type EFFECT_TYPE_KNOCKDOWN so you can't really track it. Not sure if that's relevant here though.
               
               

               


                     Modifié par Zarathustra217, 23 juillet 2012 - 05:41 .
                     
                  


            

Legacy_Shadooow

  • Hero Member
  • *****
  • Posts: 7698
  • Karma: +0/-0
Homebrew scripts vs Internal Function calls
« Reply #40 on: July 23, 2012, 07:12:55 pm »


               

Zarathustra217 wrote...

The point is that it can be an advantage that an effect is tied to a specific spell ID, because you can then use GetHasSpellEffect() to test if the effect is applied rather than searching through the effects on the object with GetFirst/NextEffect. The tests indicate that former solution is much faster.

so you think that spawning an invisible placeable, assigncommand him casting a custom spell and then in spellscript apply the effect just in order to save 0.0010ms that would be required to loop effects on object to find effect via creator is better? I doubt that sincerely.

There is a line between efficiency and efficiency obsession.
               
               

               


                     Modifié par ShaDoOoW, 23 juillet 2012 - 06:14 .
                     
                  


            

Legacy_Zarathustra217

  • Sr. Member
  • ****
  • Posts: 322
  • Karma: +0/-0
Homebrew scripts vs Internal Function calls
« Reply #41 on: July 23, 2012, 08:03:09 pm »


               You honestly lost me there. Invisible placeable? Creator?

It's just something to ponder really, nothing concrete, at least not from my side.
               
               

               
            

Legacy_Shadooow

  • Hero Member
  • *****
  • Posts: 7698
  • Karma: +0/-0
Homebrew scripts vs Internal Function calls
« Reply #42 on: July 23, 2012, 08:42:04 pm »


               

Zarathustra217 wrote...

You honestly lost me there. Invisible placeable? Creator?

It's just something to ponder really, nothing concrete, at least not from my side.

Im following the Failed.Bard's idea/suggestion that improves upon your suggestion to use custom spell to track effects.

If you want to apply some KD effect somewhere and later be able to find that effect is a knockdown effect (since kd effect returns effect_type_invalid). You would normally use object in module with speficic tag, called the effect creator. Then loop over effects and check the effect creator to find this out.

Effect are generally applied to creature/PC right? Well, you cant tell this object to cast this spell really. That would require clearing the action queve which is quite bad solution when you are applying this effect from cast spell. Etc. Etc. creature might not be commandable, or might just doing something else at the moment blocking the cast spell action. Therefore you need to spawn either invisible placeable (thats how PRC did it for example] cast the spell onto creature and possibly destroy it after. Or you need any object in module, but you need to tell the object to cast the spell on self and push him target via local variable beforehand.

And lastly. You will very probably want to remove the effect. Surely if we are speaking about KD. Now - to do that you need to? yes loop all effects on PC:wizard: and check the spell id again. Normally, you are looping all effects and once you find out match creator, then you remove effect and thats it. True - with gethasspelleffect you can avoid this in some calls perhaps, but still.

Im not saying that there aren't other possible uses of this just that this particular idea is bad example.
               
               

               


                     Modifié par ShaDoOoW, 23 juillet 2012 - 07:49 .
                     
                  


            

Legacy_Failed.Bard

  • Hero Member
  • *****
  • Posts: 1409
  • Karma: +0/-0
Homebrew scripts vs Internal Function calls
« Reply #43 on: July 23, 2012, 09:44:41 pm »


                 It looks like ShadoOw is right.  Since there's no CastSpell... function in the way that there's PlayAnimation, all spells have to be cast from within the action queue.
  Creating the invisible placeable is because spell casting requires the target to be seen by the caster, whereas any item or placeable anywhere in the module can be used as the effect creator for tracking.

  I'd been under the mistaken impression that instant cast spells bypassed the queue even though they're still actions, but some quick tests show that isn't so.  They simply execute immediately if nothing is queued before them.

  Barring a NWNX function that allows casting outside an action, it looks like effect creators are the more reliable method for custom tracking.

Edit:  On the NWNX side, from nwnx_funcs on the windows end of it:
// changes the SpellID (returned by GetEffectSpellID or GetEffectSpellIDInternal) of the last effect applied
// on oObject to iSpellID. If the last effect applied used EffectLinkEffects(...) this will affect
// all the effects linked together.
void NWNXFuncs_SetLastEffectSpellID(object oObject, int iSpellID);


  That might be one way to do it, at least for servers running under nwnx.  Since the Linux versions usually have all the functionality of the windows ones plus extras, I assume that funtion's in that version too.
               
               

               


                     Modifié par Failed.Bard, 23 juillet 2012 - 08:50 .
                     
                  


            

Legacy_Shadooow

  • Hero Member
  • *****
  • Posts: 7698
  • Karma: +0/-0
Homebrew scripts vs Internal Function calls
« Reply #44 on: July 23, 2012, 10:06:04 pm »


               + the casting spell instantly isnt really instant, player can notice slight delay its not even 1 second, but if you make spell/feat that triggers this workaround then you can notice that the spell vfxs appear a bit later than when casting normal spell

but yes, using nwnx to set spell id ss really good idea