Archived

This topic is now archived and is closed to further replies.

Ximmer

UT2003 Function history (Howd they do that?)

Recommended Posts

In UT2003 (Unreal Tournament 2003) when I get a crash I get an error message that states the nature of what killed the game. But underneath this description theres a Function history of Where this crash occured... So seeing this I thought to myself "That''d be a great thing to add to my engine!" And so I did. And I did behold that my framerate proceeded to die... on further inspection by running it through the VC6 Profiler in Release mode the 2 functions I used were taking up a very large portion of the Cpu time. (Usually within the top 5) These are the Methods I tried...
  
void MyClass::MyFunction()
{
	HistoryStart("MyClass::MyFunction");

	// Do Stuff in Here


	HistoryEnd();
}
  
this one was by far the worst as I had to copy the string into a new memory location and then add it to my Linked List of Function Names.
  
void MyClass::MyFunction()
{
	static const char s_History[] = "MyClass::MyFunction";
	HistoryStart(s_History);

	// Do Stuff in Here


	HistoryEnd();
}
  
This one was better as I could avoid a call to new and delete... but it still had a Massive impact on my framerate...
  
__inline HistoryStart(const char *FunctionName)
{
	// Add new Node to Stack

}

__inline HistoryEnd()
{
	// Remove Node from Stack

}

void MyClass::MyFunction()
{
	static const char s_History[] = "MyClass::MyFunction";
	HistoryStart(s_History);

	// Do Stuff in Here


	HistoryEnd();
}
  
This method produces some pretty good results... It finally got HistoryStart below the #1 Cpu user slot... but now it''s in the #2 slot... Can anyone think of a more efficient way of doing this? -Ximmer

Share this post


Link to post
Share on other sites
All depends on what you do in HistoryStart( const char* )

Because you''ll always be passing constant strings you don''t need to allocate new memory with new, neither do you have to declare a static local variable, you simply pass the const char to the function and store it in a stack.

Dunno how you implemented the stack but the easiest way and probably the fastest will be a simple array of pointers: Adding will simply requring assigning the pointer address and incrementing the stack pointer, that should be pretty fast. Removing is a simple decrement of the stack pointer.
The downside of using the array of pointers is obviously that there''ll have to be a limit to the number of allowed entries you can add.

Calling this in tight inner loops is probably not a good idea.


Hope this can help.

You got me interested in doing this as well now

Share this post


Link to post
Share on other sites
Hmmm... I wonder how this one would go for performance.

  
void MyClass::MyFunction()
{
try {
// stuff

}
catch (MyException& e) {
HistoryStart("MyClass::MyFunction"); // might want to rename HistoryStart if you do it this way...

throw e;
}
}

Share this post


Link to post
Share on other sites
Huh? Wouldn''t this work:

  
std::stack<const char*> functionHistory;

inline HistoryStart(const char* text) {
functionHistory.push(text);
}

inline HistoryEnd() {
functionHistory.pop();
}

//preferably wrap the stuff above inside a class!


void MyClass::MyFunction() {
HistoryStart("MyClass::MyFunction");
// Do Stuff in Here

HistoryEnd();
}

