A re-announce on GC's defects

B

Born

GC is really garbage itself



Reason 1:



There is delay between the wanted destruction and the actual destruction.



Negative effects by the destruction delay:



1) Efficiency issue



It's bad for CPU/Resource intensive but memory cheap objects.



CPU intensive objects refer to objects who own internal threads.

Resource intensive objects refer to objects who own unmanaged resources like
file handle, network connections, etc.



Don't tell me these objects are rare. Everything is possible to happen and a
general purpose language should not find any excuse not to apply to some
situations.



2) Logic issue



The need for weak reference makes the destruction delay logically incorrect.



Weak references (or you can call them handles) refer to references who do
not require the referenced targets to keep alive, while strong references
do.



When all strong references to a target go out of their lifetime, the target
also comes to the end of its lifetime. Right at this point, weak references
to this target become invalid. However, the destruction delay caused by the
GC violates this logic. Weak references will continue to think the target
alive until the GC really collects the target object.



Don't tell me the IDispose pattern is for that. There may be more than one
strong references and you don't know when and where to call Dispose.



Don't tell me WeakReference (C#) is for that. If you don't have Dispose
called properly, WeakReference still gives a wrong logic.



Don't tell me this can be solved by adding method like IsDestroyed to the
target. It holds a candle to the sun and it only adds to the complexity of
logics.



An example:

Suppose we're doing a 3D game. A radar is monitoring a target. Obviously,
the radar should hold a weak reference to the target. When the target is
killed, logical confusion is immediately brought to the radar watcher (the
gamer). Is the target destroyed or not? You can not tell him, hey, it's
killed but still shown on the radar because you've got to wait for the GC to
make it.





Reason 2:



Poor scalability



This is a Theory issue.



GC is global, which means it scales as the application's memory use scales.
Theoretically, this indicates a bad scalability.



Don't tell me an application should not use too many concurrent objects.
Again, everything is possible. Restrictions only prove the defects of the
language.





Fairly speaking, GC itself is not garbage. However, when java and C#
integrate it and prevent the user from manually managing memory, it becomes
garbage. Note GC in java and C# is not really an addictive as someone would
argue since there is no way to do real memory management like delete obj in
C++.



Better memory management in my mind is reference counting + smart pointer,
which makes things automatic and correct. You have deterministic
destructions while no need to manually call Dispose. You need not to
manually change the reference count as smart pointers help you achieve it.
The only problem with this approach is cyclic reference. However, even if
not theoretically proven, the problem generally can be solved by replacing
some strong references with weak references.



I believe the restriction by GC is one of the main reasons why in some field
(the gaming industry, for example), java or C# is rarely used in serious
products who face real computing challenges.





Solution



1) The ideal solution is to convince the language providers to give us back
the ability of managing memory by our own. GC can still be there, and it
becomes a real addictive in that situation.



2) Transfer the burden to the user. We can ask the user to always take
special cautions (for example, always use "using" in C# to have Dispose
correctly called even exception occurs). Things can work around if the user
do them right. However, that's at risk in nature and not a robust solution.
 
L

Larry Lard

Born said:
GC is really garbage itself [snip]

Better memory management in my mind is reference counting + smart pointer,

So stick with C++ ...
which makes things automatic and correct.

.... because after all, C++ is famous for its programs never having any
kind of memory-allocation-related bugs.

Oh wait.
I believe the restriction by GC is one of the main reasons why in some field
(the gaming industry, for example), java or C# is rarely used in serious
products who face real computing challenges.

How nice for you. Any evidence for this belief, or is it blind faith?
 
M

Michael Moreno

I think you have actually missed many very important reasons why the GC
is bad. I hate it all the way. But we have very little choice. It is
either we use .Net and there is a GC or we use Win 32 compiled app
written in C++ or Delphi. Nothing obliges us to use .Net.
 
M

Michael Nemtsev

Hello Born,

B> GC is really garbage itself
B>
B> Reason 1:
B>
B> There is delay between the wanted destruction and the actual
B> destruction.
B>
B> Negative effects by the destruction delay:
B>
B> 1) Efficiency issue

Does it really bad? :) It only depends on your app context. If it doesnt
meets your requirement welcome back to unmanaged world with manually memmory
management

B> 2) Logic issue
B>
B> The need for weak reference makes the destruction delay logically
B> incorrect.

Caching is the logically incorrect too?

B> The only problem with this approach is cyclic
B> reference. However, even if not theoretically proven, the problem
B> generally can be solved by replacing some strong references with weak
B> references.

But it's logically incorrect, as mentioned before :)

B> I believe the restriction by GC is one of the main reasons why in
B> some field (the gaming industry, for example), java or C# is rarely
B> used in serious products who face real computing challenges.

