Homebrew scripts vs Internal Function calls
#26
Posté 20 juillet 2012 - 08:48
GOBT uses global hash table for tags, which must be incredibly fast.
#27
Posté 20 juillet 2012 - 10:23
ShaDoOoW wrote...
yes ive heard the same but thought maybe the situation changed throrough the time, as the website is still there... But since the lexicon is not updated to 1.69 I guess the situation is still the same.
i have a hunch that virusman is trying to prod this thread back on topic, so i think we should shift our debate over to the lexicon thread.
Modifié par acomputerdood, 20 juillet 2012 - 10:23 .
#28
Posté 21 juillet 2012 - 11:26
virusman wrote...
I just looked at GNOBT code for a moment (without thoroughly studying it) and it seems that it makes a list of objects every time you call this function.
GOBT uses global hash table for tags, which must be incredibly fast.
Actually, that isn't all too surprising. Trying to rewrite the GNOBT function in NWscript, I realised that it got a bit more complicated when you wanted to find second or third nearest. I think you'd still be able to rewrite it a lot faster using GOBT, just need to think of the best way to do it.
#29
Posté 21 juillet 2012 - 12:02
To be honest, I've never seen a need for getting anything beyond the second closest target by tag.
#30
Posté 21 juillet 2012 - 12:04
object GetNearestObjectByTag_Opt(string sTag, object oSource=OBJECT_SELF, int nNth=1)
{
while(nNth>0)
{
int nCount=0;
float fNearest=9999.0;
float fTest=0.0;
object oArea=GetArea (oSource);
object oTemp;
object oNearest=OBJECT_INVALID;
while((oTemp=GetObjectByTag(sTag, nCount))!=OBJECT_INVALID)
{
if(GetArea(oTemp)==oArea&&!GetLocalInt(oTemp, "GNOBT"))
{
if((fTest=GetDistanceBetween(oSource, oTemp))<fNearest)
{
fNearest=fTest;
oNearest=oTemp;
}
}
nCount++;
}
if(oNearest==OBJECT_INVALID)
{
return oNearest;
}
else if(nNth>1)
{
SetLocalInt(oNearest, "GNOBT", TRUE);
//Use AssignCommand as this makes the function run at the end of the script.
AssignCommand(oSource, DeleteLocalInt(oNearest, "GNOBT"));
nNth--;
}
else
{
return oNearest;
}
}
return OBJECT_INVALID;
}Don't have chance to profile it right now but will do later. Any suggstions for a better solution though?
Modifié par Zarathustra217, 21 juillet 2012 - 12:16 .
#31
Posté 21 juillet 2012 - 12:21
#32
Posté 21 juillet 2012 - 12:48
Modifié par Zarathustra217, 21 juillet 2012 - 12:48 .
#33
Posté 21 juillet 2012 - 01:25
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.
#34
Posté 21 juillet 2012 - 01:47
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 .
#35
Posté 21 juillet 2012 - 11:15
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 .
#36
Posté 21 juillet 2012 - 11:16
Modifié par Zarathustra217, 21 juillet 2012 - 11:16 .
#37
Posté 23 juillet 2012 - 05:05
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.
#38
Posté 23 juillet 2012 - 05:18
this is nonsenseFailed.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.
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.
#39
Posté 23 juillet 2012 - 05:33
#40
Posté 23 juillet 2012 - 05:34
Modifié par Zarathustra217, 23 juillet 2012 - 05:41 .
#41
Posté 23 juillet 2012 - 06:12
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.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.
There is a line between efficiency and efficiency obsession.
Modifié par ShaDoOoW, 23 juillet 2012 - 06:14 .
#42
Posté 23 juillet 2012 - 07:03
It's just something to ponder really, nothing concrete, at least not from my side.
#43
Posté 23 juillet 2012 - 07:42
Im following the Failed.Bard's idea/suggestion that improves upon your suggestion to use custom spell to track effects.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.
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 .
#44
Posté 23 juillet 2012 - 08:44
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 .
#45
Posté 23 juillet 2012 - 09:06
but yes, using nwnx to set spell id ss really good idea
#46
Posté 23 juillet 2012 - 10:08
And the NWNx solution is handy - I imagine it wouldn't even matter whether the spell ID actually exists.
#47
Posté 25 juillet 2012 - 02:02
ShaDoOoW wrote...
Im following the Failed.Bard's idea/suggestion that improves upon your suggestion to use custom spell to track effects.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.
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.
Hello.
Without detracting from what has been said, it may be handy to remember that an effect is only ever equal to itself (*). By virtue of this you can make a direct comparison between two effects and tell precisely if they are the same or not -- without need to know/retrieve informations such as effect's Spell Id, Creator, and most other sub-properties (like the magnitude of an attack penalty, to name one) of the queried effect.
The very exception to this is about the Duration of the effect. An effect X can be applied Permanently on Object A and Temporarily on Object B. When compared, the two effects shall evaluate equal -- incidentally revealing that the Duration trait of an effect is not considered integrant part of the very effect (it is rather an added property).
So the trick is to save the original effect somewhere safe, with Permanent duration, usually in an inaccessible Area, and applied to an inert Creature that will not react to anything (I used this in Fox Spells with flawless results -- if you want an idea of the extent this can go, have a look at my implementation of the Dirge mobile aoe spell). Creatures can hold _any_ effect +positive and -negative, except for the summoning effects (those used to spawn a summoned creature -- To "save" a summoning effect on a creature equals to have that creature use the effect and spawn a summoned associate (regardless of who created said effect) -- which is not what you want to happen).
Saving the original effect can be useful for many reasons. One I have grown fond of (while making Fox Spells) is the ability to make quick and surgical removal of a _specific_ effect. You just issue a RemoveEffect() on the target creature, and if she had that very effect, it shall be removed. Otherwise all you wasted is 1 call -- as opposed to any [lenghty] scan to first determine if the target creature may hold an effect such as _the_ one you are interested in.
(*) = Equal to itself means precisely that. If effect A is copied into effect B, A and B are found equal always, regardless of how many variables and scopes you have them traverse during their lifetime. But if then A is linked to another effect C, once you compare the new A with B they shall be found not equal. As far as I know this unwanted side-effect of linking is irreversible (if anyone knows of a solution, I will hear it gladly).
(off topic: Quake I on Nightmare is definitely not for my poor heart)
-fox
Modifié par the.gray.fox, 25 juillet 2012 - 02:26 .





Retour en haut






