3

I have a C# program that tracks a player's position in a game. In this program I have a class called Waypoint (X, Y, Z), which represents a location on the game map. In one of the threads I spawn, I keep checking the player's distance from a certain target Waypoint, quite rapidly after each other in while(true) loops. In the Waypoint class have a method called public double Distance(Waypoint wp), that calculates the distance from the current waypoint, to the waypoint passed as a parameter.

Question: Is it okay to create a new Waypoint for the player's position, every time I want to check the distance from player to the target waypoint? The program would then potentially, in a while(true) loop, create this player Waypoint over and over again, just for the purpose of calculating the distance.

PS: My program probably needs to smartly use resources, as it is running multiple threads with continuous while loops doing various work such as posting the player's X,Y,Z location to the UI.

Thanks a lot!

Magnus
  • 6,791
  • 8
  • 53
  • 84
  • Answer performance questions by *measuring your performance*. Set a budget: say, a maximum working set, or a maximum time spent in collections. Then measure to see if you exceed your budget. If you don't, then you're under budget so worry about something else. If you do, then *make a change in your allocation strategy and measure the impact*. – Eric Lippert Aug 21 '14 at 17:06

2 Answers2

0

What the other answers are saying is:
- maybe you should make stack-local instances because it shouldn't cost much, and
- maybe you shouldn't do so, because memory allocation can be costly.
These are guesses - educated guesses - but still guesses.

You are the only one who can answer the question, by actually finding out (not guessing) if those news are taking a large enough percent of wall-clock time to be worth worrying about.

The method I (and many others) rely on for answering that kind of question is random pausing.

The idea is simple. Suppose those news, if somehow eliminated, would save - pick a percent, like 20% - of time. That means if you simply hit the pause button and display the call stack, you have at least a 20% chance of catching it in the act. So if you do that 20 times, you will see it doing it roughly 4 times, give or take.

If you do that, you will see what's accounting for the time.
- If it's the news, you will see it.
- If it's something else, you will see it.
You won't know exactly how much it costs, but you don't need to know that.
What you need to know is what the problem is, and that's what it tells you.


ADDED: If you'll bear with me to explain how this kind of performance tuning can go, here's an illustration of a hypothetical situation:
enter image description here
When you take stack samples, you may find a number of things that could be improved, of which one of them could be memory allocation, and it might not even be very big, as in this case it is (C) taking only 14%. It tells you something else is taking a lot more time, namely (A).

So if you fix (A) you get a speedup factor of 1.67x. Not bad.

Now if you repeat the process, it tells you that (B) would save you a lot of time. So you fix it and (in this example) get another 1.67x, for an overall speedup of 2.78x.

Now you do it again, and you see that the original thing you suspected, memory allocation, is indeed a large fraction of the time. So you fix it and (in this example) get another 1.67x, for an overall speedup of 4.63x. Now that is serious speedup.

So the point is 1) keep an open mind about what to speed up - let the diagnostic tell you what to fix, and 2) repeat the process, to make multiple speedups. That's how you get real speedup, because things that were small to begin with become much more significant when you've trimmed away other stuff.

Community
  • 1
  • 1