lol
What does the "serious product" and "real computing challenge" mean for your?

The FPS? :)

BTW, last DX samples are in C#. The real challenge of C# and game industry
is performance.
In 2 - 3 years it will be solved


B> Solution
B>
B> 1) The ideal solution is to convince the language providers to give
B> us back the ability of managing memory by our own. GC can still be
B> there, and it becomes a real addictive in that situation.

Nobody prohibits u to use C++


---
WBR,
Michael Nemtsev [C# MVP] :: blog: http://spaces.live.com/laflour

"The greatest danger for most of us is not that our aim is too high and we
miss it, but that it is too low and we reach it" (c) Michelangel
 
A

Andre Kaufmann

Born said:
GC is really garbage itself
[...]
Negative effects by the destruction delay:
1) Efficiency issue
It's bad for CPU/Resource intensive but memory cheap objects.

It's also bad allocating / deallocating permanently small objects on a
native heap. It's rather time consuming, compared to the managed one and
it doesn't scale that well if you have a multi core CPU.
[...]
2) Logic issue
[...]
Don't tell me the IDispose pattern is for that. There may be more than one
strong references and you don't know when and where to call Dispose.

You could use reference counting as well for a managed object. Instead
of calling the destructor you call Dispose if the reference counter is 0.


[...]
An example:

Suppose we're doing a 3D game. A radar is monitoring a target. Obviously,
the radar should hold a weak reference to the target. When the target is
killed, logical confusion is immediately brought to the radar watcher (the
gamer). Is the target destroyed or not? You can not tell him, hey, it's
killed but still shown on the radar because you've got to wait for the GC to
make it.

What has the state of an object (resource) to do with the memory it has
allocated ? That's the only thing GC is supposed to do - manage memory.
Reason 2:
[...]
Fairly speaking, GC itself is not garbage. However, when java and C#
integrate it and prevent the user from manually managing memory, it becomes
garbage. Note GC in java and C# is not really an addictive as someone would
argue since there is no way to do real memory management like delete obj in
C++.

In C++ you have many classes which handle the memory by them self, e.g.
most STL classes to ensure that not permanently memory is allocated or
that the memory is consecutive. GC handles this automatically.

Better memory management in my mind is reference counting + smart pointer,
which makes things automatic and correct. You have deterministic
destructions while no need to manually call Dispose.

As I wrote, why not also implementing reference counting for managed
objects, which are calling Dispose if the reference count is 0 ?
Though there is a performance impact, because for thread safe reference
counting you have to use Interlocked functions.
You need not to
manually change the reference count as smart pointers help you achieve it.

Agreed, you would have to call them manually in C#, because there's no
RAII. Which I'm really missing in C#.
The only problem with this approach is cyclic reference. However, even if
not theoretically proven, the problem generally can be solved by replacing
some strong references with weak references.

Yes, but it's sometimes tricky and in complex object hierarchies you
have a very high chance to build cyclic references, which are hard to
deal with.
I believe the restriction by GC is one of the main reasons why in some field
(the gaming industry, for example), java or C# is rarely used in serious
products who face real computing challenges.

Hm, perhaps because one can't see that Java or C# is used. E.g. the game
Chrome is written in Java (not 100% but many parts of it)
1) The ideal solution is to convince the language providers to give us back
the ability of managing memory by our own. GC can still be there, and it
becomes a real addictive in that situation.

GC doesn't solve resource allocation problems. They are different as in
C++ and so are the problems you have to face. It's the same with memory
handling. In C++ you still have to think over and over again, how the
memory is handled and if it's better to use an object cache. Otherwise
you will face performance problems too.

2) Transfer the burden to the user. We can ask the user to always take
special cautions (for example, always use "using" in C# to have Dispose
correctly called even exception occurs). Things can work around if the user
do them right. However, that's at risk in nature and not a robust solution.

Isn't that the case ? The developer has to use "using" e.g. for file
objects, which shall release the file handles directly after their usage.

I admit that sometimes I'm missing reference counting, when I'm dealing
with objects stored in multiple lists. How shall I know when to call
dispose ?

E.g. if a file object is stored in 2 or more lists and has to be removed
from one of the lists. How do I know if I have to call Dispose ? Only
performant solution for me would be to use reference counting.
Though you can't have smart pointers, which are automatically destroyed
and will decrease the reference count of an object automatically. You
have to do it manually in C#. :-( - perhaps there's a better solution in
C# that I don't know yet ? (any comments and solutions would be highly
appreciated)

Andre
 
B

Bruce Wood

Born said:
GC is really garbage itself