int main() {
try {
//run your program here

} catch (...) {
std::copy(functionHistory.begin(), functionHistory.end(), std::ostream_iterator<const char*>(cout, "\n");
}
}

All that thing does is a single pointer copy + incrementing (Start) and decrementing (End) the stack index (assuming the stack is implemented as a vector and is big enough). Shouldn''t take too much calculation time..

Share this post


Link to post
Share on other sites
still you don''t want to have that in an inner loop, huh?

I think the looking into the public headers of unreal (that also featured this history thingy) should shed light on this...

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
For what it''s worth:

http://www.vsl.gifu-u.ac.jp/unreal/UnrealTips/dll/guard.htm

see the guard/unguard macros, i believe they''re what you''re after...

Share this post


Link to post
Share on other sites
Hmmm some very interesting suggestions... that Exception one looks interesting but I don''t know if it''s what i was looking for... But I''ve settled on this one for now (Don''t want to optimize too much too early)


  
struct History
{
const char * m_Name;
History *m_Next;
};

extern History * g_HistoryHead;
History * g_HistoryHead = NULL; // In another File


__inline void XHistoryAdd(const char *Str)
{
History *h = new History;
h->m_Name = Str;
h->m_Next = g_HistoryHead;
g_HistoryHead = h;
}

__inline void XHistoryEnd()
{
History *h = g_HistoryHead;
g_HistoryHead = h->m_Next;
delete h;
}


I think about the only way I can improve on that is like what Jaco said and use a Fixed array... but I''m afraid that I may get into Recursive functions that may overload that...

And I actually made HistoryStart into a Macro so I don''t have to make the static const char * myself

#define XHistoryStart(x) static const char s_History[] = x; XHistoryAdd(s_History);

Share this post


Link to post
Share on other sites
Yep, that''ll work.
Just did a quick test and stepped through the teplate code...
with every push() called new is called in the teplates allocator, which is going to slow you down big time.
With pop(), delete is called.

No doubt that you will get a big performance improvement when implementing your own stack class.


Share this post


Link to post
Share on other sites
the way they do it is shown in the SDK for their opengl drivers with the original unreal.

they have two macros called guard and unguard. you put the name of the class and function (or whatever you want) in 'guard' and then at the end of the block you put 'unguard'

...i've just downloaded it for you and it's in Core\Inc\UnFile.h line 101 onwards

i just checked and it's also in the unreal tournament developers kit so take your pick

website
unreal
ut

i could just post the code i guess, but if you download the zips you can see how they're used too.

i've modified them and use them in my own work. they make a try catch block and are disabled in debug. basically they stop the program crashing horribly and, as tim sweeney said when i asked him, 'they're for in the field debugging'.

[edited by - petewood on October 18, 2002 3:40:18 AM]

Share this post


Link to post
Share on other sites
Google for: John Robbins superassert
Makes use of MSVC and win32 debug-mode things to extract a stack trace and other things from the runtimes.
(I found out about it from GPG1, 1.12: "Squeezing More out of Assert")
Note: that's just for asserts, not exceptions

[edited by - Assassin on October 18, 2002 3:43:08 AM]

Share this post


Link to post
Share on other sites
hmmm perhaps if I were to make a Dynamically Growing Array that grows in Increments of about 50? then I could get the Speed of array accesses and if I go over the top Reallocate.. then copy the old data into the new and then remove the old data?

Share this post


Link to post
Share on other sites
hmmm the try catch macros look like a good way to do it... it seems to provide no overhead if something bad happens... But this is for absolutly critical errors as it will Dump out of the program... The way I have it I can issue warnings and dump the warnings to a text file and if something seems to be amiss I can check the debug file to make sure nothing is performing improperly... (Like me forgetting to call BeginScene)...
But still the Try Catch macros look to be a very good error catching and debugging system... only thing I''m curious about is whether or not this would hamper actual program debugging? Or would these macros only be enabled in Release mode so that when people issue you bug reports you have a pretty good Idea where things are going wrong?

Share this post


Link to post
Share on other sites
Ximmer: they are disabled in debug mode, but you can enable them if you like

also if you have your own error handling code and a set of exceptions derived from, say, XimmException you can put in the macro that it should rethrow any of those type, so it becomes transparent and only handles really unexpected exceptions.

Share this post


Link to post
Share on other sites
or you can use DbgHelp routines and obtain a "real" stack trace.

+: no overhead for your code at all, because this method doesn't require any per-function information on your part.

-: requires .pdb file to be present on user's machine in order to get function names. .pdb file isn't required for a stack trace with just function addresses. distributing .pdbs is not a good choice if you fear that your program might be hacked. also, .pdb files take up space, although they do compress pretty well.
-: requires dbghelp.dll to be present. it comes with 2k/xp, you'll need to redistribute it for older oses.

alternatively, you can obtain a stack trace with just function addresses using dbghelp and use a .map file on your developer's computer to map addresses to symbols. not particularly exciting, but it may be possible to write a tool that does this automatically. one may already exist.

code complexity: dbghelp takes some time to get right. however, there's quite a bit of sample code floating around the net to help you out.

if you want to use per-function strings, i'd go for a stack implemented using vector that stores pointers to function name strings declared as static const char[] for every function. a helpful macro is essential.

[edited by - niyaw on October 18, 2002 1:08:46 PM]

Share this post


Link to post
Share on other sites
Well, shizzle.

I just whipped this up, and if I do say so myself, it's pretty damn nifty.

It traces the call stack (including the file name and line number it entered the function), all statically without a single dynamic memory allocation.

That should handle all your speed concerns.

CallStackTrace.zip
You can also call CallStackTrace::Dump() to dump the state of the current call stack at any time (not just when an error occurs)

[edited by - daerid on October 18, 2002 4:03:28 PM]

Share this post


Link to post
Share on other sites
quote:
Original post by daerid
I just whipped this up, and if I do say so myself, it''s pretty damn nifty.


damn brilliant i must say.

Share this post


Link to post
Share on other sites
Nice little piece of code. Looks like it could be quite useful!
(Though, not sure how it would fit into my apps. I use a very different debugging approach.)

You should perhaps add a blurb at the top, and then submit it to the code of the day.

I am sure it could be picked apart, improved, and quite an interesting discussion could form about it. (it could reak havok on memory allocation I assume, and perhaps some other problems. But who knows)

In any case, its saved to a file, and to my brain. I likey muchly.

Thanks,
- Kevlar-X

Share this post


Link to post
Share on other sites
I might just do that. One thing though, I''ve never submitted anything. Should I just send an email? or is there a form on the site somewhere?

Share this post


Link to post
Share on other sites
Hmm, those guards make all normal exception throwing invalid:

  
void bleh() {
GUARD("bleh");
if (??)
throw SomeNormalException;
UNGUARD();
}

void caller() {
try {
bleh();
} catch (SomeNormalException& e) {
doSomethingElse();
}
}

Now, if bleh() happens to throw something, the guard stuff will catch it first and add a function name to the CallStackTrace list - even though we did take care of that exception. Now if later on some exception won''t be caught and we call Dump, it''ll contain lots of ''invalid'' function names.

Share this post


Link to post
Share on other sites
niyaw, that's abso-fucking-lutely brilliant. Why the hell didn't I think of that?

[edited by - daerid on October 18, 2002 2:27:43 PM]

Share this post


Link to post
Share on other sites
civguy: I just updated it to ditch the UT-style guard/unguard macros, and added niyaw's suggestion of auto-object.

That way, you keep track of error handling yourself, and call Dump() whenever you want. It more closely mirrors the actual call stack anyways, because if the call stack's unwinding, then the tracer should reflect that.

** EDIT **

Actually, I'm going to ditch the auto-object idea (sorry niyaw ) because it messes up unhandled exception tracing.

I added a TRACE_UNWIND() macro that you can use whenever you handle exceptions to reset the the stack trace to the current function that's handling the exception (simulating a stack-unwind).

[edited by - daerid on October 18, 2002 4:06:02 PM]

Share this post


Link to post
Share on other sites