Mike Dunlavey
  • 40,059
  • 14
  • 91
  • 135
  • My answer is explaining exactly why this is wrong, and in a non-intuitive manor. The actual act of creating the objects is amazingly fast, and likely won't be measurable at all for the program. Your suggested approach for benchmarking it won't be effective. However, under a perfect storm of circumstances, it can cause problems *indirectly* (specifically by affecting the garbage collector). My answer is explaining that *that* is what needs to be benchmarked to evaluate if a change needs to be made. Your solution won't indicate a problem even if one exists. – Servy Aug 22 '14 at 17:52
  • Your edit is continuing to add even more information that tends to be good and useful performance benchmarking advice in general, but that just so happens to *not apply at all to this specific problem* due to it's unusual nature. – Servy Aug 22 '14 at 18:36
  • @Servy: You've been on this site long enough that I'm sure you've seen [*this*](http://stackoverflow.com/a/927773/23771), [*this*](http://stackoverflow.com/a/378024/23771), and [*this*](http://archive.today/9r927), and the theoretical foundation [*here*](http://scicomp.stackexchange.com/a/2719/1262). At any rate, creating objects may be amazingly fast, but no matter how fast, it takes some fraction of overall time, and if that fraction is significant, then not doing it would save that fraction. – Mike Dunlavey Aug 22 '14 at 18:41
  • And the application would have had significant problems *long*, *long* before you reach that point. These operations that you're talking about here are just a handful of processor cycles. Even a low end, dated machine can perform quite a few *billion* of them a second. Then compare that to a garbage collection that, if the program is designed poorly, can take a *lot* longer than just a few processor cycles per item allocated. *That* is where the bottleneck would show up, and so that's where he should be looking for trouble. – Servy Aug 22 '14 at 18:45
  • You're actively directing the user away from the benchmark that will show him whether or not he has a problem and towards a benchmark that *won't* show him if he has a problem until he's *several orders of magnitude* beyond where he should be. – Servy Aug 22 '14 at 18:46
  • @Servy: 1) Performance questions are often [*XY problems*](http://mywiki.wooledge.org/XyProblem), and it may be helpful to the OP to suggest another way to look at it. 2) No matter how fast the machine is, what matters is the % of time doing each thing - creating point objects may take very few cycles, but if those are 40% of the overall time, it's worth looking at. If 10%, maybe something else should be fixed first. Again, speed of processor doesn't matter - percents matter. (BTW - Those downvotes didn't come from me - I don't like to do that. You were being very informative.) – Mike Dunlavey Aug 22 '14 at 19:09
  • 1) Yes, they can be, *and you're pointing him off in the wrong direction*. 2) That would mean creating many billions or trillions of these objects, doing just enough with them that the runtime can't optimize out their creation entirely, but not enough to consume just about any measurable time. If you were *actively trying* to make this a problem, I don't know if you could. And on top of that the GC would *still* be far more of a problem than this code would be. – Servy Aug 22 '14 at 19:13
  • In effect the whole idea of the memory model of C# is that it defers pretty much all of the work involved in creating objects. The work is deferred to the actual collection, not the initialization. It's inherent in the nature of that design that problems involving the excessive allocations of objects will be when they are collected, not initialized. The whole point is that if the OP has a problem with creating too many objects *your metric wouldn't tell him*. Your metric wouldn't indicate a problem until he was allocating a few *hundred million times* more objects than he should be. – Servy Aug 22 '14 at 19:15
-1

The actual creation of an object with a very short lifetime is minuscule. Creating a new object pretty much just involves incrementing the heap pointer by the size of the object and zeroing out those bits. This isn't going to be a problem.

As for the actual collection of those objects, when the garbage collector performs a collection it is taking all of the objects still alive and copying them. Any objects not "alive" are not touched by the GC here, so they aren't adding work to collections. If the objects you create never, or very very rarely, are alive during a GC collection then they're not adding any costs there.

The one thing that they can do, is decrease the amount of currently available memory such that the GC needs to perform collections noticeably more often than it otherwise would. The GC will actually perform a collection when it needs some more memory. If you'r constantly using all of your available memory creating these short lived objects, then you could increase the rate of collections in your program.

Of course, it would take a lot of objects to actually have a meaningful impact on how often collections take place. If you're concerned you should spend some time measuring how often your program is performing collections with and without this block of code, to see what effect it is having. If you really are causing collections to happen much more frequently than they otherwise would, and you're noticing performance problems as a result, then consider trying to address the problem.

There are two avenues that come to mind for resolving this problem, if you have found a measurable increase in the number of collections. You could investigate the possibility of using value types instead of reference types. This may or may not make sense conceptually in context for you, and it may or may not actually help the problem. It'd depend way too much on specifics not mentioned, but it's at least something to look into. The other possibility is trying to aggressively cache the objects so that they can be re-used over time. This also needs to be looked at carefully, because it can greatly increase the complexity of a program and make it much harder to write programs that are correct, maintainable, and easy to reason about, but it can be an effective tool for reintroducing memory if used correctly.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • Thank you, that is helpful. I realize that a lot of people say to put variable initializing as close to the point of use as possible. Since "int a = 100;" is the same as "int a = new int(); a = 100", and I frequently do this inside loops and never experienced any issues, I will try to do the same here (in other words, keep new directive inside loop). – Magnus Aug 21 '14 at 15:39