Hey, I have a lot of patience, but I'm sorry... when someone starts a
post with a patently moronic statement like that, well, I conclude that
that person is a moron.

GC is worthless garbage? That must be why Java was a flash in the pan.
Hardly anyone uses Java. As one poster said, "Oh wait."

Not that I consider one study a definitive conclusion, but the one
cited here

http://www.eweek.com/article2/0,1895,2065392,00.asp

says,

"Java now holds the market penetration lead at 45 percent, followed by
C/C++ at 40 percent, and C# at 32 percent."

(Yes, I realize that the numbers add up to more than 100%... I assume
that that is because some shops, like ours, use more than one
language.)

I'm not game to draw any solid conclusions from these numbers, but a
general conclusion is fair game: for a memory management method that is
"garbage itself" it's doing very well. I don't think it's out of line
to say that more than half of all new software is being written using a
garbage-collected language.

Given this, I think that there are only two possible conclusions that
you can draw:

1. Half of the programmers in the world are benighted idiots who have
not yet attained the lofty heights of intellectual superiority that you
now enjoy. They would be using C++ if only they would realize The
Truth.

2. You're missing something.

Personally, I vote for door #2.
 
B

Bruce Wood

Born said:
Suppose we're doing a 3D game. A radar is monitoring a target. Obviously,
the radar should hold a weak reference to the target. When the target is
killed, logical confusion is immediately brought to the radar watcher (the
gamer). Is the target destroyed or not? You can not tell him, hey, it's
killed but still shown on the radar because you've got to wait for the GC to
make it.

Oh. My. God. You can't seriously tell me that you would use the memory
allocation state of an object to determine whether it is "alive" or not
in a game? Are you really that bad a designer that you would mix the
concepts of a target's relationship to the game and its memory
allocation status? Even when I programmed in C++ I didn't mix ideas
like that: an object is dead when I flag it dead. It may be destructed
immediately, or some time later. What if I decide to keep a list of the
player's kills? Does it then become impossible to kill anything,
because it's in the kill list?

If you're going to trumpet the benefits of deterministic destruction,
please, please don't offer terrible designs as evidence.
I believe the restriction by GC is one of the main reasons why in some field
(the gaming industry, for example), java or C# is rarely used in serious
products who face real computing challenges.

This made me laugh. I love the little caveat, "in some field" You had
to add that because in _many_ fields garbage collected languages do
just fine.

So what is your complaint, really? That a GC language can't handle
every problem in every domain? No shit, Sherlock. There _are_ people
using C# and Java for gaming (the latter mostly for portability to
micro platforms, but that aside...) but I wouldn't use it for that. I
would use C++ to write games for precisely the reasons that you are so
ham-handedly outlining here, which can be summed up in one sentence:
more control. GC is a two-edged sword: it removes a lot of picky
housekeeping that has caused more bugs than anything I know of. (Larry
Lard pointed this out rather sardonically in his post: C++ is infamous
for memory allocation bugs.) The other edge of the sword is that GC
removes control from the programmer, which is what you're complaining
about. For _most_ applications, the loss of control doesn't matter. For
some, such as gaming, it does. So, in those domains, use C++.

I no longer consider C++ a serious language for business development.
You know, the meat-and-potatoes, number-crunching applications that
make up most of the world's software. Why? Because C++ adds a lot of
picky housekeeping (memory management) that gains me nothing. I don't
need the additional control it offers, and the cost of that control is
bugs. For some domains (such as gaming), the tradeoff is warranted. For
most, it's not, which is why Java and C# are doing so well.

If you're so close to the metal that the tiny delays introduced by
..NET's GC will screw up your app, then the answer is simple: DON'T USE
IT. USE C++.

In the end, what are you trying to prove here? That C# can't tackle
every problem under the sun? We all know that. Move on. Nothing to see
here. Or are you trying to demonstrate that C# isn't suited to any
problem domain at all? If so then I refer you to my previous post:
you're missing something.
1) The ideal solution is to convince the language providers to give us back
the ability of managing memory by our own. GC can still be there, and it
becomes a real addictive in that situation.

And this buys us... what? The ability to use C# / Java in a few domains
where it isn't working well right now? Why not just use a better-suited
language? What is it about programmers, all of us, that makes us want
to invent the super-duper maxed-out Swiss Army Knife of programming
languages that can do anything? They already tried that. It was called
Ada. How many people still use Ada? So what if game development is
mostly done using C++ and not C#? Would you do brain surgery with a
Swiss Army Knife? Of course not: you would use specialized tools. What,
then, is so wrong about using the best language suited to a particular
domain, rather than trying to create the language that can do anything?
 
B

Brian Gideon

Born said:
GC is really garbage itself



Reason 1:



