I’m using SCOPE_CYCLE_COUNTER to assist in profiling and thanks to Rama’s excellent tutorial on profiling I managed to profile and complete optimization.
Next, I’d like to leave the counters there permanently if possible so I don’t have to uncomment them each time I need to profile. The tutorial suggests commenting these out after you’re done (as cycle measurement obviously has an associated cost in itself).
However I notice that Epic has left SCOPE_CYCLE_COUNTER intact in several key classes (AActor, BehaviorTreeComponent, etc) so this makes me wonder if SCOPE_CYCLE_COUNTER actually gets optimized out of release builds.
I couldn’t find anything inside the macro which suggests that release builds won’t be affected by cycle counters though, so I thought I’d ask all of you! Any idea?
The SCOPE_CYCLE_COUNTER macro is defined to be empty #if STATS is NOT defined (or has the value 0). See Stats2.h and Stats.h in \Source\Runtime\Core\Public\Stats\
The value of STATS is defined like this in \Source\Runtime\Core\Public\Misc\Build.h (with other defines ignored):
#if UE_BUILD_DEBUG
#define STATS (!UE_BUILD_MINIMAL || !WITH_EDITORONLY_DATA || USE_STATS_WITHOUT_ENGINE)
#elif UE_BUILD_DEVELOPMENT
#define STATS (!UE_BUILD_MINIMAL || !WITH_EDITORONLY_DATA || USE_STATS_WITHOUT_ENGINE)
#elif UE_BUILD_TEST
#define STATS 0
#elif UE_BUILD_SHIPPING
#if WITH_EDITOR
#define STATS 1
#else
#define STATS 0
#endif
#else
#error Exactly one of [UE_BUILD_DEBUG UE_BUILD_DEVELOPMENT UE_BUILD_TEST UE_BUILD_SHIPPING] should be defined to be 1
#endif
So in Shipping/Release builds (without the editor) you’ll be using the empty macro variants essentially removing all of these profiling counters (without having to actually change your code at all). The preprocessor will automatically take care of this.
So will FScopeCycleCounter CycleCount*_<whatever result from get_statid>* still linger in the code? I was expecting to see this wrapped in something like #ifdef STATS …<definition of SCOPE_CYCLE_COUNTER macro>… #endif
At which point does the preprocessor check for the value of STATS to skip the code?
I realize this may be a silly question, thanks for bearing with me
Well, that’s not entirely correct. If you look only in Stats2.h this may appear to be the case. But if you look at the bigger picture including Stats.h you’ll notice this:
#include "Stats2.h" //this include will define the SCOPE_CYCLE_COUNTER first
#if STATS
class FScopeCycleCounter : public FCycleCounter { /*omitted*/ };
#else
class FScopeCycleCounter { /*omitted*/ };
#define SCOPE_CYCLE_COUNTER(Stat)
/* more omitted */
#endif
So anywhere in code the macro is used like this:
SCOPE_CYCLE_COUNTER(MyStat);
will be replaced by the preprocessor with this #if STATS:
which still contains macros so it will be processed further (SCOPE_CYCLE_COUNTER_GUARD and GET_STATID will be replaced as well).
And in the #else (#if !STATS) simply becomes:
;
So essentially “nothing”.
No, as explained above, it will be completely gone.
I find that somewhat difficult to explain. You could probably think about it like this: the value of STATS is determined/defined by the preprocessor when it processes Build.h so depending on the conditions/other defined values.
Once it reaches Stats.h for processing it checks whether STATS has been defined at all (if not will cause an error) and then checks the value of STATS which determines whether the SCOPE_CYCLE_COUNTER macro is redefined to “nothing” or not.
Got it! Thanks a lot for taking the time to explain so thoroughly, i’d never have thought of looking for a place where the macro was being conditionally redefined with an empty definition.
Templates and macros as two areas that seem to perpetually mystify me, but they’re indeed so powerful.