Resource management

Started by
29 comments, last by SmkViper 9 years, 5 months ago


All of that description can be satisfied with a simple handle or proxy object. You have a handle/proxy to the resource. It serves as a long-term reference that can even be persisted.

Yes, exactly. What I'm talking about is an exercise in imagining a non-intrusive, non-centrally-managed system for proxy/handle semantics. You could accurately call the co-pointer I've mentioned "proxy_ptr" if you wanted to (I'm going to for the rest of this reply, just for simplicity). The somewhat new bit compared to the proxy systems I've seen is that I'm using an owning pointer (something like shared_ptr) and an external control block to facilitate notification, rather than central management, or by intruding on the proxied object or its interface. I think that's useful enough on its own, but I also think there's some room to be more efficient than, say, just using shared_ptr. At this point, I think my weekend project will be to write it up and benchmark it to get a more concrete understanding of its realities.


Maybe I don't understand your issue correctly, but isn't that exactly what one would want?

Absolutely, sometimes, probably even most times. What you go on to describe are indeed valid concerns and useful and good semantics if that is your need. In fact, its exactly the semantics of my own current resource manager that I've been quite happy with. But I think its not always the need. There already are a number of people in the "immediate-single-use proxy camp" I'm not inventing that -- just looking for a different solution than is typical and one which ends up looking a lot like shared_ptr/weak_ptr.

I concede that the semantics of my proxy_ptr don't prevent the object from being ripped out from underneath you by a poorly-timed object de-allocation if you hold onto the reference at all, but neither do non-locking proxy systems of any description, or raw pointers in a multi-threaded context for that matter. And one might reasonably object that if you somehow know that a resource is valid anyway, then a raw-pointer is sufficient -- and it is. But sometimes you might not know, or what you did know when you passed the pointer might have changed before you come to use it -- a proxy_ptr as I describe would be useful for independently propagating a checked, immediate-single-use pointer across an epoch where its resource might or might not have been deleted.


In addendum about shared_ptr inc/dec costs: You shouldn't be doing a lot of those anyway.

Agreed. I've already conceded that using a combination of shared_ptr/weak_ptr/raw pointers smartly goes a long, long way towards achieving efficiency. But it also necessarily gives you semantics you might not want, and exposes you to things you might not want to be exposed to -- e.g. leaking a shared_ptr or creating cycles between shared_ptrs that prevents the object from being deleted. Now, those problems are indicative of some other bug, and ideally would be addressed as such, but those tend to be difficult bugs to track down and I've seen more than a few go out into the wild since leaking a little memory usually isn't a catastrophic issue. Certainly other potential bugs come part and parcel with proxy_ptr, but they'd be of the immediately-crashing type rather than the silently-consume-memory type, and I'd rather have the former since it comes with a clue about what needs fixing. That said, I don't know quite what the efficiency wins might be in exact quantities; I think they're there, but proxy_ptr probably isn't very attractive without some gains;

Anyhow, like I said, I think I'm going to write this up this weekend and see what I find in terms of implementation, performance, and properties. When I manage to get to it I'll report back what I find. I'll start a new discussion thread and link to it from here. I suspect it'll be illuminating even if it ends up being a wild goose chase.

throw table_exception("(? ???)? ? ???");

Advertisement

I concede that the semantics of my proxy_ptr don't prevent the object from being ripped out from underneath you by a poorly-timed object de-allocation if you hold onto the reference at all, but neither do non-locking proxy systems of any description, or raw pointers in a multi-threaded context for that matter.
That's my point. None of these approaches is provably correct. They are "hopefully correct".

Before one worries about small efficiencies, one needs an implementation that is 100% failsafe, one that can be proven to be correct under all conditions. This can be done either by restricting the times when the "manager" can destroy a resource (say, only call the cleanup function at end-of-frame), or by using locks, or by not allowing concurrency at all. Everything else gets tricky and error-prone very quickly.

Handing out weak pointers and locking them into temporary shared pointers guarantees correctness. These two offer an unambiguous standard interface for exactly this tricky and error-prone stuff that we're trying to do there. Locking a weak pointer either succeeds or fails, there is nothing in between. If it succeeds, you know that the object is valid and will remain valid for as long as you hold the reference. Can't be much better.

And one might reasonably object that if you somehow know that a resource is valid anyway, then a raw-pointer is sufficient -- and it is.

Totally. But then again, you can only reliably know that a resource is valid "anyway" if either there is no concurrency at all, or you are holding a lock. Which sucks, kind of.

But then again, you can only reliably know that a resource is valid "anyway" if either there is no concurrency at all, or you are holding a lock. Which sucks, kind of.

You can also know through policy, which can be very speedy, and can assure certain conditions are true.

By policy you can decree that a such-and-such pointer will be valid only through the current frame/update/level/scene, using it after the lifetime is undefined.

By policy you can know that even though many systems are concurrent, the game script can treat the world as though it were single threaded as other changes are restricted.

By policy you can know that shared value is guaranteed to be set before your code runs and you don't need to test for null.

By policy you can guarantee all kinds of conditions.

If I know the pointer is valid for the entire life of the scene and I know through another policy that the object I am writing will be destroyed before the resource is unloaded, then thanks to policy I can know it is perfectly valid to keep the pointer around.

As for concurrency and the lock, if you have concurrency you absolutely must have locks anyway. The locks can be in a broad scale so the individual pieces feel like they are sequential, or the locks can be on a smaller scale with individual parts acquiring and releasing locks based on a reasonable policy that avoids contention. Which style you have is again a matter of policy.

In addendum about shared_ptr inc/dec costs: You shouldn't be doing a lot of those anyway.


Agreed. I've already conceded that using a combination of shared_ptr/weak_ptr/raw pointers smartly goes a long, long way towards achieving efficiency. But it also necessarily gives you semantics you might not want, and exposes you to things you might not want to be exposed to -- e.g. leaking a shared_ptr or creating cycles between shared_ptrs that prevents the object from being deleted. Now, those problems are indicative of some other bug, and ideally would be addressed as such, but those tend to be difficult bugs to track down and I've seen more than a few go out into the wild since leaking a little memory usually isn't a catastrophic issue. Certainly other potential bugs come part and parcel with proxy_ptr, but they'd be of the immediately-crashing type rather than the silently-consume-memory type, and I'd rather have the former since it comes with a clue about what needs fixing. That said, I don't know quite what the efficiency wins might be in exact quantities; I think they're there, but proxy_ptr probably isn't very attractive without some gains;

Anyhow, like I said, I think I'm going to write this up this weekend and see what I find in terms of implementation, performance, and properties. When I manage to get to it I'll report back what I find. I'll start a new discussion thread and link to it from here. I suspect it'll be illuminating even if it ends up being a wild goose chase.


Yeah, the main weakness of ref-counted pointers is circular references will leak (which is one of the reasons the "weak" pointer was invented in the first place). Unfortunately I'm not sure of any way around this short of writing a garbage collector that can walk your heaps and pointers and detect circular references (theoretically possible in C++ though I'm not sure I'd want to see the code...) and those have their own problems like unpredictability. And sometimes you actually do want something to go away when you tell it to. (Which is why C# has the IDisposable/using mechanic)

I look forward to your findings! This has been a fun discussion.

But then again, you can only reliably know that a resource is valid "anyway" if either there is no concurrency at all, or you are holding a lock. Which sucks, kind of.

You can also know through policy, which can be very speedy, and can assure certain conditions are true.


Knowing things by policy is awesome for speedups. Unfortunately I (as a human) am constantly breaking my own policies on accident and those bugs are always a major pain to track down...

Knowing things by policy is awesome for speedups. Unfortunately I (as a human) am constantly breaking my own policies on accident and those bugs are always a major pain to track down...

Seems like a great area to improve your skills. Most developers will add a policy to prevent bad things from happening, rather than looking for blunt instruments that make things safer.

Making complex software is an exercise in discipline. You must carefully design the rules and polices of the software then rigorously follow them. That is rather central to the field. Blunting the tools in the name of safety can have quite negative consequences.

In games (and most high performance computing environments) it is better to have fast tools with rules that are followed and then specifically violated when the situation warrants rather than to use systems that enforce compliance but introduce global costs and inefficiencies. Policies introduce a mental cost to the developer but are zero cost when executing the code.

Knowing things by policy is awesome for speedups. Unfortunately I (as a human) am constantly breaking my own policies on accident and those bugs are always a major pain to track down...


Seems like a great area to improve your skills. Most developers will add a policy to prevent bad things from happening, rather than looking for blunt instruments that make things safer.

Making complex software is an exercise in discipline. You must carefully design the rules and polices of the software then rigorously follow them. That is rather central to the field. Blunting the tools in the name of safety can have quite negative consequences.

In games (and most high performance computing environments) it is better to have fast tools with rules that are followed and then specifically violated when the situation warrants rather than to use systems that enforce compliance but introduce global costs and inefficiencies. Policies introduce a mental cost to the developer but are zero cost when executing the code.


My point was that I'm human and make mistakes - I actually have a non-locking multi-threaded system that is safe via policy, but that doesn't mean I didn't have several very hard to track down bugs during development due to memory corruption or accidental policy violations.

In other words, it's a great tool to have in the toolbox. Just know what you're in for when you use it.


You can also know through policy, which can be very speedy, and can assure certain conditions are true.

By policy you can decree that a such-and-such pointer will be valid only through the current frame/update/level/scene, using it after the lifetime is undefined.

By policy you can know that even though many systems are concurrent, the game script can treat the world as though it were single threaded as other changes are restricted.

By policy you can know that shared value is guaranteed to be set before your code runs and you don't need to test for null.

By policy you can guarantee all kinds of conditions.

this a billion times!

in so may ways, in so many situations, so many hassles and headaches can be avoided "by policy" IE by thoughtful design of code behavior, that complex mechanisms are unnecessary. it seems we so often look to syntax for solutions that can be more eloquently handled by policy (IE designed code behavior).

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php


Knowing things by policy is awesome for speedups. Unfortunately I (as a human) am constantly breaking my own policies on accident and those bugs are always a major pain to track down...

can't do that. coder discipline is mandatory in code that uses behavioral policies. without perfect coder discipline, you can shoot yourself in the foot. you might think of policies as allocated objects you need to remember to deallocate - IE they're around and you have to keep them in mind all the time and handle them properly to avoid problems. another one of those dangling string type things you have to be sure keep in mind and to take care of. In the case of policies, its more about remembering they are in effect, and not violating them, rather than remembering they are in existence and must be dealt with.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php


Seems like a great area to improve your skills. Most developers will add a policy to prevent bad things from happening, rather than looking for blunt instruments that make things safer.

Making complex software is an exercise in discipline. You must carefully design the rules and polices of the software then rigorously follow them. That is rather central to the field. Blunting the tools in the name of safety can have quite negative consequences.

In games (and most high performance computing environments) it is better to have fast tools with rules that are followed and then specifically violated when the situation warrants rather than to use systems that enforce compliance but introduce global costs and inefficiencies. Policies introduce a mental cost to the developer but are zero cost when executing the code.

Another billion times THIS!

every gamedev should be required to tattoo this on their forehead!

i've never heard the argument for performance vs safe code better expressed.

in fact that's probably why i don't tend to use OO syntax - i learned to use policies before OO syntax existed, and therefore found many of the "safer code" features rather useless - or even worse: no safer - yet slower running that using policies.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php


In other words, it's a great tool to have in the toolbox. Just know what you're in for when you use it.

"Just know what you're in for when you use it."

yes, this is the secret - and why its not necessarily recommended for beginners. more stuff to keep track of, more ways to write "incorrect" code with no compiler warning or error that you're doing so. modular testing and debugging and adding "policy violation" to the list of possible causes for bugs can help eliminate policy violating code. eventually, as you work more with policies, and especially as you work on more titles with the same or similar policies, the policies tend to become second nature, and the number of policy violations introduced into the code base in the first place tends to go down. its also very important that these policies be well documented, preferably with a copy appearing in comments in relevant sections of the code as a friendly reminder of the "rules of the game" (policies of interest) when coding.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

This topic is closed to new replies.

Advertisement