There is delay between the wanted destruction and the actual destruction.



Negative effects by the destruction delay:



1) Efficiency issue



It's bad for CPU/Resource intensive but memory cheap objects.



CPU intensive objects refer to objects who own internal threads.

Resource intensive objects refer to objects who own unmanaged resources like
file handle, network connections, etc.



Don't tell me these objects are rare. Everything is possible to happen and a
general purpose language should not find any excuse not to apply to some
situations.

They're not rare. In fact, they're all over the place. That's why
IDisposable was created. It provides a mechanism for doing
deterministic finalization. That leaves managed memory as the only
resource whose cleanup is necessarily delayed. But, I think the
decrease in efficiency here is more than offset by the increase in
efficiency during memory allocation.
2) Logic issue



The need for weak reference makes the destruction delay logically incorrect.



Weak references (or you can call them handles) refer to references who do
not require the referenced targets to keep alive, while strong references
do.



When all strong references to a target go out of their lifetime, the target
also comes to the end of its lifetime. Right at this point, weak references
to this target become invalid. However, the destruction delay caused by the
GC violates this logic. Weak references will continue to think the target
alive until the GC really collects the target object.

That's one of the advantages of a WeakReferece though. You can
resurrect a WeakReference target. What GC logic does that violate?
Don't tell me the IDispose pattern is for that. There may be more than one
strong references and you don't know when and where to call Dispose.

It depends on whose perspective you're considering. As the developer
of the object you have little control over when or if Dispose gets
called, but as the caller of the object you have absolute control.
Isn't it the callers responsibility to decide when the object isn't
needed any longer? Maybe it's just me, but I'd hate it if the
FileStream decided for me when I was done writing to a file. Now, if
the FileStream reference falls out of scope (and assuming that I don't
have references to it elsewhere) before I've called Dispose then it's
my fault for not using it correctly. Still, the GC is nice enough to
call Dispose for me as a last resort. So, I think I understand your
point. I just don't see it as a big problem.
Don't tell me WeakReference (C#) is for that. If you don't have Dispose
called properly, WeakReference still gives a wrong logic.

A WeakReference isn't suppose to be used as a counter-measure to a
missing Dispose call. That's what the class' destructor (or Finalize
method) is used for.
Don't tell me this can be solved by adding method like IsDestroyed to the
target. It holds a candle to the sun and it only adds to the complexity of
logics.



An example:

Suppose we're doing a 3D game. A radar is monitoring a target. Obviously,
the radar should hold a weak reference to the target.

I'd hardly call that an obvious conclusion. I would not use a
WeakReference in this situation. In fact, I rarely use a WeakReference
at all.
When the target is
killed, logical confusion is immediately brought to the radar watcher (the
gamer). Is the target destroyed or not? You can not tell him, hey, it's
killed but still shown on the radar because you've got to wait for the GC to
make it.

So don't rely on the GC to propagate that information to the radar.
Code it deterministically.
Reason 2:



Poor scalability



This is a Theory issue.



GC is global, which means it scales as the application's memory use scales.
Theoretically, this indicates a bad scalability.



Don't tell me an application should not use too many concurrent objects.
Again, everything is possible. Restrictions only prove the defects of the
language.

I disagree with your poor scalability argument. Creating a new object
in .NET is blindingly fast. Do some benchmarks. This speed is
*because* of the GC not in *spite* of it. What might be problematic is
that the GC will suspend all threads in the application while it is
cleaning up memory.
Fairly speaking, GC itself is not garbage. However, when java and C#
integrate it and prevent the user from manually managing memory, it becomes
garbage. Note GC in java and C# is not really an addictive as someone would
argue since there is no way to do real memory management like delete obj in
C++.



Better memory management in my mind is reference counting + smart pointer,
which makes things automatic and correct. You have deterministic
destructions while no need to manually call Dispose. You need not to
manually change the reference count as smart pointers help you achieve it.
The only problem with this approach is cyclic reference. However, even if
not theoretically proven, the problem generally can be solved by replacing
some strong references with weak references.



I believe the restriction by GC is one of the main reasons why in some field
(the gaming industry, for example), java or C# is rarely used in serious
products who face real computing challenges.

I think it has more to do with the GC suspending threads at
unpredictable times. That would cause the display to appear jerky.
This may be one scenario where a strategically placed call to
GC.Collect would be appropriate. Regardless, I don't think C# was ever
intended to be a tool for gamer developers.
 
M

Michael D. Ober

Andre Kaufmann said:
Born said:
GC is really garbage itself
[...]
Negative effects by the destruction delay:
1) Efficiency issue
It's bad for CPU/Resource intensive but memory cheap objects.

It's also bad allocating / deallocating permanently small objects on a
native heap. It's rather time consuming, compared to the managed one and
it doesn't scale that well if you have a multi core CPU.
The framework GC is actually one of the more efficient garbage collectors
out there. Properly done, reference counting can take up to 50% of your
program's CPU cycles. Reference counting will also leave objects that refer
to each other in memory, even when no other objects refer to them. Google
for my GC posts in late 2005 for sample code in VB 6 and VB 2005 for a
simple program that demonstrates the difference between the mark/sweep
algorithm in dotNet vs the reference counting algorithm in the VB 6/COM
model.

The dotNet GC is actually a three generation GC. What this means is that
when the Gen(0) heap fills up, all accessible objects are marked and their
size is computed. If there is insufficient space in the Gen(1) heap, then
and only then is the Gen(1) heap cleaned up to the Gen(2) heap. Once there
is sufficient space in the Gen(1) heap, the marked objects in the Gen(0)
heap are copied to the Gen(1) heap and the heap pointer in Gen(0) is reset
to the base address. The only time a full mark/sweep/compact garbage
collection is done is when the Gen(2) heap is full. This appears to be done
before the framework requests additional memory from the OS.
[...]
2) Logic issue
[...]
Don't tell me the IDispose pattern is for that. There may be more than
one strong references and you don't know when and where to call Dispose.

You could use reference counting as well for a managed object. Instead of
calling the destructor you call Dispose if the reference counter is 0.

You never have to call the IDispose interface. The GC will do this for you.
Yes, it does result in those objects possibly being left in memory for one
more GC cycle, but the benefit is that you, as the programmer, never need
worry about dangling pointers and memory leaks. As long as your objects
themselves clean up properly in the dispose method, memory leaks and
dangling pointers simply cannot occur. The GC class even provides
interfaces to that if your object allocates a lot of unmanaged system
memory, you can tell the framework how much is being allocated in its
constructor. You then tell the framework about the release of this memory
in your Dispose method.

[...]
An example:

Suppose we're doing a 3D game. A radar is monitoring a target. Obviously,
the radar should hold a weak reference to the target. When the target is
killed, logical confusion is immediately brought to the radar watcher
(the gamer). Is the target destroyed or not? You can not tell him, hey,
it's killed but still shown on the radar because you've got to wait for
the GC to make it.

What has the state of an object (resource) to do with the memory it has
allocated ? That's the only thing GC is supposed to do - manage memory.

You're thinking is backwards. You are letting your resource management
determine the scope of an object's lifetime. You need to use the
applicatoin domain logic, in this case, the object going off the edge of the
radar or being destroyed by a missile to remove the references to the
object. Yes, the object's resources will still be allocated for an
indeterminate time, but the object will never again be able to appear on
your radar anyway. The runtime will eventually get around to releasing
those resources via the object's IDispose interface - you don't have to do
it yourself. In C++ you must explicitely handle the release of the memory
at some point in your program execution. In C#, the runtime will execute
the destruction code for you when it either has idle time or needs the
memory to satisfy a allocation request.
Reason 2:
[...]
Fairly speaking, GC itself is not garbage. However, when java and C#
integrate it and prevent the user from manually managing memory, it
becomes garbage. Note GC in java and C# is not really an addictive as
someone would argue since there is no way to do real memory management
like delete obj in C++.

In C++ you have many classes which handle the memory by them self, e.g.
most STL classes to ensure that not permanently memory is allocated or
that the memory is consecutive. GC handles this automatically.

Better memory management in my mind is reference counting + smart
pointer, which makes things automatic and correct. You have deterministic
destructions while no need to manually call Dispose.

As I wrote, why not also implementing reference counting for managed
objects, which are calling Dispose if the reference count is 0 ?
Though there is a performance impact, because for thread safe reference
counting you have to use Interlocked functions.

The performance impact can be huge. Early Smalltalk implementations spent
50% of their time reference counting.
Agreed, you would have to call them manually in C#, because there's no
RAII. Which I'm really missing in C#.

When using weak references, you still need to handle dangling pointer
exceptions.
Yes, but it's sometimes tricky and in complex object hierarchies you have
a very high chance to build cyclic references, which are hard to deal
with.


Hm, perhaps because one can't see that Java or C# is used. E.g. the game
Chrome is written in Java (not 100% but many parts of it)

It's there is C# - you can mix C++ modules with C#. The penalty is that you
now spend your time managing memory.
GC doesn't solve resource allocation problems. They are different as in
C++ and so are the problems you have to face. It's the same with memory
handling. In C++ you still have to think over and over again, how the
memory is handled and if it's better to use an object cache. Otherwise you
will face performance problems too.

This is what the IDispose and Finalize model is for. Yes, it takes an
additional GC cycle to free the memory, but the object has a chance to clean
up after itself first.
Isn't that the case ? The developer has to use "using" e.g. for file
objects, which shall release the file handles directly after their usage.

I admit that sometimes I'm missing reference counting, when I'm dealing
with objects stored in multiple lists. How shall I know when to call
dispose ?

E.g. if a file object is stored in 2 or more lists and has to be removed
from one of the lists. How do I know if I have to call Dispose ? Only
performant solution for me would be to use reference counting.
Though you can't have smart pointers, which are automatically destroyed
and will decrease the reference count of an object automatically. You have
to do it manually in C#. :-( - perhaps there's a better solution in C#
that I don't know yet ? (any comments and solutions would be highly
appreciated)

You never need to call Dispose. By telling the compiler that an object
class implements IDisposable, the GC will call Dispose for you before it
actually deallocates memory.

I have researched various allocation/deallocation schemes over the years.
Here's a quick summary:

Explicit allocation/deallocation
- Example: C malloc/dealloc and C++ new/delete
- Benefits: Very easy to implement in the runtime and inital
allocations/deallocations are fast.
- Drawbacks: Dangling pointers, attempts to access deallocated/deleted
objects (null pointer references), heap fragmentation
- Where I would use: Only in Real-Time environments where reliable response
time is the driving factor.

Reference Counting:
- Example: Early SmallTalk and VB 6
- Benefits: Programmer doesn't have to worry about memory allocation.
Impossible to dangle pointers or attempt to dereference pointers to
deallocated objects.. Easy to implement in the runtime
- Drawbacks: Cyclic objects never being freed from memory. Early SmallTalk
implementations spent up to 50% of their time doing reference counting.
Doesn't handle resources other than memory.
- Where I would use: General purpose computing

Simple Mark/Sweep/Compact
- Example: Some older Lisp implementations
- Benefits: Programmer doesn't have to worry about memory allocation. Self
referencing objects will be collected.
- Drawbacks: Long pauses while the GC cycle runs. I've seen Lisp machines
pause for several minutes while the GC cycle runs. Implementation can be
tricky. Object lifetime cannot be predicted.
- Where I would use: General purpose computing.

Multi-Generational Mark/Sweep/Compact with explicit disposal interfaces
- Example: Java and dotNet
- Benefits: Programmer controls the allocation of unmanaged resources
through constructors and deallocation through destructors, limiting the
amount of code that actually handles this issue. Usually faster than simple
mark/sweep/compact since most GC passes don't need to process all
generations of memory, especially since most objects have very short
accessible lifetimes and won't be accessible the next time the GC needs to
run.
- Drawbacks: Hard to implement (but not too much more difficult than simple
mark/sweep/compact). Object lifetime cannot be predicted. Must provide at
least one more generation than the maximum number of generations the
destruction of an object can take. Otherwise this becomes a very bloated
version of the simple mark/sweep/compaction algorithm.
- Where I would use: Everywhere, including most real-time systems.

As you move up this chain of memory management, you're thinking of how to
manage objects in your program needs to change, especially when moving from
the explicit allocation/deallocation to any of the other three models.
Moving from Reference counting to mark/sweep removes the restriction of not
creating cyclic references, making the end developer's job easier. Moving
from simple mark/sweep/compaction to generational provides, in most cases,
is a major performance boost for the same source code.

Mike Ober.
 
B

Barry Kelly

Born said:
There is delay between the wanted destruction and the actual destruction.

The "destruction" (i.e. disposal) happens under the caller's control.
It's up to the user how much of a delay they want, not GC.

Don't forget that GC manages memory, not resources. If you are trying to
make an argument that GC is bad for managing resources, the simple
answer is that it's not *designed* for managing resources.
1) Efficiency issue
It's bad for CPU/Resource intensive but memory cheap objects.
CPU intensive objects refer to objects who own internal threads.
Resource intensive objects refer to objects who own unmanaged resources like
file handle, network connections, etc.

Here you are, criticising the memory-managing GC for not managing
resources well, the very job it's not designed for.
2) Logic issue

The need for weak reference makes the destruction delay logically incorrect.

Destruction, i.e. disposal of resources other than memory, is the
caller's responsibility, not the GC. GC applies to the collection of
memory, and it's logically correct in the way it manages memory - i.e.
the object lifetime.
Poor scalability
GC is global, which means it scales as the application's memory use scales.
Theoretically, this indicates a bad scalability.

Given enough memory, a copying GC (like the CLR GC) is provably more
efficient than manual memory allocation that treats each allocation
individually. Let me repeat that: it's actually provably *more* scalable
than manual memory allocation.
Note GC in java and C# is not really an addictive as someone would
argue since there is no way to do real memory management like delete obj in
C++.

'delete obj' is a memory safety hole. If there are any other references
to the object being deleted, you've just created a security violation.
That's what GC is there to prevent.
Better memory management in my mind is reference counting + smart pointer,
which makes things automatic and correct.

Reference counting *is* GC - poor GC, because it doesn't deal with
cycles.
You have deterministic
destructions while no need to manually call Dispose.

Here you are confusing GC with resource management again.

-- Barry
 
B

Barry Kelly

Michael said:
You never have to call the IDispose interface. The GC will do this for you.

The GC never calls Dispose(). It makes its best effort to call
Finalize() as soon as possible if it's been overridden, but very few
disposable (implementing IDisposable) objects should have finalizers
(which should be restricted to handle-like objects).

The implementing Finalize() ("~ClassNameHere()" in C#) according to
recommended practices involves writing a protected virtual Dispose(bool)
method, but that's a separate (yet related) issue to implementing
IDisposable.

Do not confuse IDisposable with Finalize(). Any class overriding
Finalize() should implement IDisposable, but the reverse is *not* true.
Objects should implement IDisposable when they override Finalize, or if
they are the logical owner of any other resource which implements
IDisposable.

And ignore the horrific example of System.ComponentModel.Component. That
class is like a car accident :). You should implement Finalize() only on
handle-like classes, or preferably descend from SafeHandle (or one of
its descendants) and override the appropriate methods.
The runtime will eventually get around to releasing
those resources via the object's IDispose interface - you don't have to do
it yourself.

You've got the .NET GC wrong, I'm afraid. It knows nothing about
IDispose, or the Dispose() method. And it certainly tries hard to call
finalizers on finalizable objects (objects overriding the Finalize()
method) as soon as possible, but leaving resource deallocation up to the
GC is irresponsible and a recipe for bugs. It will leave sockets open,
files open and locked, database connections open etc. longer than
necessary, and will ultimately result in unexpected resource acquisition
failures.

For an easy life, deterministically dispose your resources, please!
In C#, the runtime will execute
the destruction code for you when it either has idle time or needs the
memory to satisfy a allocation request.

..NET doesn't have an exact analogue to C++ destruction. C++ destruction
combines two things: (1) memory disposal and (2) resource disposal. In
..NET, the GC does (1) and has facilities to catch (2) as a *last* line
of defense, while IDisposable is for (2).
You never need to call Dispose. By telling the compiler that an object
class implements IDisposable, the GC will call Dispose for you before it
actually deallocates memory.

Please, educate yourself before you spread more misinformation!

-- Barry
 
B

Bruce Wood

Barry said:
Please, educate yourself before you spread more misinformation!

Sorry, Barry, but could you provide some references? I believe your
description of how finalizers / IDisposable interact, but I too have
read (somewhere) on MSDN that the _ideal_ is to write your classes so
that if the caller forgets to Dispose your objects, it will be taken
care of in garbage collection, later.

I realize that it's possible to write classes that don't act that way;
I'm talking about "best practices" here.

I'm fully open to being corrected. This is a vague memory of "something
I read," nothing more.
 
B

Barry Kelly

Bruce said:
Sorry, Barry, but could you provide some references?

For what exactly? What do you think I'm saying that you disagree with?
I believe your
description of how finalizers / IDisposable interact,

I take this to be referring to how IDisposable objects don't necessarily
need to implement a finalizer, but anything implementing a finalizer
should implement IDisposable, but that's just a guess. I'm verbose below
to try to be clear.
but I too have
read (somewhere) on MSDN that the _ideal_ is to write your classes so
that if the caller forgets to Dispose your objects, it will be taken
care of in garbage collection, later.

Of course - I never contradicted that the GC has finalizer support as a
last line of defense, and that this should be used (or preferably
SafeHandle or one of its descendants) to implement that last line of
defense. I mention SafeHandle etc. in the grandparent, and that it
should be used for handle-like objects that wrap unmanaged resources.

Objects that wrap managed resources shouldn't implement finalizers
because you can't (reliably) access other managed objects from a
finalizer (they may or may not have been collected already), because
order of finalization isn't guaranteed. Instead, they should implement
IDisposable, and call Dispose() (or the relevant Close()) on the managed
resources, preferably via a protected virtual Dispose(bool) method so it
works nicely (polymorphically) with descendants.

See the documentation on Object.Finalize():

"Finalize operations have the following limitations:

* The exact time when the finalizer executes during garbage collection
is undefined. Resources are not guaranteed to be released at any
specific time, unless calling a Close method or a Dispose method.

* The finalizers of two objects are not guaranteed to run in any
specific order, even if one object refers to the other. That is, if
Object A has a reference to Object B and both have finalizers, Object B
might have already finalized when the finalizer of Object A starts.

The thread on which the finalizer is run is unspecified.

The Finalize method might not run to completion or might not run at all
in the following exceptional circumstances:

* Another finalizer blocks indefinitely (goes into an infinite loop,
tries to obtain a lock it can never obtain and so on). Because the
runtime attempts to run finalizers to completion, other finalizers might
not be called if a finalizer blocks indefinitely.

* The process terminates without giving the runtime a chance to clean
up. In this case, the runtime's first notification of process
termination is a DLL_PROCESS_DETACH notification."

It's pretty clear in there that you can't absolutely rely on
(1) finalizers always executing, e.g. to close files properly etc., or
(2) the order of finalization.
I realize that it's possible to write classes that don't act that way;
I'm talking about "best practices" here.

I'm fully open to being corrected. This is a vague memory of "something
I read," nothing more.

I don't know what you're objecting to, exactly. Microsoft haven't helped
here - there's a lot of confusion even now, years after the fact - with
mixing/confusing these two things:

* implementing IDisposable & protected virtual Dispose(bool)
- this is common, because most code wants to affect the world,
which means dealing with resources, which need to be
deterministically disposed of for reliable operation.

* implementing a finalizer with C# "~YourClassName()"
- this is rare, because it implies one or both of:
. working with unmanaged code directly via handles etc.
. something pretty advanced, where the exact behaviour of
the GC is integral to the design

The mistakes were partially fixed in, e.g. C++/CLI, which (unlike its
predecessor MC++) doesn't try to make Finalizers look like destructors,
and instead creates a new declaration construct for them -
"!YourClassName()".

-- Barry
 
B

Born

I admit that sometimes I'm missing reference counting, when I'm dealing
with objects stored in multiple lists. How shall I know when to call
dispose ?

E.g. if a file object is stored in 2 or more lists and has to be removed
from one of the lists. How do I know if I have to call Dispose ? Only
performant solution for me would be to use reference counting.
Though you can't have smart pointers, which are automatically destroyed
and will decrease the reference count of an object automatically. You have
to do it manually in C#. :-( - perhaps there's a better solution in C#
that I don't know yet ? (any comments and solutions would be highly
appreciated)

Andre


Very good. You gave me an another example.
 
B

Born

">
Oh. My. God. You can't seriously tell me that you would use the memory
allocation state of an object to determine whether it is "alive" or not
in a game?

Open you mind. The memory allocation state is a general solution to inform
a weak reference that its target has gone. So why should I use domain
context to complicate things?

Again: It holds a candle to the sun.

where it isn't working well right now? Why not just use a better-suited
language?


Be creative. The world can be better. Imagine C++ with enhanced features
like Reflection in C#. This is what I'm trying to prove here. Actually I'm
checking with C++/CLI, but I'm not sure how good it can be.
 
M

Michael D. Ober

Barry,

I stand corrected. I got the Finalizer and Dispose interfaces backwards.
The Dispose interface drives finalization overrides, not the other way
around. The concept of using Dispose to clean up is still valid, however.

Mike.
 
B

Born

I have to say my charges are not answered. No practical solution but
criticisms. I was told C# is not fit for my need or I gave bad cases.

As I said, what I want is a better world. The C++ can be better if it
integrates some new features. The C#/jave can be better if they makes GC
really an addictive. I know C# and java have gained vast success, but that
should not become the reason for us not to improve them.
 
B

Barry Kelly

Andre said:
I admit that sometimes I'm missing reference counting, when I'm dealing
with objects stored in multiple lists. How shall I know when to call
dispose ?

E.g. if a file object is stored in 2 or more lists and has to be removed
from one of the lists. How do I know if I have to call Dispose ?
Only
performant solution for me would be to use reference counting.
Though you can't have smart pointers, which are automatically destroyed
and will decrease the reference count of an object automatically. You
have to do it manually in C#.

A solution (but you're not going to like it) would be to do it manually
via IDisposable. Create a proxy with the reference count and a reference
to the resource, and create handles with a reference to both the
resource and the proxy and that decrement the reference count when
disposed. Have the proxy dispose the resource when the refcount reaches
0. Only hand out handles to the resource, and (if necessary) wrap access
to the resource so that it's not visible to the outside. The handles
could have a Clone() method that creates a new handle and increments the
refcount.

That technique turns N-ownership into N instances of 1-ownership. So
theoretically, if you're able to deal with a single parent, you're able
to deal with N single parents (and thus N parents).

-- Barry
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top