Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

C++ desired features

13 views
Skip to first unread message

Michael Pryhodko

unread,
Jan 4, 2005, 4:35:28 AM1/4/05
to
Hello

I started reading this newsgroup quite recently (~2 month) -- please,
do not upset if something wrong. In my C++ practice I developed some
ideas and tricks I'd like to share and discuss... So I decided to start
with this:

Here is the list of features that comes to my mind whenever someone
mention C++ 'desired features':

1. 'typeof' support -- quite obvious. Does anybody knows if it will be
included in the next C++ standard?

2. fix 'forward declaration problem' for type aliases:
// type alias declaration
typedef foo<int> FooAlias;
...
// forward declaration in another translation unit
class FooAlias; // will not work :(

Since type alias is intended for convenient way to reference real type
it is a shame you can not use it in forward declarations.

3. 'template typedef' -- almost everybody want it :). Here is how it
could be implemented:

// some templates
template<class T> class type {};
template<class T, class U> class ptr {};

// convenient shortcut
templatedef<class T, class U> ptr< T, type<U> > TmplAlias;

Rules are pretty the same as for 'typedef':
- just like typedef does not create new type 'templatedef' does not
create new template -- it is only convenient shortcut to given
'template-name + params' entity.
- it has similar syntax
- templatedef for template functions could be built to look similar to
typedef for usual functions.

I'd like to outline some advantages of this 'templatedef':
a) you can create fine-looking and human-readable complex template
specializations, e.g.:

template<class U> class TmplAlias<U, U> {};
is equivalent to:
template<class U> class ptr<U, type<U> > {};

b) you can change number of template parameters in order to pass
template as template template argument, e.g.:

template<class U, class T> class foo;
template<template<class A> class T> class B;

templatedef<class U> foo< U*, foo2<U> > TmplAlias;
B<TmplAlias> foo_var; // cannot be done without templatedef

c) consequently you can get around that nasty 'default template
parameters' problem, e.g.:

template<template<class A> class T> class B;
template<class T, class U = A> class Foo;

templatedef<class T> Foo<T> Foo_without_def_params;
// now Foo_without_def_params can be used whenever
// template with one parameter required, e.g.:

B<Foo_without_def_params> foo_var;


Well... I suppose this is quite enough for one posting. What do you
think about this?

Bye.
Sincerely yours, Michael.


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

ntrif...@hotmail.com

unread,
Jan 4, 2005, 1:20:43 PM1/4/05
to
Take a look at this article:
http://www.artima.com/cppsource/wishlist.html

Francis Glassborow

unread,
Jan 4, 2005, 1:19:03 PM1/4/05
to
In article <1104811678.9...@f14g2000cwb.googlegroups.com>,
Michael Pryhodko <mpry...@westpac.com.au> writes

>Hello
>
>I started reading this newsgroup quite recently (~2 month) -- please,
>do not upset if something wrong. In my C++ practice I developed some
>ideas and tricks I'd like to share and discuss... So I decided to start
>with this:

You need to spend some time looking at what is already being worked on
by those responsible for the future of C++. Try

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/

But be warned that you have a lot of homework to do.


--
Francis Glassborow ACCU
Author of 'You Can Do It!' see http://www.spellen.org/youcandoit
For project ideas and contributions: http://www.spellen.org/youcandoit/projects

Victor Bazarov

unread,
Jan 4, 2005, 1:28:04 PM1/4/05
to
Michael Pryhodko wrote:
> Here is the list of features that comes to my mind whenever someone
> mention C++ 'desired features':
>
> 1. 'typeof' support -- quite obvious. Does anybody knows if it will be
> included in the next C++ standard?

See new suggested keyword 'decltype'.

> 2. fix 'forward declaration problem' for type aliases:
> // type alias declaration
> typedef foo<int> FooAlias;
> ...
> // forward declaration in another translation unit
> class FooAlias; // will not work :(
>
> Since type alias is intended for convenient way to reference real type
> it is a shame you can not use it in forward declarations.

Not sure about that.

> 3. 'template typedef' -- almost everybody want it :). Here is how it
> could be implemented:

I think n1489 is the document you want to read.

Visit and browse http://www.open-std.org/jtc1/sc22/wg21/

Victor

Michael Pryhodko

unread,
Jan 6, 2005, 10:32:14 AM1/6/05
to
>> Since type alias is intended for convenient way to reference real
type
>> it is a shame you can not use it in forward declarations.

> Not sure about that.

Why?


> I think n1489 is the document you want to read.

Thanks -- it was helpful. But:
1. I think that:
templatedef<class T> foo<T, U> t_alias;
looks more 'natural' than
template<class T> using t_alias = foo<T, U>;
because it more closely reflects 'typedef' syntax with respect to
templates (IMHO).

2. I disagree that (partial) template specialization using t-aliases
should be banned:
a) it looks natural to me
b) since t-alias is just substitution to more complex t-name it should
not create any problem in any existing template specialization logic
c) I was not convinced by the reasons described in n1489 :))

3. well... that paper introduces extensions to 'using' keyword more
than introducing 'template typedef' :)

Bye.
Sincerely yours, Michael.

Victor Bazarov

unread,
Jan 6, 2005, 2:09:42 PM1/6/05
to
Michael Pryhodko wrote:
>>>Since type alias is intended for convenient way to reference real
>
> type
>
>>>it is a shame you can not use it in forward declarations.
>
>
>>Not sure about that.
>
>
> Why?

Because I don't follow the upcoming changes to the language closely.

I still think you'll find that comp.std.c++ is a better place to discuss
that stuff. And do follow other's advice and study the proposals (and
see the discussions involving them in the archives of comp.std.c++).

V

ka...@gabi-soft.fr

unread,
Jan 11, 2005, 3:54:27 PM1/11/05
to
Michael Pryhodko wrote:

> I started reading this newsgroup quite recently (~2 month) --
> please, do not upset if something wrong. In my C++ practice I
> developed some ideas and tricks I'd like to share and
> discuss... So I decided to start with this:

> Here is the list of features that comes to my mind whenever
> someone mention C++ 'desired features':

I think that many of your features are already under discussion.
But you don't mention the feature that most desire: a compiler
which actually implements the standard. As long as compiler
vendors don't even give us what the current standard requires,
it seems futile to ask for more.

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Michael Pryhodko

unread,
Jan 12, 2005, 3:54:39 PM1/12/05
to
> I think that many of your features are already under discussion. But
> you don't mention the feature that most desire: a compiler which
> actually implements the standard.

:) well, VC7.1 so is ok for me in terms of "implementing standard". I
do not need features it does not implement (at least yet :) ). But
things I mentioned is what I REALLY need in my everyday practice -- and
they are missing from standard!


> As long as compiler vendors don't even give us what the current
> standard requires, it seems futile to ask for more.

If we stop demanding from compiler vendor -- sometimes they will
implement averything and C++ evolution will stop. It is just like
multi-stage pipeline with demands on input and compiler on output.
Demand needs to move some "turns" in pipeline, until it ends up as a
feature in the compiler. Stopping providing demands will end up in some
"turns" without any compiler evolution. :))
Bye.
Sincerely yours, Michael.

Alberto Barbati

unread,
Jan 12, 2005, 4:08:42 PM1/12/05
to
Michael Pryhodko wrote:
>
>>I think n1489 is the document you want to read.
>
>
> Thanks -- it was helpful. But:
> 1. I think that:
> templatedef<class T> foo<T, U> t_alias;
> looks more 'natural' than
> template<class T> using t_alias = foo<T, U>;
> because it more closely reflects 'typedef' syntax with respect to
> templates (IMHO).
>
> 2. I disagree that (partial) template specialization using t-aliases
> should be banned:
> a) it looks natural to me
> b) since t-alias is just substitution to more complex t-name it should
> not create any problem in any existing template specialization logic
> c) I was not convinced by the reasons described in n1489 :))
>
> 3. well... that paper introduces extensions to 'using' keyword more
> than introducing 'template typedef' :)
>

All these objections have been made and discussed several times on
comp.std.c++. I suggest you have a good read there.

What you fail to understand (I agree that is not too clear from n1489,
you may also want to read the previous proposal n1449) is that template
aliases and template typedefs are two completely different concepts. A
template is a parametrized family of types, right? Well,
over-simplifying: a template alias is *one* parametrized alias that
refers to a template, while a template typedef is a parametrized
*family* of aliases that refer to types. That explains a lot of things,
in particular:
1) the different choice of keywords ("using" vs. "typedef")
2) why specialization for template aliases is not allowed (you can
specialize a family of things, but you can't specialize one thing)
3) why template aliases works well in deduced contexts while template
typedefs have problems there

Number 3 is the key strenght of the template alias proposal, IMHO. My
impression (I'm not a compiler maker, so I'm just guessing) is that
number 2 makes template aliases much easier to implement than template
typedefs. That's why the C++ community seems more inclined to consider
the template alias proposal.

About which of the two concept "looks more natural", you'll agree that
it's a matter of opinion... ;-)

Alberto

Michael Pryhodko

unread,
Jan 13, 2005, 7:11:31 AM1/13/05
to
> What you fail to understand (I agree that is not too clear from
> n1489, you may also want to read the previous proposal n1449) is that
> template aliases and template typedefs are two completely different
> concepts.

No, I understand that. Unfortunately since I posted 'dried' version of
my thoughts and missed very important entry:
. with proposed templatedef you cannot do like this:
. templatedef<T> foo<T, smth<T> >* const& SmthMixedAndStrange;

My reasong where to 'mirror' 'typedef' behavior to templates world. So
speaking your language I want 'template alias'. I am somewhat surprised
to your reaction since I alway thought that 'typedef' is just a way to
declare 'type alias'. Is there any hidden features in 'typedef' that
makes impossible to mirror its functionality to templates world by
replacing word 'type' with 'template'?


> That explains a lot of things, in particular:
> 1) the different choice of keywords ("using" vs. "typedef")

I will support it with only if you ban 'typedef' keyword (obviously not
good naming) and introduce generic type/template aliasing (for example
like proposed in n1489). But I doubt that because of backward
compatibility. So since 'typedef' will stay, I propose to use similar
looking syntax for template aliases, i.e. 'templatedef' (that means
introducing new reserved keyword, however :( ) instead of introducing
new syntax features in C++.

> 2) why specialization for template aliases is not allowed (you can
> specialize a family of things, but you can't specialize one thing)

Agreed, however that does not forbid *template specialization using
template aliases*. Hmm... I hope you understand me, it looks like
playing with words. I'll try with example:

template<class T, class U> class foo;
templatedef<class T> foo<T, T*> t_name;

template<class T> t_name<T*> { ... };
should be equivalent to:
template<class T> class foo<T*, T**> { ... };

I do not see any problems here, usual rules of template specializations
apply here.


> 3) why template aliases works well in deduced contexts while template
> typedefs have problems there

> Number 3 is the key strenght of the template alias proposal, IMHO. My
> impression (I'm not a compiler maker, so I'm just guessing) is that
> number 2 makes template aliases much easier to implement than
> template typedefs. That's why the C++ community seems more inclined
> to consider the template alias proposal.

Agreed with every word.


> About which of the two concept "looks more natural", you'll agree
> that it's a matter of opinion... ;-)

Certainly! Every word I say (until I am quoting somebody) is my IMHO.
Bye.
Sincerely yours, Michael.

jd

unread,
Jan 13, 2005, 7:18:48 AM1/13/05
to
Le Tue, 04 Jan 2005 04:35:28 -0500, Michael Pryhodko a écrit :

> Hello
>
> I started reading this newsgroup quite recently (~2 month) -- please,
> do not upset if something wrong. In my C++ practice I developed some
> ideas and tricks I'd like to share and discuss... So I decided to start
> with this:
>
> Here is the list of features that comes to my mind whenever someone
> mention C++ 'desired features':

Sorry to came so late on this discussion.

>
> 1. 'typeof' support -- quite obvious. Does anybody knows if it will be
> included in the next C++ standard?

I think C++ should have a similar functionality. But I don't think it
will be easy. Where do you use typeof ?
Just imagine that:

template <class T>
T*
MakeT()
{
return new T;
}

// will the using code looks like:
typeof MakeT<SomeType>& var = *MakeT<SomeType>() ?

It looks quiete rubbish for me. However, I might mis-know how you would
like to realize it and how you think it will be useable. I need more
information about that in order to understand better.

>
> 2. fix 'forward declaration problem' for type aliases:
> // type alias declaration
> typedef foo<int> FooAlias;
> ...
> // forward declaration in another translation unit
> class FooAlias; // will not work :(
>
> Since type alias is intended for convenient way to reference real type
> it is a shame you can not use it in forward declarations.

I, sometimes, have the bad tendency to mis-understand (I'm french), but I
think this is not a problem. If you really want to do that, just use
different name scopes.

Does for you foo<int> can be a new class FooAlias ?

well:

typedef int Int;
class Int
{...};

Huh ? What the compiler is expecting to think about such a code ? What is
the type of Int: int or Int ?

To my logic, this is simply a bad idea.

>
> 3. 'template typedef' -- almost everybody want it :). Here is how it
> could be implemented:
>
> // some templates
> template<class T> class type {};
> template<class T, class U> class ptr {};
>
> // convenient shortcut
> templatedef<class T, class U> ptr< T, type<U> > TmplAlias;

Should we be able to do such code ?

TmpAlias<int,long> tmpalias_instance;

to produce this concrete type : ptr<int, type<long> ?

C++ already offers such possibilities directly threw templates,
but as you mentioned it creates new types.

ka...@gabi-soft.fr

unread,
Jan 13, 2005, 5:00:01 PM1/13/05
to
Michael Pryhodko wrote:
> > I think that many of your features are already under
> > discussion. But you don't mention the feature that most
> > desire: a compiler which actually implements the standard.

> :) well, VC7.1 so is ok for me in terms of "implementing
> standard".

It's missing at least one major feature, export. Possibly
others, like template templates -- I don't know exactly.

> I do not need features it does not implement (at least yet :)
> ).

Maybe, but that's not the point, the point is...

> But things I mentioned is what I REALLY need in my everyday
> practice -- and they are missing from standard!

What does getting them into the standard buy you, if the
compiler vendors don't bother implementing what's in the
standard ? (That's what I meant by "futile" in the following
sentence.)

> > As long as compiler vendors don't even give us what the
> > current standard requires, it seems futile to ask for more.

> If we stop demanding from compiler vendor -- sometimes they
> will implement averything and C++ evolution will stop. It is
> just like multi-stage pipeline with demands on input and
> compiler on output. Demand needs to move some "turns" in
> pipeline, until it ends up as a feature in the
> compiler. Stopping providing demands will end up in some
> "turns" without any compiler evolution. :))

Except maybe for improved optimization and less errors:-). Time
compiler implementers spend in implementing new features is time
they don't spend in improving quality.

But that really wasn't my point -- there is an equilibrium which
has to be found, and we should be able to get both quality and
at least some new features. My point was rather that getting
something adopted in the standard doesn't seem to be a sure
fired way of getting compilers which implement it. Unless
something can be done to "push" compiler vendors to full
conformance (and not just implementing the features they feel
like), adding features to the standard is just adding words to a
paper that no one adhers to.

I'm actually in favor of a number of the proposed features
(garbage collection, threads, "lambda", dynamic linking...).
But unless compiler vendors actually implement them, putting
them into the standard doesn't buy me anything. And the respect
the current standard has gotten doesn't bide well for the
future.

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Michael Pryhodko

unread,
Jan 13, 2005, 6:47:35 PM1/13/05
to
> Where do you use typeof ?

Mostly in generic code. There was a big discussions about this feature
all the time, I believe. This feature is VERY handy here.


> typedef int Int;
> class Int {...};
>
> Huh ? What the compiler is expecting to think about such a code ?
> What is the type of Int: int or Int ?

Hmm... I do not completely understand your idea. But I'll try to giuess
and answer:
I want to declare incomplete type using name of any his 'alias' (i.e.
name created by typedef). It seems rather convent to me and should not
put any unbearable burden on compiler vendor. (on the other hand I may
be missing something)


> Should we be able to do such code ?
>
> TmpAlias<int,long> tmpalias_instance;
> to produce this concrete type : ptr<int, type<long> ?

Right.


> C++ already offers such possibilities directly threw templates, but
> as you mentioned it creates new types.

Hmm... let me demonstrate:

template<template<class T> > class Cont;
template<class T, class U> class Elem;

Could you pass 'Elem' as a template parameter to 'Cont' using today's
C++? You can not, you need to do rather ugly 'rebinding', wich is pain
in the neck when things bump into 'undeduced context' problem. With
'templatedef' it is easy:

templatedef<class T> Elem<T, T> t_alias;
Cont<t_alias> v;

Here is another example:
template<template<class T, class U> > class Cont;
template<class T> class Elem;

you need to increase template parameters count:
templatedef<class T, class U> Elem<T> t_alias;
Cont<t_alias> v;


Bye.
Sincerely oyurs, Michael.

Michael Pryhodko

unread,
Jan 13, 2005, 6:53:46 PM1/13/05
to
> I'm actually in favor of a number of the proposed features (garbage
collection, threads, "lambda", dynamic linking...). But unless compiler
vendors actually implement them, putting them into the standard doesn't
buy me anything. And the respect the current standard has gotten
doesn't bide well for the future.

1. About features -- I'd like to express my opinion (in order to
influence ever-changing equilibrium). Everything I say here is my IMHO:

garbage collection -- ultimate testament of average developer's
inability(disability :) ); pushing it into standard is just a sign that
people gave up idea to teach 'average developer' to develop. It could
be fine addition to standard library, but changing language... I am
really against it. What is really annoying me that GC does not solve
ANYTHING -- memory is just only one of the resources used by
application.

threads -- only as library extension. C++ was designed from the ground
ignoring this issue. And since there is no common model for
multithreading -- it is very dangerous to enforce any to underlying
platform.

lambda -- quite interesting idea, but not without flaws.

2. Pushing anything into standard DO buy smth for you: without it
compiler vendors will not implement that feature for sure.
We are going 'off topic'...

Bye.
Sincerely yours, Michael.

ka...@gabi-soft.fr

unread,
Jan 14, 2005, 11:12:13 PM1/14/05
to
Michael Pryhodko wrote:
> > I'm actually in favor of a number of the proposed features
> > (garbage collection, threads, "lambda", dynamic
> > linking...). But unless compiler vendors actually implement
> > them, putting them into the standard doesn't buy me
> > anything. And the respect the current standard has gotten
> > doesn't bide well for the future.

> 1. About features -- I'd like to express my opinion (in order
> to influence ever-changing equilibrium). Everything I say here
> is my IMHO:

> garbage collection -- ultimate testament of average
> developer's inability(disability :) ); pushing it into
> standard is just a sign that people gave up idea to teach
> 'average developer' to develop. It could be fine addition to
> standard library, but changing language... I am really against
> it. What is really annoying me that GC does not solve ANYTHING
> -- memory is just only one of the resources used by
> application.

That's sort of true. In the same way that using a high-level
language instead of assembler is a testament of the average
programmers inability.

In fact, it's not really a question of ability or not -- IMHO,
if your development process is incapable of producing correct
code without garbage collection, then it will be incapable of
producing it with. The only difference is one of cost -- it's
usually cheaper if the machine or the system does it, rather
than the programmers, provided the machine or the system can do
it correctly (where correctly, of course, also means meeting any
response time constraints on the application). The time garbage
collection saves me is time available to improve other parts of
the program.

And of course, need a few small tweaks of the language for
garbage collection to be possible.

> threads -- only as library extension. C++ was designed from
> the ground ignoring this issue. And since there is no common
> model for multithreading -- it is very dangerous to enforce
> any to underlying platform.

Threading is simply impossible at the library level. The major
issues aren't library issues, but questions concerning memory
visibility and sequencing.

> lambda -- quite interesting idea, but not without flaws.

Nothings without flaws.

> 2. Pushing anything into standard DO buy smth for you: without
> it compiler vendors will not implement that feature for sure.
> We are going 'off topic'...

It depends. About half of the features I use in any given
program are not part of the C++ standard. The compiler vendors
provided them anyway.

My impression today is that the compiler vendors pretty much
pick and choose what they want to implement. All of them
implement a lot more than that standard. But very few of them
implement all of the standard.

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Herb Sutter

unread,
Jan 16, 2005, 6:20:48 AM1/16/05
to
On 14 Jan 2005 23:12:13 -0500, ka...@gabi-soft.fr wrote:

>Michael Pryhodko wrote:
>> garbage collection -- ultimate testament of average
>> developer's inability(disability :) ); pushing it into
>> standard is just a sign that people gave up idea to teach
>> 'average developer' to develop. It could be fine addition to
>> standard library, but changing language... I am really against
>> it. What is really annoying me that GC does not solve ANYTHING
>> -- memory is just only one of the resources used by
>> application.
>
>That's sort of true. In the same way that using a high-level
>language instead of assembler is a testament of the average
>programmers inability.

It's sort of true in that memory is just one resource, but it is a very
special resource. Specifically, GC is essential (or nearly so) for:

a) guaranteeing type safety and memory safety (because you have to
guarantee that the memory that held an object stays around for as long as
there are any pointers to it, even if the object has already been
destroyed); and

b) lock-free programming (see Andrei Alexandrescu's recent articles and
talks; you can do it yourself without GC but it's like working with knives
that are sharp on both edges and don't have handles).

It's true that many programmers misuse GC as a crutch. It's also true that
GC is technically compelling (or essential) as a fundamental building
block for a still-growing list of reasons.


>> threads -- only as library extension. C++ was designed from
>> the ground ignoring this issue. And since there is no common
>> model for multithreading -- it is very dangerous to enforce
>> any to underlying platform.
>
>Threading is simply impossible at the library level. The major
>issues aren't library issues, but questions concerning memory
>visibility and sequencing.

James' first part is exactly right; threading can't be done purely as a
library, period. (That includes pthreads.)

When it comes to language guarantees you need to have, the usual suspects
include guarantees about the memory model, (re)ordering of operations (by
the compiler's optimizer, the system cache manager, the processor core
itself, or any other level), atomicity, and semantics for shared objects.
I'd be inclined to also throw in a couple of guarantees about the
scheduler (e.g., FIFO unblocking, fairness) just to avoid some
pathological starvation cases and confusing execution orderings.

But in fact there's more: Not only does threading require basic language
support to be possible to do correctly at all, but we need also need
still-to-be-designed higher-level language abstractions for concurrency
that we don't yet have in any language on any platform. Lock-based
programming is our status quo and it ain't enough; as semaphores are to
assembler, locks are to C (they're just as low-level and probably more
dangerous), and we need an "OO" level of abstraction for concurrency.
There's work like Ada95 and Comega that begin raising the abstraction
level, but we need more. I'm writing further about this problem (but not
any solution) this weekend as one part of my March CUJ column. Deadlines,
love 'em and hate 'em...


>> lambda -- quite interesting idea, but not without flaws.
>
>Nothings without flaws.

Something that has surprised me is that the more completely separate
language design problems I work on, the more lambdas/closures turn out to
be a key language abstraction that adds substantial elegance and power.
Boost Lambda is a great approximation, but even if it were perfect (and
unfortunately it's not although it's very impressive) you really need this
to be in the language, not just to get reasonable diagnostics and
debugger/tool support, but because some features and semantics that turn
out to be desirable are impossible to achieve exactly the way you want
them to be if lambdas are a library-only solution. I'll have more to say
about this over the coming year or two; it's an interesting area.

Herb

---
Herb Sutter (www.gotw.ca) (www.pluralsight.com/blogs/hsutter)

Convener, ISO WG21 (C++ standards committee) (www.gotw.ca/iso)
Contributing editor, C/C++ Users Journal (www.gotw.ca/cuj)
Architect, Developer Division, Microsoft (www.gotw.ca/microsoft)

Michael Pryhodko

unread,
Jan 17, 2005, 4:05:55 AM1/17/05
to
> It's sort of true in that memory is just one resource, but it is a
> very special resource.

Only special thing about memory is the binding between object lifetime
and its memory. And this is a pain for containers, since they need to
handle memory separately to be reasonably efficient. And as a side
effect there is no 'move' semantic support in C++ (which is containers
pain also).


> Specifically, GC is essential (or nearly so) for:
>
> a) guaranteeing type safety and memory safety (because you have to
> guarantee that the memory that held an object stays around for as
> long as there are any pointers to it, even if the object has
> already been destroyed); and

why? I do not see any point for it. If object is already destroyed, who
needs that memory? (let's not touch case when container itself handles
memory) -- this sentence looks like misspoken to me.
And I do not see how GC guarantees type safety?


> b) lock-free programming (see Andrei Alexandrescu's recent articles
> and talks; you can do it yourself without GC but it's like working
> with knives that are sharp on both edges and don't have handles).

Hmm... didn't read it yet. Can't say anything. Looks impossible to me,
since we will always have a LOCK :) prefix (on x86). And what about
others achitectures where processors caches are not synchronized?


> It's true that many programmers misuse GC as a crutch. It's also
> true that GC is technically compelling (or essential) as a
> fundamental building block for a still-growing list of reasons.

I am not sure about its "fundamentality". Indeed it is handy for RAD,
but if you need RAD -- just use C# or Java, since they were designed
with this in mind. GC alone solves so little, that it is unworthy
(imho) to complicate language with it.


>> Threading is simply impossible at the library level. The major
>> issues aren't library issues, but questions concerning memory
>> visibility and sequencing.

> James' first part is exactly right; threading can't be done purely
> as a library, period. (That includes pthreads.)

I need to read proposal before arguing with you here.
Bye.
Sincerely yours, Michael.

Michael Pryhodko

unread,
Jan 17, 2005, 4:07:20 AM1/17/05
to
> That's sort of true. In the same way that using a high-level language
instead of assembler is a testament of the average programmers
inability.

No, it is not. There are too many reasons why this sentence is wrong.
Most obvious is platform independence. Basically every 'language level'
is an 'abstraction layer' for developer. GC in C++ is just a 'plug this
damn hole' thing.


> The only difference is one of cost -- it's usually cheaper if the
machine or the system does it, rather than the programmers, provided
the machine or the system can do it correctly (where correctly, of
course, also means meeting any response time constraints on the
application).

You must be joking :). Maybe it is cheaper in terms of dollars spent on
project? And I doubt it as well -- if your development group in order
to produce reliable enough product needs GC you will pay twice in later
product support and extension.
In my experience good approach (e.g. usage of smart pointers and local
objects) was always enough to handle leaks. And by the way -- C++
classes + stack unwinding was designed for the purpose of seamless
semi-automatic resource handling!


> And of course, need a few small tweaks of the language for garbage
collection to be possible.

Yeah-yeah.... And will make it more complicated. Thus making C++
developers a bit more expensive. :))


> Threading is simply impossible at the library level. The major issues
aren't library issues, but questions concerning memory visibility and
sequencing.

Hmm... I am on slippery ground here, since I never used MT on other
than x86 platforms. But:
1. It IS implemented for x86 (VC + Boost.Treads works for me along with
some guarantees provided by compiler)
2. Could you:
a) tell me about these issues or
b) direct me to corresponding resources about this problem (I will
read corresponding Alexandrescu's proposal, it is just a matter of
precious time :) )

Bye.
Sincerely yours, Michael.


Ioannis Vranos

unread,
Jan 18, 2005, 6:00:47 PM1/18/05
to
Herb Sutter wrote:

> But in fact there's more: Not only does threading require basic language
> support to be possible to do correctly at all, but we need also need
> still-to-be-designed higher-level language abstractions for concurrency
> that we don't yet have in any language on any platform. Lock-based
> programming is our status quo and it ain't enough; as semaphores are to
> assembler, locks are to C (they're just as low-level and probably more
> dangerous), and we need an "OO" level of abstraction for concurrency.
> There's work like Ada95 and Comega that begin raising the abstraction
> level, but we need more. I'm writing further about this problem (but not
> any solution) this weekend as one part of my March CUJ column. Deadlines,
> love 'em and hate 'em...


I think what we really need for concurrency so as to take advantage of
multicore processors in straight-forward applications (that is our usual
applications that have no reason to have concurrent design), is a safe
language level support in the style of OpenMP (which as far as I know is
not safe in the sense that it is "hard-coded" and does not throw
exceptions in case of errors for example).


Perhaps the "safe part", should be additional compiler checks on such
multithreading declarations.


--
Ioannis Vranos

http://www23.brinkster.com/noicys

Tom Widmer

unread,
Jan 18, 2005, 6:01:53 PM1/18/05
to

Michael Pryhodko wrote:
> > What you fail to understand (I agree that is not too clear from
> > n1489, you may also want to read the previous proposal n1449) is
that
> > template aliases and template typedefs are two completely different
> > concepts.
>
> No, I understand that. Unfortunately since I posted 'dried' version
of
> my thoughts and missed very important entry:
> . with proposed templatedef you cannot do like this:
> . templatedef<T> foo<T, smth<T> >* const& SmthMixedAndStrange;
>
> My reasong where to 'mirror' 'typedef' behavior to templates world.
So
> speaking your language I want 'template alias'. I am somewhat
surprised
> to your reaction since I alway thought that 'typedef' is just a way
to
> declare 'type alias'. Is there any hidden features in 'typedef' that
> makes impossible to mirror its functionality to templates world by
> replacing word 'type' with 'template'?

template syntax wasn't all that well thought out originally IMHO.
template<class T>
class A;
doesn't declare a class at all, which is slightly problematic to
understanding the concept. Similarly, extending ordinary typedef syntax
with templates wouldn't be a typedef. e.g.
template <class T>
typedef std::vector<T> Vec;

Your suggested templatedef at least doesn't suffer from the same
problem as class templates, but it isn't uniform either. The extension
of the "using" keyword to replace both typedef and templatedef seems
like quite a nice idea to me, since it distances us slightly from the
horrible old C declaration syntax.

> > That explains a lot of things, in particular:
> > 1) the different choice of keywords ("using" vs. "typedef")
>
> I will support it with only if you ban 'typedef' keyword (obviously
not
> good naming) and introduce generic type/template aliasing (for
example
> like proposed in n1489). But I doubt that because of backward
> compatibility.

typedef can't be banned; ideally we want to maintain or even improve C
compatibility. However, it could be demoted (but not deprecated) to
being a compatibility syntax that shouldn't be used in new code.

So since 'typedef' will stay, I propose to use similar
> looking syntax for template aliases, i.e. 'templatedef' (that means
> introducing new reserved keyword, however :( ) instead of introducing
> new syntax features in C++.

If you want a similar looking syntax, what's wrong with:

template <class T>
typedef std::vector<T> Vec;
?

>
> > 2) why specialization for template aliases is not allowed (you can
> > specialize a family of things, but you can't specialize one thing)
>
> Agreed, however that does not forbid *template specialization using
> template aliases*. Hmm... I hope you understand me, it looks like
> playing with words. I'll try with example:
>
> template<class T, class U> class foo;
> templatedef<class T> foo<T, T*> t_name;
>
> template<class T> t_name<T*> { ... };
> should be equivalent to:
> template<class T> class foo<T*, T**> { ... };
>
> I do not see any problems here, usual rules of template
specializations
> apply here.

Is it necessary though? Seems like an unnecessary addition to me,
unless you can come up with a reasonable use case where you can't use
the original template directly? It seems like a recipe for duplicate
specializations to me...

The ability to have the "typedef" point to different classes for
different T's is definitely useful, but we can already do that with a
traits class combined with the original proposal.

A key feature for me is that the template aliases must behave exactly
like a template of the parameters in question. e.g.

template <class T>
using myvec = std::vector<T>;

template <template<class> TT>
class Tester;

Tester<std::vector> - illegal since vector has 2 args
Tester<myvec> - hopefully legal, since myvec has only 1 arg

I haven't seen this issue mentioned in the proposal, but currently
template templates are restrictive because of the above problem.

Tom

Ioannis Vranos

unread,
Jan 18, 2005, 6:08:29 PM1/18/05
to
Herb Sutter wrote:

> But in fact there's more: Not only does threading require basic language
> support to be possible to do correctly at all, but we need also need
> still-to-be-designed higher-level language abstractions for concurrency
> that we don't yet have in any language on any platform. Lock-based
> programming is our status quo and it ain't enough; as semaphores are to
> assembler, locks are to C (they're just as low-level and probably more
> dangerous), and we need an "OO" level of abstraction for concurrency.
> There's work like Ada95 and Comega that begin raising the abstraction
> level, but we need more. I'm writing further about this problem (but not
> any solution) this weekend as one part of my March CUJ column. Deadlines,
> love 'em and hate 'em...

I think what we really need for concurrency so as to take advantage of
multicore processors in straight-forward applications (that is our usual
applications that have no reason to have concurrent design), is a safe
language level support in the style of OpenMP (which as far as I know is
not safe in the sense that it is "hard-coded" and does not throw
exceptions in case of errors for example).


Perhaps the "safe part", should be additional compiler checks on such
multithreading declarations.


--
Ioannis Vranos

http://www23.brinkster.com/noicys

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

ka...@gabi-soft.fr

unread,
Jan 18, 2005, 6:05:01 PM1/18/05
to
Michael Pryhodko wrote:
> > That's sort of true. In the same way that using a high-level
> > language instead of assembler is a testament of the average
> > programmers inability.

> No, it is not. There are too many reasons why this sentence is
> wrong.

Such as...

> Most obvious is platform independence.

I don't write platform independant code as is. I know that my
code will run on a Sun Sparc, under Solaris. And only on a Sun
Sparc, under Solaris.

I still don't use assembler (although I know Sparc assembler).

> Basically every 'language level' is an 'abstraction layer' for
> developer. GC in C++ is just a 'plug this damn hole' thing.

GC is an abstraction.

> > The only difference is one of cost -- it's usually cheaper
> > if the machine or the system does it, rather than the
> > programmers, provided the machine or the system can do it
> > correctly (where correctly, of course, also means meeting
> > any response time constraints on the application).

> You must be joking :). Maybe it is cheaper in terms of dollars
> spent on project? And I doubt it as well -- if your
> development group in order to produce reliable enough product
> needs GC you will pay twice in later product support and
> extension.

Could you point out some studies which back this up, or is it
just a figment of your imagination. All of the studies I've
seen suggest that GC reduces cost. Including maintenance
costs. (Actually, especially maintenance costs. It's actually
pretty easy to manage memory correctly in a new design. It's a
lot more difficult to ensure that later modifications don't
break anything.)

> In my experience good approach (e.g. usage of smart pointers
> and local objects) was always enough to handle leaks. And by
> the way -- C++ classes + stack unwinding was designed for the
> purpose of seamless semi-automatic resource handling!

C++ stack unwinding was designed to unwind the stack. Resource
handling is but a minor aspect. And of course, as you
doubtlessly know, no one smart pointer works everywhere. As
resources go, memory is a bit particular, because it can involve
cyclic dependancies. (It's also a bit particular because it is
a basic part of the machine, and not something "external".)

> > And of course, need a few small tweaks of the language for
> garbage collection to be possible.

> Yeah-yeah.... And will make it more complicated. Thus making
> C++ developers a bit more expensive. :))

Again, could you give some evidence of what you are talking
about? How would the language tweeks necessary for garbage
collection make the language more complicated.

> > Threading is simply impossible at the library level. The
> > major issues aren't library issues, but questions concerning
> > memory visibility and sequencing.

> Hmm... I am on slippery ground here, since I never used MT on
> other than x86 platforms. But:

> 1. It IS implemented for x86 (VC + Boost.Treads works for me
> along with some guarantees provided by compiler)

Precisely. Along with some guarantees provided by the
compiler. I use pthreads regularly; Posix makes a number of
requirements concerning visibility for a compiler to be Posix
compliant.

> 2. Could you:
> a) tell me about these issues or
> b) direct me to corresponding resources about this problem (I
> will read corresponding Alexandrescu's proposal, it is just a
> matter of precious time :) )

The issues are fairly complicated, and not easily explained in a
single posting. There have been threads concerning them here in
the past, but I suppose that working paper N1680
(http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1680.pdf)
is as good a place to start as any.

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Michael Pryhodko

unread,
Jan 19, 2005, 6:57:52 PM1/19/05
to
> template syntax wasn't all that well thought out originally IMHO.
> template<class T>
> class A;
> doesn't declare a class at all, which is slightly problematic to
> understanding the concept.

Well, I think in another way:

template<class T> // declaration of parametrized 'entity' that after
// substitution of its parameters will
result in *type* wich will look like:
class A { ... };

It is evident (IMHO) that main purpose of templates is to create TYPES
(well... and functions :) ) This is one of the reasons why I do not
like this:

1. template<class T> typedef ...; // this will not create type after
instantiation

also:

2. 'templatedef' syntax is shorter
3. 'templatedef' does not reads 'declaration of parametrized 'entity'
.... '. It is a new keyword and nobody has any assumptions regarding
it. It clearly states 'this is declaration of template name alias'.
4. 'templatedef' synatx will make thing like that:
template<class T> typedef A<T, T*>* const& B;

to look ugly/unnatural:
templatedef<class T> A<T, T*>* const& B;
(IMHO).

5. something telling me that 'templatedef' is easier to implement for
compiler vendor.


> Similarly, extending ordinary typedef syntax with templates wouldn't
> be a typedef. e.g.
> template <class T>
> typedef std::vector<T> Vec;

It will be typedef after instantiation (according to my approach of
understanding 'template' keyword). And I do not like it since I need
template name alias useable as template template parameter.


> Your suggested templatedef at least doesn't suffer from the same
> problem as class templates, but it isn't uniform either.

You want me to critique my child? ;)


> The extension of the "using" keyword to replace both typedef and
> templatedef seems like quite a nice idea to me, since it distances
> us slightly from the horrible old C declaration syntax.

Agreed 100%. After reading n1489 second time I found that I really like
uniform approach for name aliases proposed here. My first intention
with 'templatedef' was to create something very close to typedef since
everyone accustomized to it already.


> If you want a similar looking syntax, what's wrong with:
> template <class T> typedef std::vector<T> Vec; ?

See point above.


About specializations using template name aliases. I find this logical.
I understand this could create problems, but it looks natural to me,
and should not provide any problems to compiler vendors. I call it
'futher template specialization' :)


Bye.
Sincerely yours, Michael.


Herb Sutter

unread,
Jan 20, 2005, 6:26:16 PM1/20/05
to
On 17 Jan 2005 04:07:20 -0500, "Michael Pryhodko"

<mpry...@westpac.com.au> wrote:
>> Threading is simply impossible at the library level. The major issues
>>aren't library issues, but questions concerning memory visibility and
>>sequencing.
>
>Hmm... I am on slippery ground here, since I never used MT on other
>than x86 platforms. But:
>1. It IS implemented for x86 (VC + Boost.Treads works for me along with
>some guarantees provided by compiler)
>2. Could you:
>a) tell me about these issues or
>b) direct me to corresponding resources about this problem (I will
>read corresponding Alexandrescu's proposal, it is just a matter of
>precious time :) )

Here is a readable overview (displaying Hans' usual excellent clarity that
makes his papers a joy to read):

Hans Boehm. "Threads Cannot Be Implemented as a Library"
http://www.hpl.hp.com/techreports/2004/HPL-2004-209.html

Herb

Convener, ISO WG21 (C++ standards committee) (www.gotw.ca/iso)
Contributing editor, C/C++ Users Journal (www.gotw.ca/cuj)
Architect, Developer Division, Microsoft (www.gotw.ca/microsoft)

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

Michael Pryhodko

unread,
Jan 21, 2005, 5:23:10 AM1/21/05
to
I studied current publications and papers regarding the subjects we
discuss. Because topics are big and somewhat complicated I decided to
answer with one post for each topic. This one is about GC in C++.

I do not like GC in C++, because of:

========================================
Reason #1: It does not help to manage resources properly

Memory is not only resource available to application. Suppose you have
a class that "wraps" HANDLE (i.e. handle to some underlying system
resource). When you "lose" object of this class it will take a
considerable time before GC reclaims object and calls corresponding
destruction function which will in turn release system resource.
a) Deterministic destruction of local GC objects will not help -- you
cannot put every such object to local storage.
b) Introducing "special" Dispose() function:
b1) ugly as it could be. C# clearly demonstrated this with its
"using" keyword and countless try/catch clauses introduced only to call
Dispose for local objects and propagate exception further
b2) defies the whole purpose of C++ destructors
b3) what will you do if you derive a class B (which also handles some
system resources) from this class? Call A::Dispose() from B::Dispose()?
This defies fundamental feature of C++, i.e. coupling subobject's
constructors/destructors into one atomic-looking function


========================================
Reason #2: Leads to unnecessary high demands from underlying system

It is clear that increasing system resources possibilities (e.g. number
of simultaneously open handles) could solve the problem produced by
delayed objects destruction. For example Windows2000 allows something
around ~15k handles which is much more than in NT4. But increasing
application size and complexity could increases time of GC session,
thus increasing "resource release" delay and thus increasing demands
for open handles (I am not sure if these demands will be linear of
application complexity).
This effect pushes industry in "expansive way", forcing users to do
upgrades (both software and hardware) more often that necessary. I do
not like it.
Also -- "resource" is not only "system resource". Resources could be:
a) transaction
b) some action that should be done later
etc.

It is easy to think about such "resource" which provided with "resource
release delaying" will introduce exponential demands. That means you
need to use 'Dispose()' and 'using' thus defying C++ elegancy.


========================================
Reason #3: No one likes 30Mb memory footprint in quite simple program

Clearly with the progress of GC technology it would be better and
better, but anyway GC has cost and cost is big!


========================================
Reason #4: RT applications

GC is inappropriate for 99% of real-time sensitive applications. Thus
introducing GC into C++ will divide library vendors into two camps --
those who use GC and cannot be used by RT application and those who
does not.


========================================
Reason #5: Changing language syntax is one way road

GC could be implemented in more than one way. Changing language syntax
and dictated behaviour to satisfy current GC design could prevent
future introduction of "better" GC (or not GC) memory handling
mechanism


========================================
Reason #6: Giving developer one more reason to be not careful

I will risk repeating myself -- memory is not the only resource! It is
somewhat unique in the way that object's lifetime and memory bound
together (usually). Considerable amount of bugs I found in C# code (not
mine) was connected to missing 'Dispose()' calls, just because
developer "relaxed" too much with GC under him. :)


========================================
Reason #7: Unordered destruction

Could be a problem. Leads to 'Dispose()' introduction.


========================================
Reason #8: Political reasons

I am risking myself to be hanged right here... Everything here is my
IMHO after all:
a) MS is pushing its GC (which is mostly complete) design into C++ ->
MS will be the first one to implement C++ compiler with GC feature
b) Like in case of IE company plans to catch majority of users with
"cool features" and I fear that history will repeat itself but this
time with C++
c) As a platform vendor MS never will pursue platform independency ->
with b) this could drive majority of development into Windows domain

Herb, I really appreciate your efforts you made for C++ -- do not take
"political reasons" personally, please. :)


********************************************
And one more thing -- preparing this posting I was searching through
google and found that I have "made my way" into Herb's weblog. I am
really impressed and somewhat flattened, but:

> Michael's third not-in-my-language feature was:


>> lambda -- quite interesting idea, but not without flaws.

This is wrong; the whole idea behind that posting was to express my
opinion about certain popular features which are being pushed into C++.
With this sentence I meant exactly what I said, i.e. interesting idea +
has some flaws. I am not against putting that feature into C++, but
fear that flaws could make it impossible technically.


********************************************
Now, answering your statements:

> Specifically, GC is essential (or nearly so) for:
> a) guaranteeing type safety and memory safety

not convincing. C++ provides model with good enough type and memory
safety -- I do not see how GC could improve it.


> b) lock-free programming (see Andrei Alexandrescu's recent articles
> and talks; you can do it yourself without GC but it's like working
> with knives that are sharp on both edges and don't have handles).

I read "Lock-Free Data Structures" article. I do not understand how GC
will help here? It will help only if "read pointer and register new
root in GC" is an atomic operation (Is it? If yes -- how?). In steps:
1. thread read value
2. thread is interrupted for very long time
3. artificial map changes its data pointer
4. GC finds no root and disposes of object
5. thread awakes and (hopefully) fails to register new root in GC

Where I am wrong?

About this problem in general --- could be solved with MROW lock
(multiple readers one writer). Easily implemented as lock-free on x86
as a spinlock with highest bit is 'for writer'. (LOCK bts/btr). Like
any spinlock suitable for limited number of participants 2^31 in this
case.


> It's also true that GC is technically compelling (or essential) as a
> fundamental building block for a still-growing list of reasons.

I disagree. Main reason behind GC is to automate half of memory
management, which itself is a result of:
a) Low general C++ developer professional level
b) C++ complexity
c) Absence of common resource management approach in most development
companies

It turned out that introduction of GC has some neat features, indeed:
a) Each object has thread-safe reference-counted semantic
b) Automatic resolution of cycle dependencies (which is not a problem
until you introduce ref-counting :)) )
c) Memory allocation/deallocation seems very fast (if you calculate:
sum(allocation_time) + sum(deallocation_time) +
sum(garbage_collection_time)

over long period of time, I am sure it will be more that

sum(allocation_time) + sum(deallocation_time)

for application without GC (especially if application uses advanced
allocation technique, e.g. lock-free allocation)
)
d) Automatically improving locality of references by compacting storage
(indeed a good feature wich could only be half-beaten by careful design
of corresponding GC-free application)
e) Reduced development costs (unfortunately yes, from my experience 99%
of software is written bad. At first user uses it because there is no
alternative, and after -- because he is sticked to its bugs and
migration is just too costly. I am myself currently supporting 14-years
system which was developed by people who knows very little about
multithreading and many other things -- it a real pain in the #@@ to
make it work, I am sure that company payed many times more for support
than for development -- simply just because someone long ago decided to
cut down development costs)

Anyway -- I can not call GC as "essential fundamental building block".
It will become this after introduction to C++, but I will not be happy.
:)


Bye.
Sincerely yours, Michael.


Michael Pryhodko

unread,
Jan 21, 2005, 5:21:06 AM1/21/05
to
>> Basically every 'language level' is an 'abstraction layer'
>> for developer. GC in C++ is just a 'plug this damn hole' thing.
>
>GC is an abstraction.

This arguing is pointless, lets stop it :). When I think about GC I
always remember main reason of its invention.


>> You must be joking :). Maybe it is cheaper in terms of dollars spent
>> on project? And I doubt it as well -- if your development group in
>> order to produce reliable enough product needs GC you will pay
>> twice in later product support and extension.
>
> Could you point out some studies which back this up, or is it just a
> figment of your imagination. All of the studies I've seen suggest
> that GC reduces cost. Including maintenance costs. (Actually,
> especially maintenance costs. It's actually pretty easy to manage
> memory correctly in a new design. It's a lot more difficult to
> ensure that later modifications don't break anything.)

No, it is figment of my imagination. However I will say some resons:
a) GC will make "participation in complex designs" is more easy for
inexperienced(or simply saying bad) developers
b) as result companies in general will start to hire less competent
developers
c) since programming are MUCH more than just memory handling this will
result in increasing later support and maintenance costs

I clearly understand that small group of highly experienced developers
will do very well without GC and even better with it, but overall
picture will become darker (imho, as usual). However GC leads to some
complications which may remove it from "good tool" list for those
highly experienced developers, especially giving that they should not
have any problems managing memory. :)
And by the way -- imho, it is easy to manage memory correctly at any
time provided that everyone involved has know that given project memory
management approach and follows it.


>> And by the way -- C++ classes + stack unwinding was designed for
>> the purpose of seamless semi-automatic resource handling!
>
> C++ stack unwinding was designed to unwind the stack. Resource
> handling is but a minor aspect.

Hmm... let me explain my point of view:
what is a class? Simply speaking it is a bitset value + associated with
it resources, which are handled by its member functions (such as
constructor and destructor). In these terms "classes + unwinding" is a
major aspect.


> And of course, as you doubtlessly know, no one smart pointer works
> everywhere. As resources go, memory is a bit particular, because it
> can involve cyclic dependancies.

Which is impossible to create if you do not use smart pointers :). I
agree that GC elegantly solves 'cyclic dependency'. However, I think
that this is a problem in application architecture and should be solved
on another level.


> (It's also a bit particular because it is a basic part of the
> machine, and not something "external".)

Well... I was thinking about some things for last 3 years, it seem I
need to make them available to public. I hope they will change your
opinion, in any case you'll get an opportunity to look on these issues
from my angle. :)


>>> And of course, need a few small tweaks of the language for garbage
>>> collection to be possible.
>> Yeah-yeah.... And will make it more complicated. Thus making C++
>> developers a bit more expensive. :))
>
> Again, could you give some evidence of what you are talking about?
> How would the language tweeks necessary for garbage collection make
> the language more complicated.

Yes, any feature introduction that touches already existing ones
complicate language. I call it complication multiplication :)). I.e. if
we have two features and want to introduce third wich intersects with
those two -- it will make things more complicated. For example:
introducing '%' as pointer to GC object will lead to a lot of
construction:
1. T%* -- is it ok?
2. T& r = *(T%) -- is it ok?

and so on. Basically adding another axis of freedom increases number of
possibilities which leads to further complication.

James, do not take my statements personally; please, understand that
since I am not native speaker I can accidentally create, well,
embarassing sentences. It is not intentional.
You could read my answer to Herb about GC and participate in discussion
if you find that topic interesting. Meanwhile I need to familiarize
myself with current MT stuff :)

Bye.
Sincerely yours, Michael.


Ioannis Vranos

unread,
Jan 22, 2005, 12:06:33 AM1/22/05
to
Michael Pryhodko wrote:

> I studied current publications and papers regarding the subjects we
> discuss. Because topics are big and somewhat complicated I decided to
> answer with one post for each topic. This one is about GC in C++.


Having C++/CLI in mind:


> I do not like GC in C++, because of:
>
> ========================================
> Reason #1: It does not help to manage resources properly
>
> Memory is not only resource available to application. Suppose you have
> a class that "wraps" HANDLE (i.e. handle to some underlying system
> resource). When you "lose" object of this class it will take a
> considerable time before GC reclaims object and calls corresponding
> destruction function which will in turn release system resource.
> a) Deterministic destruction of local GC objects will not help -- you
> cannot put every such object to local storage.


C++/CLI is about "stack semantics", the real object is still in the
managed heap. Dispose() is automatically compiler generated by the
destructor (destructor is Dispose).


> b) Introducing "special" Dispose() function:
> b1) ugly as it could be. C# clearly demonstrated this with its
> "using" keyword and countless try/catch clauses introduced only to call
> Dispose for local objects and propagate exception further
> b2) defies the whole purpose of C++ destructors
> b3) what will you do if you derive a class B (which also handles some
> system resources) from this class? Call A::Dispose() from B::Dispose()?


Yes implicitly. The compiler produces automatically the Dispose()
function, including chaining calls to Dispose().


> This defies fundamental feature of C++, i.e. coupling subobject's
> constructors/destructors into one atomic-looking function
>
>
> ========================================
> Reason #2: Leads to unnecessary high demands from underlying system
>
> It is clear that increasing system resources possibilities (e.g. number
> of simultaneously open handles) could solve the problem produced by
> delayed objects destruction. For example Windows2000 allows something
> around ~15k handles which is much more than in NT4. But increasing
> application size and complexity could increases time of GC session,
> thus increasing "resource release" delay and thus increasing demands
> for open handles (I am not sure if these demands will be linear of
> application complexity).


Not if one uses deterministic destruction, the recommended way in C++/CLI.


> This effect pushes industry in "expansive way", forcing users to do
> upgrades (both software and hardware) more often that necessary. I do
> not like it.
> Also -- "resource" is not only "system resource". Resources could be:
> a) transaction
> b) some action that should be done later
> etc.
>
> It is easy to think about such "resource" which provided with "resource
> release delaying" will introduce exponential demands. That means you
> need to use 'Dispose()' and 'using' thus defying C++ elegancy.
>
>
> ========================================
> Reason #3: No one likes 30Mb memory footprint in quite simple program
>
> Clearly with the progress of GC technology it would be better and
> better, but anyway GC has cost and cost is big!


Well in general, C++ provides the choice not using GC if you can not
afford it.

C++/CLI will also provide the ability to create objects of managed types
in the unmanaged heap, objects of unmanaged types in the managed heap,
derived managed types from unmanaged, derive unmanaged from managed etc
(although I am not sure I can understand all of these). As far as I know
these will be supported by the VC++ release after the 2005 release.


Under C++/CLI, C++ is essentially two worlds, the managed and the
unmanaged and the choice which one will use, is yours.


There are some other things that I have concerns about though,
preserving C++ semantics.


The default access of interface class is public and not private, default
inheritance of ref classes is public etc, and I have strong objections
on these non-C++ semantics.


--
Ioannis Vranos

http://www23.brinkster.com/noicys

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

ka...@gabi-soft.fr

unread,
Jan 22, 2005, 12:09:45 AM1/22/05
to
Michael Pryhodko wrote:
> >> Basically every 'language level' is an 'abstraction layer'
> >> for developer. GC in C++ is just a 'plug this damn hole'
> >> thing.

> >GC is an abstraction.

> This arguing is pointless, lets stop it :). When I think about
> GC I always remember main reason of its invention.

Which is? (I always thought that it was because Lisp required
it.)

> >> You must be joking :). Maybe it is cheaper in terms of
> >> dollars spent on project? And I doubt it as well -- if your
> >> development group in order to produce reliable enough
> >> product needs GC you will pay twice in later product
> >> support and extension.

> > Could you point out some studies which back this up, or is
> > it just a figment of your imagination. All of the studies
> > I've seen suggest that GC reduces cost. Including
> > maintenance costs. (Actually, especially maintenance
> > costs. It's actually pretty easy to manage memory correctly
> > in a new design. It's a lot more difficult to ensure that
> > later modifications don't break anything.)

> No, it is figment of my imagination. However I will say some
> resons:
> a) GC will make "participation in complex designs" is more
> easy for inexperienced(or simply saying bad) developers
> b) as result companies in general will start to hire less
> competent developers
> c) since programming are MUCH more than just memory handling
> this will result in increasing later support and maintenance
> costs

Well, Cobol sort of had that effect. It looked sort of like
English, so managers did suppose that just anyone could do it.
But from what I can see, that sort of thing doesn't seem to be
too much of a problem no adays, or if it is, it doesn't work at
the level we're considering. (At higher level language and tool
levels, it is still sometimes a consideration. But generally,
only when someone has a financial interest to make it so -- buy
my product, and you can reduce programmer cost.)

A company that wants to use incompetent people on a project
because they are cheaper will do so. GC or no. And in the end,
companies that don't know what they are doing will write bad
software, and companies that do know what they are doing will
write good software. At that level, GC doesn't enter into the
equation.

What it does mean is that the competent people you have can do
more work, and be involved in more areas of the program, which
should improve quality.

> I clearly understand that small group of highly experienced
> developers will do very well without GC and even better with
> it, but overall picture will become darker (imho, as usual).
> However GC leads to some complications which may remove it
> from "good tool" list for those highly experienced developers,
> especially giving that they should not have any problems
> managing memory. :)

It's not necessarily a problem, but it is work. If a machine
can do it better, well, it's not like there is nothing else to
take up my time.

> And by the way -- imho, it is easy to manage memory correctly
> at any time provided that everyone involved has know that
> given project memory management approach and follows it.

In theory, everything necessary for writing good software is
easy. Good documentation is easy. Good communication between
programmers is easy. Still...

But we're talking about the necessity of designing and writing
code, then documenting it and communicating the underlying
principles, against letting the machine take care of
everything. Easy work (maybe) vs. no work (at the programming
level -- GC does not mean that you can skip on design).

> >> And by the way -- C++ classes + stack unwinding was
> >> designed for the purpose of seamless semi-automatic
> >> resource handling!

> > C++ stack unwinding was designed to unwind the
> > stack. Resource handling is but a minor aspect.

> Hmm... let me explain my point of view: what is a class?
> Simply speaking it is a bitset value + associated with it
> resources, which are handled by its member functions (such as
> constructor and destructor). In these terms "classes +
> unwinding" is a major aspect.

The fact that C++ calls destructors of local variables when the
variables go out of scope is an important aspect of C++.
Independantly of resource handling.

> > And of course, as you doubtlessly know, no one smart pointer
> > works everywhere. As resources go, memory is a bit
> > particular, because it can involve cyclic dependancies.

> Which is impossible to create if you do not use smart pointers
> :).

I'm not sure that I understand what you are trying to say here.
Cycles in memory references are a fact of life -- they're even
the basis of some very basic structures, like a double linked
list. In practice, you can't get away from them.

> I agree that GC elegantly solves 'cyclic dependency'.
> However, I think that this is a problem in application
> architecture and should be solved on another level.

Sometimes, you need more than garbage collection provides.
Sometimes you don't. At the design level, at least at the high
level design level, I think that the influence of garbage
collection is minimal; it's a programming tool, not a design
tool.

> > (It's also a bit particular because it is a basic part of
> > the machine, and not something "external".)

> Well... I was thinking about some things for last 3 years, it
> seem I need to make them available to public. I hope they will
> change your opinion, in any case you'll get an opportunity to
> look on these issues from my angle. :)

> >>> And of course, need a few small tweaks of the language for
> >>> garbage collection to be possible. Yeah-yeah.... And will
> >>> make it more complicated. Thus making C++ developers a bit
> >>> more expensive. :))

> > Again, could you give some evidence of what you are talking
> > about? How would the language tweeks necessary for garbage
> > collection make the language more complicated.

> Yes, any feature introduction that touches already existing
> ones complicate language. I call it complication
> multiplication :)). I.e. if we have two features and want to
> introduce third wich intersects with those two -- it will make
> things more complicated. For example: introducing '%' as
> pointer to GC object will lead to a lot of construction:

> 1. T%* -- is it ok?
> 2. T& r = *(T%) -- is it ok?

I agree that garbage collection can be done wrong. I certainly
don't see whether something is garbage collected or not as part
of the type system. I'm very sceptical as to the need of
anything special beyond a different new operator (for the
allocation of garbage collected memory). In fact, I'm not even
sure that that would be necessary.

> and so on. Basically adding another axis of freedom increases
> number of possibilities which leads to further complication.

> James, do not take my statements personally;

I didn't. I do think that that your original statements were
expressed rather dogmatically, without much backing argument.

> please, understand that since I am not native speaker I can
> accidentally create, well, embarassing sentences. It is not
> intentional.

I recognize this. For that matter, it happens even with native
speakers.

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

ka...@gabi-soft.fr

unread,
Jan 22, 2005, 12:37:07 AM1/22/05
to
Michael Pryhodko wrote:
> I studied current publications and papers regarding the
> subjects we discuss. Because topics are big and somewhat
> complicated I decided to answer with one post for each
> topic. This one is about GC in C++.

> I do not like GC in C++, because of:

> ========================================
> Reason #1: It does not help to manage resources properly

> Memory is not only resource available to application.

So. And managing resources isn't the only thing necessary to do
right for a program to work. Garbage collection is a tool
designed to solve one very particular (but important) problem.
It solves that problem. It doesn't solve other problems.

I don't refuse to use a car because it won't get me from Paris
to New York.

> Suppose you have a class that "wraps" HANDLE (i.e. handle to
> some underlying system resource). When you "lose" object of
> this class it will take a considerable time before GC reclaims
> object and calls corresponding destruction function which will
> in turn release system resource.

Why do you want to loose an object of this class? If you loose
it without garbage collection, it is truely lost. If you losse
it with garbage collection, you can eventually signal the error
in the finalizer, if you decide to do so.

> a) Deterministic destruction of local GC objects will not help
> -- you cannot put every such object to local storage.

Local objects shouldn't be garbage collected.

> b) Introducing "special" Dispose() function:
> b1) ugly as it could be.

So call it clean-up. Or better still, call it ~ClassName. And
use a special syntax to call it, say delete ptr.

If you need deterministic clean-up, then waiting for garbage
collection to do it is obviously an error. If you need anything
deterministic, unless it follows scoping rules very rigorously,
you reall want some explicit indication of it being invoked.

I think you're looking at the problem backwards. The current
problem is that I am obliged to use deterministic cleanup even
when it is not the appropriate solution, because there is
nothing else.

> C# clearly demonstrated this with its "using" keyword and
> countless try/catch clauses introduced only to call Dispose
> for local objects and propagate exception further

> b2) defies the whole purpose of C++ destructors

I'm not too sure what you mean by "defies" in this context. The
purpose of C++ destructors is to provide deterministic cleanup.
Regretfully, we're also required to use them for memory
management, since we don't have any alternative.

> b3) what will you do if you derive a class B (which also
> handles some system resources) from this class? Call
> A::Dispose() from B::Dispose()? This defies fundamental
> feature of C++, i.e. coupling subobject's
> constructors/destructors into one atomic-looking function

Why do you want to change the way destructors work in C++?
There's nothing in the proposals I've seen for garbage
collection to even suggest that this should be done.

Adding garbage collection to C++ does not replace (and remove)
any present functionality. It offers an additional
functionality, for the cases where it is appropriate.

Have you read any of the actual proposals? Or the older ones?
Have you experimented with existing garbage collectors?

> ========================================
> Reason #2: Leads to unnecessary high demands from underlying
> system

> It is clear that increasing system resources possibilities
> (e.g. number of simultaneously open handles) could solve the
> problem produced by delayed objects destruction. For example
> Windows2000 allows something around ~15k handles which is much
> more than in NT4. But increasing application size and
> complexity could increases time of GC session, thus increasing
> "resource release" delay and thus increasing demands for open
> handles (I am not sure if these demands will be linear of
> application complexity).

What is the relationship between garbage collection and the
number of open file handles? I don't see one.

> This effect pushes industry in "expansive way", forcing users
> to do upgrades (both software and hardware) more often that
> necessary. I do not like it.
> Also -- "resource" is not only "system resource". Resources could be:
> a) transaction
> b) some action that should be done later
> etc.

> It is easy to think about such "resource" which provided with
> "resource release delaying" will introduce exponential
> demands. That means you need to use 'Dispose()' and 'using'
> thus defying C++ elegancy.

> ========================================
> Reason #3: No one likes 30Mb memory footprint in quite simple
> program

> Clearly with the progress of GC technology it would be better
> and better, but anyway GC has cost and cost is big!

This is probably the one real point you've made so far.
Typically, programs using garbage collection will have a bigger
memory footprint than those using manual memory management.
It's the classic time/space trade-off -- they run faster, but
require more space to do so.

> ========================================
> Reason #4: RT applications

> GC is inappropriate for 99% of real-time sensitive
> applications.

If you are talking about hard real time, all dynamic memory
allocation is inappropriate. If you are talking simply about
response times, garbage collection typically does better than
manual management.

There also exist real-time garbage collection implementations,
with guaranteed hard response times. (I don't know of anything
equivalent for malloc/free, but it would be possible. Using the
same techniques the real-time garbage collectors use.)

> Thus introducing GC into C++ will divide library vendors into
> two camps -- those who use GC and cannot be used by RT
> application and those who does not.

Again: if you're talking about hard real-time, most operating
systems don't support it. If you're talking about soft
real-time, garbage collection typically does better than
malloc/free.

> ========================================
> Reason #5: Changing language syntax is one way road

> GC could be implemented in more than one way. Changing
> language syntax and dictated behaviour to satisfy current GC
> design could prevent future introduction of "better" GC (or
> not GC) memory handling mechanism

The same thing could be said about any feature. Including
malloc/free.

> ========================================
> Reason #6: Giving developer one more reason to be not careful

> I will risk repeating myself -- memory is not the only
> resource! It is somewhat unique in the way that object's
> lifetime and memory bound together (usually). Considerable
> amount of bugs I found in C# code (not mine) was connected to
> missing 'Dispose()' calls, just because developer "relaxed"
> too much with GC under him. :)

C++ is one more reason not to be careful. Let's go back to
assembler.

> ========================================
> Reason #7: Unordered destruction

> Could be a problem. Leads to 'Dispose()' introduction.

I don't know where you got this idea that garbage collection has
any effect on destruction. Destruction works exactly like it
always has, garbage collection or not.

> ========================================
> Reason #8: Political reasons

> I am risking myself to be hanged right here... Everything here
> is my IMHO after all:

> a) MS is pushing its GC (which is mostly complete) design into
> C++ -> MS will be the first one to implement C++ compiler with
> GC feature

Actually, the first "push" came from Detlefs and Ellis
(http://www.hpl.hp.com/techreports/Compaq-DEC/SRC-RR-102.html).
Or maybe even before that. (That was 1993. Microsoft was still
a newcomer in the C++ arena. The copyright on the paper is
Digital Equipment and Xerox.)

> b) Like in case of IE company plans to catch majority of users
> with "cool features" and I fear that history will repeat
> itself but this time with C++

> c) As a platform vendor MS never will pursue platform
> independency -> with b) this could drive majority of
> development into Windows domain

> Herb, I really appreciate your efforts you made for C++ -- do
> not take "political reasons" personally, please. :)

Herb is actually a latecomer amongst the proponents of garbage
collection. The idea has been around for a long time.

[...]


> > It's also true that GC is technically compelling (or
> > essential) as a fundamental building block for a
> > still-growing list of reasons.

> I disagree. Main reason behind GC is to automate half of
> memory management, which itself is a result of:
> a) Low general C++ developer professional level

What's unprofessional about letting the machine do things it can
do better than you? I already use a compiler, rather than
compiling by hand.

(And why half: garbage collection automates all of the memory
management. You seem to be confounding memory management with a
lot of other unrelated issues, like the lifetime of objects.)

> b) C++ complexity

Or rather, application complexity. Which means that I have
enough things to worry about as is, without artificially adding
to the list.

> c) Absence of common resource management approach in most
> development companies

??? Don't understand this one.

> It turned out that introduction of GC has some neat features,
> indeed:
> a) Each object has thread-safe reference-counted semantic
> b) Automatic resolution of cycle dependencies (which is not a
> problem until you introduce ref-counting :)) )
> c) Memory allocation/deallocation seems very fast (if you
> calculate: sum(allocation_time) + sum(deallocation_time) +
> sum(garbage_collection_time)

> over long period of time, I am sure it will be more that

> sum(allocation_time) + sum(deallocation_time)

> for application without GC (especially if application uses
> advanced allocation technique, e.g. lock-free allocation)

Have you actually measured this? All of the benchmarks I've
seen contradict it.

> d) Automatically improving locality of references by
> compacting storage (indeed a good feature wich could only be
> half-beaten by careful design

A good feature which probably cannot be used in C++:-).

> of corresponding GC-free application)
> e) Reduced development costs (unfortunately yes, from my
> experience 99% of software is written bad. At first user uses
> it because there is no alternative, and after -- because he is
> sticked to its bugs and migration is just too costly.

> I am myself currently supporting 14-years system which was
> developed by people who knows very little about multithreading
> and many other things -- it a real pain in the #@@ to make it
> work, I am sure that company payed many times more for support
> than for development -- simply just because someone long ago
> decided to cut down development costs)

That's regretfully a frequent situation for most of us.
Technology advances, and that includes software engineering
technology. And applications written years ago don't use modern
technology; I'd sure complain about having to maintain code
using <generic.h>, rather than templates, but 15 years ago, it
was all we had. And of course, what was considered acceptable
has evolved as well; when I started in the profession, it was
frequent to not indent code at all, something that will get you
fired in just about any company today.

But what does all this have to do with garbage collection?
Unless to say that garbage collection is another of those
techniques.

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Herb Sutter

unread,
Jan 22, 2005, 6:32:42 AM1/22/05
to
On 21 Jan 2005 05:21:06 -0500, "Michael Pryhodko"

<mpry...@westpac.com.au> wrote:
>>> Basically every 'language level' is an 'abstraction layer'
>>> for developer. GC in C++ is just a 'plug this damn hole' thing.
>>
>>GC is an abstraction.
>
>This arguing is pointless, lets stop it :). When I think about GC I
>always remember main reason of its invention.

What do you see as the main reason for its invention, and approximately
what year and language do you have in mind?

Herb

Convener, ISO WG21 (C++ standards committee) (www.gotw.ca/iso)
Contributing editor, C/C++ Users Journal (www.gotw.ca/cuj)
Architect, Developer Division, Microsoft (www.gotw.ca/microsoft)

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

Herb Sutter

unread,
Jan 22, 2005, 6:32:20 AM1/22/05
to
On 21 Jan 2005 05:23:10 -0500, "Michael Pryhodko"
<mpry...@westpac.com.au> wrote:
[...]

I agree with some of those points. Others are generalizations that are
only true of some GC approaches/systems, and that aren't true (at least
not as strongly as written) for all modern forms of GC. The
overgeneralizations don't acknowledge the variations among GC strategies
and actual range of implementations now in use.


>Now, answering your statements:
>
>> Specifically, GC is essential (or nearly so) for:
>> a) guaranteeing type safety and memory safety
>
>not convincing. C++ provides model with good enough type and memory
>safety -- I do not see how GC could improve it.

Then you need to do more research. Type safety is pretty boolean -- you
either have it or you don't. A basic requirement of type safety is to able
to guarantee that every pointer to a T object actually points to a T
object. It's nonobvious how to completely accomplish that without some
form GC (note that ref counting is one of the three forms of GC).
Simplistically, if you don't null out all pointers to the object, you
can't do a normal delete that also deallocates the memory (that would
violate the type safety constraint, as well as memory safety because you'd
have pointers to freed memory); but for each object to track all the
pointers to it so it can null them out is prohibitively expensive. That's
the basic sketch; there are other alternatives that also aren't practical
(for example to do stuff like keep a hash table of deleted pointes and
always check that first on pointer derefs but that's expensive too).

In my opinion Java and .NET are actually not quite type-safe in this
regard, but they are gradually closing the holes before construction and
after Dispose/destruction. In Java 5 they're mostly closing the leading
edge window (getting a reference to an object before it's fully
constructed) but since those platforms conflate object lifetime with
memory lifetime and have no system-supported concept of a destructor (only
a Dispose pattern) they have safety holes because object lifetime is a
subset of memory lifetime. So before full construction or after Dispose
you have a block of raw memory, not an object that satisfies the
invariants of its type. That's a violation of type safety because you have
a T* that doesn't really point to a T -- it just points to a block of
memory that's the right size and that soon will, or recently used to, hold
a T, and that's not the same thing.


>> b) lock-free programming (see Andrei Alexandrescu's recent articles
>> and talks; you can do it yourself without GC but it's like working
>> with knives that are sharp on both edges and don't have handles).
>
>I read "Lock-Free Data Structures" article. I do not understand how GC
>will help here?

Did you read his Hazard Pointers article? It shows (some of) what you need
to deal with if you roll your own memory management without GC. And
remember that some of his code is pseudocode and has to be atomic; if you
try running just the code as it appears you end up with windows of
vulnerability.

The basic idea is that lock-free structures do object versioning, and you
need to throw away the old versions when they're no longer needed. But at
any given time there can actually be multiple versions of the object in
use by different threads, so you can't just throw it away immediately
(again the issue is knowing who might be using the object). If you don't
have some GC (ref counting or something else) you need to have other
mechanisms that will throw away the old versions.

>It will help only if "read pointer and register new
>root in GC" is an atomic operation (Is it? If yes -- how?). In steps:
>1. thread read value

Not quite... in lock-free, the first thing you do is:

0. Take a copy of the pointer to your state, and then always use that
pointer (not the actual pointer).

Then:

>1. thread read value
>2. thread is interrupted for very long time
>3. artificial map changes its data pointer
>4. GC finds no root and disposes of object
>5. thread awakes and (hopefully) fails to register new root in GC
>Where I am wrong?

By omitting #0, which shows that #4 isn't right -- the state is still
reachable because you took a copy of the pointer.

Try reading a little more about this and try working through a few
examples to get the flavor (try searching the ACM Portal for "lock-free"
and there are several papers about implementing queues and hash tables).

Herb

Convener, ISO WG21 (C++ standards committee) (www.gotw.ca/iso)
Contributing editor, C/C++ Users Journal (www.gotw.ca/cuj)
Architect, Developer Division, Microsoft (www.gotw.ca/microsoft)

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

Dave Harris

unread,
Jan 22, 2005, 1:45:54 PM1/22/05
to
hsu...@gotw.ca (Herb Sutter) wrote (abridged):

> A basic requirement of type safety is to able to guarantee that
> every pointer to a T object actually points to a T object. It's
> nonobvious how to completely accomplish that without some form GC

I agree in principle, but surely it is too late to add this kind of type
safety to C++ ? C++ allows pointers to stack-allocated objects. When the
stack is unwound, the pointer is left hanging. None of the GC proposals
are going to take away stack-allocated objects.

Explicit destructors are as bad. After "delete p;" p no longer points to a
valid object, whether or not the expression deallocates memory.

As you say, type safety is boolean. It's guaranteed by the language or it
isn't. In C++, it isn't. Adding GC won't change that. It's not a relevant
issue here.

-- Dave Harris, Nottingham, UK

davide...@yahoo.com

unread,
Jan 22, 2005, 9:42:34 PM1/22/05
to
First, let me claim that I am just an average C++ programmer. I
don't have enough knowledge in machine level to argue whether GC is
good or bad for C++. I'd like to argue it form a different angle:
philosophy. Why do modern great physicists and mathematician are also
great philosophers? Philosophy always guides them to create great
theories. One of wisdom by intellectual means is beauty, or in Bjarne
Stroustrup's word, Elegance. This belief also guides Bjarne Stroustrup
and E. F. Codd to create two greatest abstraction models in computer
science: class and relational database. What's this beauty to do
with GC? A lot!

As trained in physics field, we all know very well what abstraction
means. A good abstraction model hides a great deal of detail
information. C++ class provides such a great model. It allows class
writers to control how objects are created, where and when they are
created, and how they are destroyed. Now, put the GC into the picture.
The class writers will no longer to control these details to provide a
great abstraction model. In other word, GC will totally destroy class
abstraction model, or the beauty of C++ class will be gone with GC,
period.

C++ is not like Java or .Net which are based on virtual machine
concept. C++ is compiled to native machine instruction and has a
limited run time environment. This characteristics determines that C++
shouldn't emulate Java or .Net. As an average C++ programmer, I
never feel there is problem to manage memory. I have great pleasure to
write my own smart pointer and memory manager. GC not only is
unnecessary but also violates the design philosophy of C++: elegant and
efficient. I wonder why these C++ gurus want to add GC to C++
standard? Does it come from political reasons or they just cannot see
the big picture?

Michael Pryhodko

unread,
Jan 24, 2005, 6:25:17 AM1/24/05
to
>> not convincing. C++ provides model with good enough type and memory
>> safety -- I do not see how GC could improve it.
>
> Then you need to do more research. Type safety is pretty boolean --
> you either have it or you don't. A basic requirement of type safety
> is to able to guarantee that every pointer to a T object actually
> points to a T object.

Hmm... My 'type safety' is slightly different than yours. Yours can not
be accomplished in C++ -- you will need to forbid casting of pointers
to integral types. Also even if you will achieve such type safety, you
will still have big (and almost the same) problem:
it is true that any T* will point to valid T, but after 'Dispose()'
call object becomes unusable. So the main problem is pretty the same --
when to call 'delete' (Dispose) in your program. GC will only prevent
your application from crashing.


> It's nonobvious how to completely accomplish that without some form
> GC (note that ref counting is one of the three forms of GC).

Agreed in the described context. But given my note above do you REALLY
NEED to accomplish that? :)
Also -- you can replace my 'refcounting' words with more generic
'shared ownership' -- it does not matter here.


I doubt that type safety you described has a big practical aplication
-- mostly its just replaces 'when to call delete' problem with 'when to
call Dispose'.


>> I read "Lock-Free Data Structures" article. I do not understand how
>> GC will help here?
>
> Did you read his Hazard Pointers article? It shows (some of) what you
> need to deal with if you roll your own memory management without GC.
> And remember that some of his code is pseudocode and has to be atomic
> ; if you try running just the code as it appears you end up with
> windows of vulnerability.

No, I didn't read it yet. And certainly I understand these
complications, I am quite proficient with lock-free concept -- it is
passed more than 3 years since I created my first lock-free
single-linked list.


[skip]


>> It will help only if "read pointer and register new root in GC" is
>> an atomic operation (Is it? If yes -- how?). In steps:
>> 1. thread read value
>
> Not quite... in lock-free, the first thing you do is:
>
> 0. Take a copy of the pointer to your state, and then always use that
> pointer (not the actual pointer).

Sorry, I was too brief. "Read value" meant "read state pointer's value
into register". What I was trying to say is:
this example will work with GC only if statement:

void Lookup()
{
. T* pLocalState = this->m_pState;
. ...
}

could be implemented atomically lock-free. Which is not evident to me
since conceptually you need to do two things (x86):
a) load value 'this->m_pState' from memory to register
b) for given object register new 'root' in GC

So I asked -- how compiler implements this step without any locking?

The only thing I could guess is that compiler will always load such
values to the same register and GC during 'search for roots' process
will look into every thread's state for specific register value.
However this approach should slowdown application considerably.

Another thing that comes to my mind is that GC will only interrupt
program if it is "appropriate" using some kind of flag variable... Or
maybe setting up special flag used by every thread to stop when it is
ok; and only after every threads stops -- perform garbage collection.
This approach looks very wxpensive to me.


> Try reading a little more about this and try working through a few
> examples to get the flavor (try searching the ACM Portal for
> "lock-free" and there are several papers about implementing queues
> and hash tables).

Thanks, I read about lock-free implementation of queues, hash-tables
and memory allocators quite long ago (basically soon after those papers
publication). :)


Bye.
Sincerely yours, Michael.


Ioannis Vranos

unread,
Jan 24, 2005, 6:33:56 AM1/24/05
to
Ioannis Vranos wrote:

> I think what we really need for concurrency so as to take advantage of
> multicore processors in straight-forward applications (that is our usual
> applications that have no reason to have concurrent design), is a safe
> language level support in the style of OpenMP (which as far as I know is
> not safe in the sense that it is "hard-coded" and does not throw
> exceptions in case of errors for example).
>
>
> Perhaps the "safe part", should be additional compiler checks on such
> multithreading declarations.


Or perhaps the answer to the clock speed problem is what I came across
today:

http://slashdot.org/articles/05/01/23/0239207.shtml?tid=126&tid=1


Very interesting article.

Francis Glassborow

unread,
Jan 24, 2005, 10:35:02 AM1/24/05
to
In article <1106434748.7...@f14g2000cwb.googlegroups.com>,
davide...@yahoo.com writes

> C++ is not like Java or .Net which are based on virtual machine
> concept. C++ is compiled to native machine instruction and has a
> limited run time environment. This characteristics determines that C++
> shouldn't emulate Java or .Net. As an average C++ programmer, I
> never feel there is problem to manage memory. I have great pleasure to
> write my own smart pointer and memory manager. GC not only is
> unnecessary but also violates the design philosophy of C++: elegant and
> efficient. I wonder why these C++ gurus want to add GC to C++
> standard? Does it come from political reasons or they just cannot see
> the big picture?

Go back to Bjarne (who, by the way, did not invent the class concept)
and you will find that he never excluded GC from C++, all he argued was
that it should not be essential and compulsory. He has always argued in
favour of it being an optional facility. The development and experience
from Managed C++ to C++/CLI is taking that route and providing some
practical experience with solving problems caused by having GC as an
option.


--
Francis Glassborow ACCU
Author of 'You Can Do It!' see http://www.spellen.org/youcandoit
For project ideas and contributions:
http://www.spellen.org/youcandoit/projects

Michael Pryhodko

unread,
Jan 24, 2005, 3:11:10 PM1/24/05
to
> Having C++/CLI in mind:

>
> C++/CLI is about "stack semantics", the real object is still in the
> managed heap. Dispose() is automatically compiler generated by the
> destructor (destructor is Dispose).

Does not work for me. From C++'s point of view it does not matter where
local, temporary, static and dynamic objects are located. They all
could be allocated from the same heap... Main difference is how C++
environment will treat them (e.g. lifetime of local objects is bound by
declaration scope).
Since after call to Dispose object becomes basically a 'zombie' -- we
have the same problem: when we need to destroy(dispose) object? The
difference is that now you need to foresight access to disposed object
while in current C++ it is simply declared as undefined behavior (or
ill-formed?).


>> But increasing application size and complexity could increases time
>> of GC session, thus increasing "resource release" delay and thus
>> increasing demands for open handles (I am not sure if these demands
>> will be linear of application complexity).
>
> Not if one uses deterministic destruction, the recommended way in
C++/CLI.

As I understand you deterministic destruction applied to local objects
only (what about temporaries?). But this won't help with dynamically
allocated objects -- like I said you will need to call dispose sooner
or later. And to make thing more complicated object will stay after
this and will be considered valid (but unusable).


> Well in general, C++ provides the choice not using GC if you can not
> afford it.

[a lot of future C++ complications skipped :)]


> Under C++/CLI, C++ is essentially two worlds, the managed and the
> unmanaged and the choice which one will use, is yours.

I already stated this as one of the reasons I do not like GC in C++.
Bye.
Sincerely yours, Michael.

Herb Sutter

unread,
Jan 24, 2005, 5:40:40 PM1/24/05
to
On 22 Jan 2005 21:42:34 -0500, davide...@yahoo.com wrote:
>As trained in physics field, we all know very well what abstraction
>means. A good abstraction model hides a great deal of detail
>information. C++ class provides such a great model. It allows class
>writers to control how objects are created, where and when they are
>created, and how they are destroyed. Now, put the GC into the picture.
>The class writers will no longer to control these details to provide a
>great abstraction model. In other word, GC will totally destroy class
>abstraction model, or the beauty of C++ class will be gone with GC,
>period.

I think you're making the (very common) assumption that conflates GC
(reclaiming memory) and object lifetime/teardown (destruction). Consider:
How would you feel about a C++ memory manager that did all the usual
new/delete and malloc/free just as today, but under the covers when you
did a delete or a free it didn't really reclaim the memory immediately,
but did it lazily later in response to other memory demands? That
describes the effect of work like the Boehm collector, which when used in
that way is transparent to the program.

>C++ is not like Java or .Net which are based on virtual machine
>concept. C++ is compiled to native machine instruction and has a
>limited run time environment. This characteristics determines that

Of course, the virtual machine can be viewed as just another target
machine. C and C++ are very good at being able to target many processors
with the same source base.

>C++ shouldn't emulate Java or .Net.

Agreed. Anything added to C++ should be to better serve C++ developers,
not just because some other language has it (copying for the sake of
copying).

>As an average C++ programmer, I
>never feel there is problem to manage memory. I have great pleasure to
>write my own smart pointer and memory manager. GC not only is
>unnecessary but also violates the design philosophy of C++: elegant and
>efficient. I wonder why these C++ gurus want to add GC to C++
>standard? Does it come from political reasons or they just cannot see
>the big picture?

Well, in Bjarne's talks about C++0x evolution, you'll always find the
bullet "optional garbage collection." I hope Bjarne has the reputation of
being a credible C++ guru without a political axe to grind. :-)

More broadly, 15-20 years ago many people felt the same way about OO.
(Today, a significant fraction of C programmers still feel that way about
OO!) It can take time for a community that's used to a certain way of
doing things to appreciate the value of (apparently) new features and
strategies. When those features make sense they should be used -- I agree
it would be bad to adopt features just because other languages have them.
And not everyone may need them enough to find them compelling.

Your post represents a completely understandable and widely held view.
There was once a time, a decade ago, when I was in the "I don't need no
stinkin' garbage collection, isn't that for lazy programmers who need
languages with training wheels" mindset. I didn't get over it overnight,
but I now see much more real value in it for C++, and see why Bjarne
mentioned the value of optional GC for C++ as far back as D&E (over a
decade ago).

Herb

Convener, ISO WG21 (C++ standards committee) (www.gotw.ca/iso)
Contributing editor, C/C++ Users Journal (www.gotw.ca/cuj)
Architect, Developer Division, Microsoft (www.gotw.ca/microsoft)

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

ka...@gabi-soft.fr

unread,
Jan 24, 2005, 4:05:18 PM1/24/05
to
davide...@yahoo.com wrote:

> GC not only is
> unnecessary but also violates the design philosophy of C++:
> elegant and efficient. I wonder why these C++ gurus want to
> add GC to C++ standard?

Probably because they realize that garbage collection is elegant
and efficient. Any time you want really efficient memory
management, you end up implementing garbage collection anyway.

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Ioannis Vranos

unread,
Jan 24, 2005, 6:56:47 PM1/24/05
to
Michael Pryhodko wrote:

> Does not work for me. From C++'s point of view it does not matter where
> local, temporary, static and dynamic objects are located. They all
> could be allocated from the same heap... Main difference is how C++
> environment will treat them (e.g. lifetime of local objects is bound by
> declaration scope).
> Since after call to Dispose object becomes basically a 'zombie' -- we
> have the same problem: when we need to destroy(dispose) object? The
> difference is that now you need to foresight access to disposed object
> while in current C++ it is simply declared as undefined behavior (or
> ill-formed?).


Essentially the same after a call to delete in ISO C++. If you use the
object you get undefined behaviour.

In fact in C++/CLI you do not call Dispose, when in the stack, the
object is Disposed at the end of its scope, and when in the heap the
object is Disposed after a call to delete.

> As I understand you deterministic destruction applied to local objects
> only (what about temporaries?). But this won't help with dynamically
> allocated objects -- like I said you will need to call dispose sooner
> or later. And to make thing more complicated object will stay after
> this and will be considered valid (but unusable).


No, you do not call Dispose but delete. You can also call Dispose
explicitly, but it has the same effect with delete.


--
Ioannis Vranos

http://www23.brinkster.com/noicys

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

Ben Hutchings

unread,
Jan 24, 2005, 6:52:56 PM1/24/05
to
Michael Pryhodko wrote:
<snip>

>>> It will help only if "read pointer and register new root in GC" is
>>> an atomic operation (Is it? If yes -- how?). In steps:
>>> 1. thread read value
>>
>> Not quite... in lock-free, the first thing you do is:
>>
>> 0. Take a copy of the pointer to your state, and then always use that
>> pointer (not the actual pointer).
>
> Sorry, I was too brief. "Read value" meant "read state pointer's value
> into register". What I was trying to say is:
> this example will work with GC only if statement:
>
> void Lookup()
> {
> . T* pLocalState = this->m_pState;
> . ...
> }
>
> could be implemented atomically lock-free. Which is not evident to me
> since conceptually you need to do two things (x86):
> a) load value 'this->m_pState' from memory to register
> b) for given object register new 'root' in GC
>
> So I asked -- how compiler implements this step without any locking?

There is no need for (b).

> The only thing I could guess is that compiler will always load such
> values to the same register and GC during 'search for roots' process
> will look into every thread's state for specific register value.
> However this approach should slowdown application considerably.

<snip>

On the contrary, registering and deregistering roots regularly would
slow things down. Marking doesn't need to be done that often.

Normally all registers are roots, and either all appropriately-
aligned stack slots are roots or the program has range tables or
similar statically-generated information indicating which slots in
each stack frame may contain pointers.

--
Ben Hutchings
Having problems with C++ templates? Your questions may be answered by
<http://womble.decadentplace.org.uk/c++/template-faq.html>.

Michael Pryhodko

unread,
Jan 25, 2005, 5:47:51 AM1/25/05
to
>> The only thing I could guess is that compiler will always load such
>> values to the same register and GC during 'search for roots' process
>> will look into every thread's state for specific register value.
>> However this approach should slowdown application considerably.
>
> <snip>
>
> On the contrary, registering and deregistering roots regularly would
> slow things down. Marking doesn't need to be done that often.
>
> Normally all registers are roots, and either all appropriately-
> aligned stack slots are roots or the program has range tables or
> similar statically-generated information indicating which slots in
> each stack frame may contain pointers.

Hmm... Consequently for given GC implementation that means:
1. optimizer is limited in the ways it could use registers (for example
never load pointer into EAX since it is used for arithmetic operations
and GC could not at given time detect whether EAX holds pointer or not)
2. GC performance depends on number of threads in the system... and
size of their stacks! (since it needs to traverse every ?registered?
stack frame to find all roots in the same way as it is done when
exception thrown)
3. also there is a cost associated with registering and "upong entry
initialization" of stack tables used to hold pointers. Thus making
function calls more expensive.

Point 2. looks small in comparison with garbage collection itself but
anyway adds...

Also in case of compacting GC we have additional two levels of
indirection (first -- read address of the pointers table, second --
using index stored in pointer to access object itself). This adds in
cost especially considering modern processor's architecture with
predictions.
And I wonder how object could be moved if any thread started working
with it? And which costs are associated with mechanism that makes this
safe?
And about memory visibility in multi-processor case -- it should not be
a big problem on x86, but what about other architectures? what cost
could incur GC in order to make sure that its looking at "current"
thread state and stack?

Unfortunately GC introduces additional costs all over the generated
code. Maybe propose to move GC support to hardware level? ;)
Bye.
Sincerely yours, Michael.


Michael Pryhodko

unread,
Jan 25, 2005, 5:48:47 AM1/25/05
to
>> When I think about GC Ialways remember main reason of its invention.

>
> What do you see as the main reason for its invention, and
> approximately what year and language do you have in mind?

:) You've catched me here...
Indeed it seems that GC was invented in Lisp. However in my defense
here is the quote from
'http://www.hpl.hp.com/techreports/Compaq-DEC/SRC-RR-102.pdf':

[quote]
We propose adding safe, efficient garbage collection to C++,
eliminating the possibility of storage-management bugs and making the
design of complex, object-oriented systems much easier.
[/quote]

As you see from the beginning of the introduction GC to C++ one of the
main idea was 'to eliminate storage-management bugs'.

I was working with Lisp about year or so when at the very beginning of
my career and GC didn't left any traces in my memory.
Bye.
Sincerely yours, Michael.

ka...@gabi-soft.fr

unread,
Jan 25, 2005, 2:19:06 PM1/25/05
to
Ben Hutchings wrote:

> Normally all registers are roots, and either all
> appropriately- aligned stack slots are roots or the program
> has range tables or similar statically-generated information
> indicating which slots in each stack frame may contain
> pointers.

That's true for conservative collectors. If you want a copying
collector, you need to know exactly which registers contain
addresses, and which don't. In a mono-threaded environment,
this is rather simple: the compiler knows when the garbage
collector may be invoked, and can do whatever is necessary. In
a multithreaded environment, it is a lot more difficult; I've
got some papers somewhere which describe the technique, but
basically, when garbage collecting cuts in, it looks at where
each thread is, and advances it to a "safe" point, where it can
know which registers contain addresses. (Presumably -- I've no
real experience with this, so I'm just guessing -- ensuring that
there are enough "safe" points could have a negative impact on
optimization. But I imagine that the people developing this
technology are aware of the issues, and taking them into
account.)

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Sergey P. Derevyago

unread,
Jan 25, 2005, 2:30:19 PM1/25/05
to
ka...@gabi-soft.fr wrote:
> > GC not only is
> > unnecessary but also violates the design philosophy of C++:
> > elegant and efficient. I wonder why these C++ gurus want to
> > add GC to C++ standard?
>
> Probably because they realize that garbage collection is elegant
> and efficient. Any time you want really efficient memory
> management, you end up implementing garbage collection anyway.
>
IMHO it's not the case, i.e. GC isn't the panacea.

In particular, MMM can be thought as N steps with nearly constant small
run-time cost.
While GC is N-1 nearly free steps plus one which is really big and heavy.

In other words, the complexity doesn't disappear: GC just changes the
distribution of the complexity.
--
With all respect, Sergey. http://ders.angen.net/
mailto : ders at skeptik.net

David Abrahams

unread,
Jan 25, 2005, 3:11:16 PM1/25/05
to
"Michael Pryhodko" <mpry...@westpac.com.au> writes:

> I was working with Lisp about year or so when at the very beginning of
> my career and GC didn't left any traces in my memory.

Probably because it "just works." It raises the abstraction level of
the programming language to the point where you never have to consider
allocations and deallocations (though you do have to watch out for
inadvertently hanging onto data that you ought to let go of).

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

david eng

unread,
Jan 26, 2005, 4:53:01 AM1/26/05
to

"David Abrahams" <da...@boost-consulting.com> wrote in message
news:uzmyxo...@boost-consulting.com...

> "Michael Pryhodko" <mpry...@westpac.com.au> writes:
>
> > I was working with Lisp about year or so when at the very beginning of
> > my career and GC didn't left any traces in my memory.
>
> Probably because it "just works." It raises the abstraction level of
> the programming language to the point where you never have to consider
> allocations and deallocations (though you do have to watch out for
> inadvertently hanging onto data that you ought to let go of).

Looks like we are planning to raise the abstraction level of C++ to get rid
of deallocation by garbage collection. Then, we raise another abstraction
level of C++ to get rid of allocation by garbage delivery. Finally, we can
raise yet another abstraction level of C++ to get rid of programming by
garbage container. Sounds wonderful, isn't it? Abstraction has a different
meaning to different people. However, to really understand it and use it
properly is hard. It requires good judgment and education. That's why only
few people can become Bjarne or Dennis Ritchie likes, I guess.

David Bradley

unread,
Jan 26, 2005, 4:53:41 AM1/26/05
to
Sergey P. Derevyago wrote:
> IMHO it's not the case, i.e. GC isn't the panacea.

I'm still leary of the fact that GC allows developers to create overly
complex object relationships.

In addition I've been very unimpressed with Java and .Net when running
in an OS that is under memory pressure.

What is needed is a hybrid approach. Don't assume I should never manage
an object's lifetime. GC should be a safety net, not a straight jacket.
C# has "using" IIRC which allows you to essentially scope an object,
though I don't know the details of eactly what happens to the memory. In
the end what I want is something that allows me to take control and
manage things when I need to but watches my back if I forget. There's
just things that I know about my application that GC can't know.

I should also be able to take memory allocation out of the realm of
collector when I know that traversing that memory will be expensive and
that all things in the group will have the same lifetime.

David

Michael Pryhodko

unread,
Jan 26, 2005, 4:55:10 AM1/26/05
to
>> Memory is not only resource available to application.
>
> So. And managing resources isn't the only thing necessary to do
> right for a program to work. Garbage collection is a tool
> designed to solve one very particular (but important) problem.
> It solves that problem. It doesn't solve other problems.

Unfortunately it creates a lot of other problems.


>> Suppose you have a class that "wraps" HANDLE (i.e. handle to
>> some underlying system resource). When you "lose" object of
>> this class it will take a considerable time before GC reclaims
>> object and calls corresponding destruction function which will
>> in turn release system resource.
>
> Why do you want to loose an object of this class? If you loose
> it without garbage collection, it is truely lost. If you losse
> it with garbage collection, you can eventually signal the error
> in the finalizer, if you decide to do so.

Because "loosing" object -- is a natural way of working with objects in
GC environment. I.e. create, use and forget. Finalizer won't help -- it
will just stretch "enjoyment" over undefinite period of time,
especially in big projects with a lot of logic. I prefer to have
unified approach to resources handling -- whether it is memory or
HANDLE.


> Local objects shouldn't be garbage collected.

Do not see any problems here. And it is logical if "locally declared"
object in C# calls Dispose on exit from scope. (C# is not implemented
in this way though -- basically it does not have "local storage"
notion).


>> b) Introducing "special" Dispose() function:
>> b1) ugly as it could be.
> So call it clean-up. Or better still, call it ~ClassName. And
> use a special syntax to call it, say delete ptr.

Won't work... After Dispose() object is "valid" in terms of C++, after
~ClassName -- not. And poviding that GC will call destructors for
object which is not destroyed yet this yield to a number of problems:
1. it could lead to deadlock if destructor tries to do smth
multithreaded
2. GC-called dtor could access objects which was destroyed explicitly
(or by GC)


> If you need deterministic clean-up, then waiting for garbage
> collection to do it is obviously an error. If you need anything
> deterministic, unless it follows scoping rules very rigorously,
> you reall want some explicit indication of it being invoked.

Right, but mixing two approaches in one application will surely lead to
problems.


> I think you're looking at the problem backwards. The current
> problem is that I am obliged to use deterministic cleanup even
> when it is not the appropriate solution, because there is
> nothing else.

Well, you are not obliged to do 'deteministic cleanup' -- simply
language doesn't provide inherent support for alternative. However you
easily could create it -- but you'll need to "uglify" your application
with explicit calls to new mechanism.


>> b) Introducing "special" Dispose() function:

[skip]


>> b2) defies the whole purpose of C++ destructors
>
> I'm not too sure what you mean by "defies" in this context. The
> purpose of C++ destructors is to provide deterministic cleanup.
> Regretfully, we're also required to use them for memory
> management, since we don't have any alternative.

No, purpose of C++ destructors is to provide cleanup for any system
resource logically associated with object's bit value (and current
environment state, to be completely precise). This is uniform approach
for any type of resource and as I am trying to prove breaking this
uniformity leads to a lot of problems. "Deterministic" -- is just the
way of using C++ destructors by C++ machine.


>> b3) what will you do if you derive a class B (which also
>> handles some system resources) from this class? Call
>> A::Dispose() from B::Dispose()? This defies fundamental
>> feature of C++, i.e. coupling subobject's
>> constructors/destructors into one atomic-looking function
>
> Why do you want to change the way destructors work in C++?
> There's nothing in the proposals I've seen for garbage
> collection to even suggest that this should be done.

I do not want to change it. This speculation was about way proposed in
C#/.NET -- i.e. putting non-memory related cleanup into special
function, which itself looks like C++ destructor.


> Adding garbage collection to C++ does not replace (and remove)
> any present functionality. It offers an additional
> functionality, for the cases where it is appropriate.

It depends on approach you are having in mind.


> Have you read any of the actual proposals? Or the older ones?
> Have you experimented with existing garbage collectors?

Yes, yes and yes.


> What is the relationship between garbage collection and the
> number of open file handles? I don't see one.

Suppose you have a class that wraps HANDLE -- i.e. calls CloseHandle on
destruction. If you put burden of destroying this object on GC's
shoulders it will take considerable time until HANDLE will be returned
to system. And as application becomes bigger and bigger that delay will
become longer.


>> GC is inappropriate for 99% of real-time sensitive
>> applications.
>
> If you are talking about hard real time, all dynamic memory
> allocation is inappropriate. If you are talking simply about
> response times, garbage collection typically does better than
> manual management.

Hmmm... I was talking about unpredictiveness of GC session. For
example: system that every minute runs very expensive analysis of data
gathered for this minute (for example -- any signal-based system for
traders). You need to finish all calculations very fast without any
interruption, since every miliseconds counts here. GC will really spoil
the fun if it will interrupt execution with its expensive garbage
collection session.


> There also exist real-time garbage collection implementations,
> with guaranteed hard response times. (I don't know of anything
> equivalent for malloc/free, but it would be possible. Using the
> same techniques the real-time garbage collectors use.)

Agreed, but I think nobody will put specialized GC into C++ -- it would
be general-purpose GC.


> C++ is one more reason not to be careful. Let's go back to
> assembler.

No. C++ doing very many useful things automatically and efficiently.
For example -- generating code for class constructor. In assembler
these features could be implemented more efficient but just marginally.


>> Reason #7: Unordered destruction
>> Could be a problem. Leads to 'Dispose()' introduction.
>
> I don't know where you got this idea that garbage collection has
> any effect on destruction. Destruction works exactly like it
> always has, garbage collection or not.

If your GC will call object's destructor (if it was not called yet) --
it has effect. But since there are a lot of resources that should be
released right now without waiting for GC -- you need some kind of C#'s
Dispose().
Now about unordered destruction -- some objects needs to be destroyed
before others. GC could make a guess but it will fail if there is a
cycle dependency. That means you need to explicitly break this
dependency -- that leads to conclusion that GC does not help with ALL
cyclic dependencies :)


>> I disagree. Main reason behind GC is to automate half of
>> memory management, which itself is a result of:

Sorry should be something like this:
Main reason behind GC is to automate half of memory management, "need
to automate" is a result of: ...


>> c) Absence of common resource management approach in most
>> development companies
>
> ??? Don't understand this one.

Every time I come in new company I found that their project has no
common approach to resource handling (whether memory or handles or
anything else).


>> c) Memory allocation/deallocation seems very fast (if you
>> calculate: sum(allocation_time) + sum(deallocation_time) +
>> sum(garbage_collection_time)
>> over long period of time, I am sure it will be more that
>> sum(allocation_time) + sum(deallocation_time)
>> for application without GC (especially if application uses
>> advanced allocation technique, e.g. lock-free allocation)
>
> Have you actually measured this? All of the benchmarks I've
> seen contradict it.

Indeed general allocator functions performs slower that GC's. But:
1. using GC has run-time impact on overall application performance
2. GC session length is unpredictable and could be really huge
depending on number of allocated objects and complexity of links
between them.
3. http://www.cs.utah.edu/~wilson/compilers/papers/pldi04-michael.pdf
describes lock-free allocator with equivalent to GC
allocation/deallocation time. But without costly GC session and runtime
performance impact. Extra memory usage in this allocator scheme is not
bigger than GC's footprint.
4. I measured this in managed C++ and pure C++, also I used my own
allocator.

So I doubt that having GC in your application will give you significant
gains over long time providing that your app is complex (and big)
enough.


>> e) Reduced development costs
[skip]


> But what does all this have to do with garbage collection?

I told about it already... It will make language seemingly easier thus
creating more projects written by amateurs that should be restarted
every day to work. And unfortunately such solutions has advantage over
good software since majority of management has no necessary technical
level to make right decision -- theey will choose cheapest solution in
70% of cases.


Bye.
Sincerely yours, Michael.

david eng

unread,
Jan 26, 2005, 5:04:23 AM1/26/05
to
"Herb Sutter" <hsu...@gotw.ca> wrote in message
news:7daav0tl216o43nj2...@4ax.com...

> On 22 Jan 2005 21:42:34 -0500, davide...@yahoo.com wrote:
> >As trained in physics field, we all know very well what abstraction
> >means. A good abstraction model hides a great deal of detail
> >information. C++ class provides such a great model. It allows class
> >writers to control how objects are created, where and when they are
> >created, and how they are destroyed. Now, put the GC into the picture.
> >The class writers will no longer to control these details to provide a
> >great abstraction model. In other word, GC will totally destroy class
> >abstraction model, or the beauty of C++ class will be gone with GC,
> >period.
>
> I think you're making the (very common) assumption that conflates GC
> (reclaiming memory) and object lifetime/teardown (destruction). Consider:
> How would you feel about a C++ memory manager that did all the usual
> new/delete and malloc/free just as today, but under the covers when you
> did a delete or a free it didn't really reclaim the memory immediately,
> but did it lazily later in response to other memory demands? That
> describes the effect of work like the Boehm collector, which when used in
> that way is transparent to the program.

No! I was not conflation. I was talking about abstraction. For a C++
class, the abstraction model provides constructor and destructor. For a GC
C++ class, there will be no destructor, period. How machine to implement
this abstraction is none of business of abstraction model. I argue that GC
will damage C++ class abstraction model.


> >C++ is not like Java or .Net which are based on virtual machine
> >concept. C++ is compiled to native machine instruction and has a
> >limited run time environment. This characteristics determines that
>
> Of course, the virtual machine can be viewed as just another target
> machine. C and C++ are very good at being able to target many processors
> with the same source base.

Yes. However, virtual machine concept is not just GC based. There are none
GC based virtual machines. C++ work well with COM and CORBA. As a matt of
fact, most heavy dirty and mission critical server applications are none GC
based virtual machine (run-time environment), i.e., CORBA and COM
applications. Please give me an example of GC based virtual machine
applications in such production environment, if you can.


> >As an average C++ programmer, I
> >never feel there is problem to manage memory. I have great pleasure to
> >write my own smart pointer and memory manager. GC not only is
> >unnecessary but also violates the design philosophy of C++: elegant and
> >efficient. I wonder why these C++ gurus want to add GC to C++
> >standard? Does it come from political reasons or they just cannot see
> >the big picture?
>
> Well, in Bjarne's talks about C++0x evolution, you'll always find the
> bullet "optional garbage collection." I hope Bjarne has the reputation of
> being a credible C++ guru without a political axe to grind. :-)

Bjarne only talks about “optional garbage collection”. It feels like if you
don’t talk about GC it is certainly that you are wrong. I feel it is more
political than reality. From Bjarne’s heart, how much does he really want
GC? To me, Bjarne is the smartest people among language creators. He is
the only one among programmer gurus has philosophy mind. I do hope Bjarne
can offer his opinion regarding this topic. But I doubt that he can totally
avoid politics. Remember, Microsoft just hosted the annual C++ standard
committee meeting.

{As a moderator, I have some concern about that last sentence. That MS
has hosted several WG21 & J16 meetings is absolutely irrelevant to the
decisions made by those committees. -mod/fwg}

> More broadly, 15-20 years ago many people felt the same way about OO.
> (Today, a significant fraction of C programmers still feel that way about
> OO!) It can take time for a community that's used to a certain way of
> doing things to appreciate the value of (apparently) new features and
> strategies. When those features make sense they should be used -- I agree
> it would be bad to adopt features just because other languages have them.
> And not everyone may need them enough to find them compelling.

> Your post represents a completely understandable and widely held view.
> There was once a time, a decade ago, when I was in the "I don't need no
> stinkin' garbage collection, isn't that for lazy programmers who need
> languages with training wheels" mindset. I didn't get over it overnight,

I think you really confuse yourself in term of abstraction. OO and C
function are two different abstraction models. There is nothing like OO is
better than C function. C function borrows the function concept from
mathematics and OO borrows the real world object concept from physics.
However, the foundation of physics is mathematics. It is nonsense to say
that physics is better than mathematics. They are in a different
abstraction level. In the same token, OO is based on function. Without
function, where is object? 15-20 years ago, there aren’t a lot of
distributed or enterprise applications so function abstraction is more
adequate for applications. But today, with the internet, class abstraction
is more suitable for distributed applications. However, this does not mean
that OO is better than function. I don’t think kernel should use OO instead
of function. STL algorithms make a right choice to be coded in function
instead class. For this point, I disagree with Bjarne’s claim that C++ is
better than C. Sometimes, function abstraction is more suitable for a
defined problem while sometimes class abstraction is the best choice. This
relationship is like the relationship between physics and mathematics. We
use both abstraction model to solve really problem. But mathematics is the
foundation of physics. That's why every modern language claims it is
derived from C, I guess.

In this regard, I also don’t agree with most C++ gurus that people should
learn class instead of function first. We study calculus and
differentiation equation first before we study relative and quantum
theories. There is no other way around. To me, to be a better programmer,
people should learn C first, then C++. The first listen they should learn
is abstraction, then function abstraction, class abstraction, generic
abstraction (another great abstraction model but it is abused in the name of
"meta programming"). Unfortunately, there are few good books talking about
abstraction other than syntax. If they talk about abstraction, most of them
get the concept of abstraction wrong. It is not “take time for a community


that's used to a certain way of doing things to appreciate the value of

(apparently) new features and strategies.” It is that does the abstraction
make sense for a giving problem? Most C programmers are system programming,
such as kernel and device. The class abstraction doesn’t make sense to
them. That doesn’t mean they cannot appreciate OO. For GUI programmers,
class abstraction makes a lot of sense to them, but that doesn’t mean OO is
better then C.


> but I now see much more real value in it for C++, and see why Bjarne
> mentioned the value of optional GC for C++ as far back as D&E (over a
> decade ago).

It might have real value for C++ a decade ago, but I see little value for
C++ today. There are a quite few programming languages today than a decade
ago. Every language has its own characteristics. I don’t like the concept
that everything should be based on Java or .Net. By same token, I also don’
t think that everything should be based on C++. If you need GC, choose Java
or .Net. If you need heavy dirty and mission critical processes, choose
C++. The key here is cooperation. Every language should focus on its own
characteristics or abstraction model instead of full bloom features. We
really don’t need hegemony like Microsoft or Java. The real needs of C++
are a component model which makes Java, .Net, or other languages easy to
call C++ processes and a connectivity library which make C++ easy to access
data stores, rather then GC since GC is not suitable for heavy dirty and
mission critical processes.

Ben Hutchings

unread,
Jan 26, 2005, 5:12:04 AM1/26/05
to
Michael Pryhodko wrote:
>>> The only thing I could guess is that compiler will always load such
>>> values to the same register and GC during 'search for roots' process
>>> will look into every thread's state for specific register value.
>>> However this approach should slowdown application considerably.
>>
>> <snip>
>>
>> On the contrary, registering and deregistering roots regularly would
>> slow things down. Marking doesn't need to be done that often.
>>
>> Normally all registers are roots, and either all appropriately-
>> aligned stack slots are roots or the program has range tables or
>> similar statically-generated information indicating which slots in
>> each stack frame may contain pointers.
>
> Hmm... Consequently for given GC implementation that means:
> 1. optimizer is limited in the ways it could use registers (for example
> never load pointer into EAX since it is used for arithmetic operations
> and GC could not at given time detect whether EAX holds pointer or not)

No, it is not essential for a GC to know for sure whether a memory
location holds a pointer or not (this is called perfect GC).

> 2. GC performance depends on number of threads in the system... and
> size of their stacks! (since it needs to traverse every ?registered?
> stack frame to find all roots in the same way as it is done when
> exception thrown)

Yes, and on the size of the static data segment and the size of the
live heap (or at least the parts of those that contain pointers).

> 3. also there is a cost associated with registering and "upong entry
> initialization" of stack tables used to hold pointers. Thus making
> function calls more expensive.

No, it should be possible to use range tables, as for exceptions.
In any case it is not essential to have that information.

> Point 2. looks small in comparison with garbage collection itself but
> anyway adds...
>
> Also in case of compacting GC we have additional two levels of
> indirection (first -- read address of the pointers table, second --
> using index stored in pointer to access object itself). This adds in
> cost especially considering modern processor's architecture with
> predictions.
>
> And I wonder how object could be moved if any thread started working
> with it? And which costs are associated with mechanism that makes this
> safe?

I couldn't comment on this because I have not worked with a compacting
GC.

> And about memory visibility in multi-processor case -- it should not be
> a big problem on x86, but what about other architectures? what cost
> could incur GC in order to make sure that its looking at "current"
> thread state and stack?

It is necessary to suspend the threads before doing this. At least,
this is what Boehm's GC does. It's open source and fairly well
documented, so why not have a look at it rather than just
speculating: <http://www.hpl.hp.com/personal/Hans_Boehm/gc/>

> Unfortunately GC introduces additional costs all over the generated
> code.

The net additional time cost is generally low, and may sometimes be
negative. If one can successfully manage memory without introducing
leaks, then the additional memory cost of a GC may be substantial;
otherwise it will become negative if the program runs for long enough.

Boehm claims that "[t]he overall processor time used by the collector
for small objects allocation and collection is comparable to a good
malloc/free implementations. In a multi-threaded environment, it may
be substantially less, since there are no calls that acquire lock
acquisitions; thus the number of lock acquisitions may be effectively
halved." (But it does not perform so well with large objects.)

> Maybe propose to move GC support to hardware level? ;)

Hardware that maintained integer/pointer tag bits might help, but then
that might complicate the genration of code for pointer arithmetic,
and it would presumably be incompatible with existing architectures.

--
Ben Hutchings
Having problems with C++ templates? Your questions may be answered by
<http://womble.decadentplace.org.uk/c++/template-faq.html>.

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

Ben Hutchings

unread,
Jan 26, 2005, 5:12:25 AM1/26/05
to
Sergey P. Derevyago wrote:
> ka...@gabi-soft.fr wrote:
>> > GC not only is
>> > unnecessary but also violates the design philosophy of C++:
>> > elegant and efficient. I wonder why these C++ gurus want to
>> > add GC to C++ standard?
>>
>> Probably because they realize that garbage collection is elegant
>> and efficient. Any time you want really efficient memory
>> management, you end up implementing garbage collection anyway.
>>
> IMHO it's not the case, i.e. GC isn't the panacea.
>
> In particular, MMM can be thought as N steps with nearly
> constant small run-time cost.

Yet when you free a large set of linked objects, a lot of those steps
will be needed.

> While GC is N-1 nearly free steps plus one which is really big
> and heavy.

In a mark-and-sweep GC, most of the work of marking can be fairly
evenly spread out between allocations, and sweeping can be done
entirely on an as-needed basis. So there is not such a big and heavy
step.

> In other words, the complexity doesn't disappear: GC just
> changes the distribution of the complexity.

Not necessarily in the way that you think!

--
Ben Hutchings
Having problems with C++ templates? Your questions may be answered by
<http://womble.decadentplace.org.uk/c++/template-faq.html>.

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

Sergey P. Derevyago

unread,
Jan 26, 2005, 1:48:33 PM1/26/05
to
Ben Hutchings wrote:
> > In other words, the complexity doesn't disappear: GC just
> > changes the distribution of the complexity.
>
> Not necessarily in the way that you think!
>
Ben, I think about the _fundamental_ issue: the complexity doesn't disappear.

--
With all respect, Sergey. http://ders.angen.net/
mailto : ders at skeptik.net

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

Michael Pryhodko

unread,
Jan 26, 2005, 1:52:31 PM1/26/05
to
>> 1. optimizer is limited in the ways it could use registers (for
>> example never load pointer into EAX since it is used for arithmetic
>> operations and GC could not at given time detect whether EAX holds
>> pointer or not)
>
> No, it is not essential for a GC to know for sure whether a memory
> location holds a pointer or not (this is called perfect GC).

Lokks VERY strange to me.. could you prove it?


>> stack frame to find all roots in the same way as it is done when
>> exception thrown)
>
> Yes, and on the size of the static data segment and the size of the
> live heap (or at least the parts of those that contain pointers).

.. and the complexity of links between the objects (i.e. how often
object holds reference to another).

>> 3. also there is a cost associated with registering and "upong
>> entry initialization" of stack tables used to hold pointers. Thus
>> making function calls more expensive.
>
> No, it should be possible to use range tables, as for exceptions.
> In any case it is not essential to have that information.

Range tables in exceptions handling is VERY expensive, the only excuse
for them that exceptions should not normally happens. But GC session
will happen every N seconds, and tables for them could be bigger than
for exceprions. That adds to GC session expensiveness.


>> And I wonder how object could be moved if any thread started
>> working with it? And which costs are associated with mechanism that
>> makes this safe?
>
> I couldn't comment on this because I have not worked with a
compacting GC.

You never used C#/.NET? I can not believe it! :))


>> And about memory visibility in multi-processor case -- it should
>> not be a big problem on x86, but what about other architectures?
>> what cost could incur GC in order to make sure that its looking at
>> "current" thread state and stack?
>
> It is necessary to suspend the threads before doing this. At least,
> this is what Boehm's GC does. It's open source and fairly well
> documented, so why not have a look at it rather than just
> speculating: <http://www.hpl.hp.com/personal/Hans_Boehm/gc/>

My knowledge is small in this field and I am risking to say something
stupid, but: for example on 'Cray's suspending threads will not make
their memory visible to your thread.
Thanks for link, but it does not work for me (timeout).


> Boehm claims that "[t]he overall processor time used by the collector
> for small objects allocation and collection is comparable to a good
> malloc/free implementations. In a multi-threaded environment, it may
> be substantially less, since there are no calls that acquire lock
> acquisitions; thus the number of lock acquisitions may be effectively
> halved." (But it does not perform so well with large objects.)

Hmm... According to my knowledge there are plenty of new malloc/free
libraries that scales well on multi-processor machines. Unfortunately
old ones (with process-wide mutex) are having very deep roots... For
example just look at ATL code that is used instead of CRT whenever you
declare ATL_MIN_CRT -- it tries to divide heaps between threads in
order to avoid locking (with error unfortunately).


>> Maybe propose to move GC support to hardware level? ;)

[skip]

It was a joke... It could turn into truth in the future, however.
Bye.
Sincerely yours, Michael.

ka...@gabi-soft.fr

unread,
Jan 26, 2005, 2:13:12 PM1/26/05
to
Michael Pryhodko wrote:
> >> Memory is not only resource available to application.

> > So. And managing resources isn't the only thing necessary to
> > do right for a program to work. Garbage collection is a tool
> > designed to solve one very particular (but important)
> > problem. It solves that problem. It doesn't solve other
> > problems.

> Unfortunately it creates a lot of other problems.

Such as? To date, I've not seen anyone mention any, based on
real experience or knowledge.

> >> Suppose you have a class that "wraps" HANDLE (i.e. handle
> >> to some underlying system resource). When you "lose" object
> >> of this class it will take a considerable time before GC
> >> reclaims object and calls corresponding destruction
> >> function which will in turn release system resource.

> > Why do you want to loose an object of this class? If you
> > loose it without garbage collection, it is truely lost. If
> > you losse it with garbage collection, you can eventually
> > signal the error in the finalizer, if you decide to do so.

> Because "loosing" object -- is a natural way of working with
> objects in GC environment. I.e. create, use and forget.

So. If that's the appropriate solution. Why do you think that
there is so much use of garbage collection already in C++?
Because that is the appropriate solution for many objects.

The only problem is that at present, the usual garbage
collection solution is purely a library solution, something like
boost::shared_ptr. So it is relatively inefficient compared to
real garbage collection, and to prevent it from being even more
inefficient, it punts on certain problems like circular
references.

(I might add that boost::shared_ptr can be used for other things
as well, when you pass it the appropriate finalizer. But it's
usual use today is as garbage collection.)

> Finalizer won't help -- it will just stretch "enjoyment" over
> undefinite period of time, especially in big projects with a
> lot of logic. I prefer to have unified approach to resources
> handling -- whether it is memory or HANDLE.

Quite. Let's get rid of the integral types as well, since a
unified approach to numbers is preferable, whether it is an
integer or a real number. Just use double for everything.

One size doesn't fit all, and manual memory management is about
like using double for strictly integral values.

> >> b) Introducing "special" Dispose() function:
> >> b1) ugly as it could be.
> > So call it clean-up. Or better still, call it ~ClassName. And
> > use a special syntax to call it, say delete ptr.

> Won't work... After Dispose() object is "valid" in terms of
> C++, after ~ClassName -- not.

If I need it to be valid afterwards, then delete ptr is not the
function I want. In C++. If I don't want it to be valid, then
delete ptr seems to be exactly what is needed.

> And poviding that GC will call destructors for object which is
> not destroyed yet this yield to a number of problems:

Who said that GC should call destructors? (I think that this is
still a much debated topic, but IMHO, garbage collection
shouldn't call destructors. It probably should have something
else that it will call, if nothing else than to detect error
conditions.)

> 1. it could lead to deadlock if destructor tries to do smth
> multithreaded

> 2. GC-called dtor could access objects which was destroyed
> explicitly (or by GC)

What does GC have to do with this? Even today, if you access
objects which have been destructed, you have undefined
behavior. GC would actually dramatically reduce the
probabilities of errors here.

> > If you need deterministic clean-up, then waiting for garbage
> > collection to do it is obviously an error. If you need
> > anything deterministic, unless it follows scoping rules very
> > rigorously, you reall want some explicit indication of it
> > being invoked.

> Right, but mixing two approaches in one application will
> surely lead to problems.

Why? Every application I've worked on now for over ten years
has used some sort of non-deterministic destruction for some
categories of objects. Many C++ programmers are in this
situation -- look at all the references to boost::shared_ptr
here, for example.

> > I think you're looking at the problem backwards. The current
> > problem is that I am obliged to use deterministic cleanup
> > even when it is not the appropriate solution, because there
> > is nothing else.

> Well, you are not obliged to do 'deteministic cleanup' --
> simply language doesn't provide inherent support for
> alternative. However you easily could create it -- but you'll
> need to "uglify" your application with explicit calls to new
> mechanism.

No. At present, I'm forced to use some special pointer type,
like boost::shared_ptr.

> >> b) Introducing "special" Dispose() function:
> [skip]
> >> b2) defies the whole purpose of C++ destructors

> > I'm not too sure what you mean by "defies" in this
> > context. The purpose of C++ destructors is to provide
> > deterministic cleanup. Regretfully, we're also required to
> > use them for memory management, since we don't have any
> > alternative.

> No, purpose of C++ destructors is to provide cleanup for any
> system resource logically associated with object's bit value
> (and current environment state, to be completely precise).
> This is uniform approach for any type of resource and as I am
> trying to prove breaking this uniformity leads to a lot of
> problems.

The problem is that you are trying to prove something that
contradicts most peoples experience.

> "Deterministic" -- is just the way of using C++ destructors by
> C++ machine.

> >> b3) what will you do if you derive a class B (which also
> >> handles some system resources) from this class? Call
> >> A::Dispose() from B::Dispose()? This defies fundamental
> >> feature of C++, i.e. coupling subobject's
> >> constructors/destructors into one atomic-looking function

> > Why do you want to change the way destructors work in C++?
> > There's nothing in the proposals I've seen for garbage
> > collection to even suggest that this should be done.

> I do not want to change it. This speculation was about way
> proposed in C#/.NET -- i.e. putting non-memory related cleanup
> into special function, which itself looks like C++ destructor.

I don't know about that. I know nothing about .NET, nor about
C#. I've used Java, and GC was one of the (few) nice things in
it.

In the end, the question is simply: do I have to manually put
memory management somewhere (doesn't matter where), or is it
automatic. You seem to be arguing that I need the extra work.

> > Adding garbage collection to C++ does not replace (and
> > remove) any present functionality. It offers an additional
> > functionality, for the cases where it is appropriate.

> It depends on approach you are having in mind.

The only proposals I've seen.

> > Have you read any of the actual proposals? Or the older
> > ones? Have you experimented with existing garbage
> > collectors?

> Yes, yes and yes.

> > What is the relationship between garbage collection and the
> > number of open file handles? I don't see one.

> Suppose you have a class that wraps HANDLE -- i.e. calls
> CloseHandle on destruction. If you put burden of destroying
> this object on GC's shoulders it will take considerable time
> until HANDLE will be returned to system. And as application
> becomes bigger and bigger that delay will become longer.

But why on earth would you put the burden of calling CloseHandle
on garbage collection? (For that matter, CloseHandle is a
pretty bad example, because it has a return code, and you
wouldn't normally put it in a destructor anyway, since you need
to be able to propagate the return code. But it doesn't
matter. You seem intent on creating red herrings, about how
garbage collection doesn't solve things its not supposed to
solve, or about how reducing a programmer's work load will turn
them into idiots.)

> >> GC is inappropriate for 99% of real-time sensitive
> >> applications.

> > If you are talking about hard real time, all dynamic memory
> > allocation is inappropriate. If you are talking simply about
> > response times, garbage collection typically does better
> > than manual management.

> Hmmm... I was talking about unpredictiveness of GC session.
> For example: system that every minute runs very expensive
> analysis of data gathered for this minute (for example -- any
> signal-based system for traders). You need to finish all
> calculations very fast without any interruption, since every
> miliseconds counts here. GC will really spoil the fun if it
> will interrupt execution with its expensive garbage collection
> session.

I see that you don't know much about garbage collection, or
memory management in general. A system like you describe is the
very sort where garbage collection shines. Where you have a lot
of free time overall, but on a specific event, you are very
pressed for time. On such systems, it's usually pretty trivial
to arrange for garbage collection to run when you don't have
anything to do.

> > There also exist real-time garbage collection
> > implementations, with guaranteed hard response times. (I
> > don't know of anything equivalent for malloc/free, but it
> > would be possible. Using the same techniques the real-time
> > garbage collectors use.)

> Agreed, but I think nobody will put specialized GC into C++ --
> it would be general-purpose GC.

That's up to the implementation. Today, the general systems I
know (Solaris, for example), don't offer real time malloc/free,
because that's not what their users need. (Solaris doesn't
offer any real time guarantees in the OS, either.) Presumably
systems for embedded processors do, and will for garbage
collection too.

> > C++ is one more reason not to be careful. Let's go back to
> > assembler.

> No. C++ doing very many useful things automatically and
> efficiently. For example -- generating code for class
> constructor. In assembler these features could be implemented
> more efficient but just marginally.

Quite. About the only difference with regards to the garbage
collection issue is that manual implementation of memory
management is typically less efficient than garbage collection.

> >> Reason #7: Unordered destruction
> >> Could be a problem. Leads to 'Dispose()' introduction.

> > I don't know where you got this idea that garbage collection
> > has any effect on destruction. Destruction works exactly
> > like it always has, garbage collection or not.

> If your GC will call object's destructor (if it was not called
> yet) -- it has effect.

Objects with non-trivial destructors shouldn't normally be
garbage collected, so the problem doesn't occur.

> But since there are a lot of resources that should be released
> right now without waiting for GC -- you need some kind of C#'s
> Dispose().

See above. If I have something to do, I do it. If I don't, I
don't. The only difference is that currently, I almost always
have something to do: free memory, where as with garbage
collection, I only rarely have anything to do.

> Now about unordered destruction -- some objects needs to be
> destroyed before others. GC could make a guess but it will
> fail if there is a cycle dependency. That means you need to
> explicitly break this dependency -- that leads to conclusion
> that GC does not help with ALL cyclic dependencies :)

You seem caught up in this business of using garbage collection
for who knows what. Garbage collection is used to free memory.
It is not used to manage other resources, or to ensure
transactional integrety. If you use garbage collection when you
need something else, you will have problems. If you use
multiplication when you need addition, you will have problems.
Do you also want to do away with multiplication, because the
programmer can do it manually with addition.

[...]


> >> c) Absence of common resource management approach in most
> >> development companies

> > ??? Don't understand this one.

> Every time I come in new company I found that their project
> has no common approach to resource handling (whether memory or
> handles or anything else).

So. Most of the companies I've worked for have a variety of
approaches, depending on the resource being managed. One size
doesn't fit all, and the companies seem to realize this.

Most of the companies I've worked for have used smart pointers
to implement a primitive form of garbage collection, simply
because doing it by hand was too much work.

> >> c) Memory allocation/deallocation seems very fast (if you
> >> calculate: sum(allocation_time) + sum(deallocation_time) +
> >> sum(garbage_collection_time) over long period of time, I am
> >> sure it will be more that sum(allocation_time) +
> >> sum(deallocation_time) for application without GC
> >> (especially if application uses advanced allocation
> >> technique, e.g. lock-free allocation)

> > Have you actually measured this? All of the benchmarks I've
> > seen contradict it.

> Indeed general allocator functions performs slower that GC's.

Indeed.

> But:
> 1. using GC has run-time impact on overall application performance

Obviously. Anything you change will have an impact.

In the rare benchmarks I've seen, the impact of garbage
collection has been to improve overall application performance.
In specific cases where I've actually used garbage collection,
it has improved response times enormously -- those were
interactive applications, of course.

> 2. GC session length is unpredictable and could be really huge
> depending on number of allocated objects and complexity of
> links between them.

The execution time of chained destructors is even more
unpredictable than garbage collection. If you need a hard upper
bound, there exist real-time garbage collectors which give it --
I don't know of any implementations of malloc with a hard upper
bound, but it should be just as possible. If you are only
concerned about pauses, modern garbage collection does a better
job of avoiding them in most applications than manual memory
management.

> 3. http://www.cs.utah.edu/~wilson/compilers/papers/pldi04-michael.pdf
> describes lock-free allocator with equivalent to GC
> allocation/deallocation time. But without costly GC session
> and runtime performance impact. Extra memory usage in this
> allocator scheme is not bigger than GC's footprint.

> 4. I measured this in managed C++ and pure C++, also I used my
> own allocator.

The comparisons I've seen all used the Boehm collector. On
Solaris on a Sparc, in any case, this is faster overall than the
default malloc/free for most programs. But the performance of
either depends largely on memory use patterns.

> So I doubt that having GC in your application will give you
> significant gains over long time providing that your app is
> complex (and big) enough.

You doubt, but the actual measurements seem to disagree.

(One of them was X, itself, which actually ran faster with
garbage collection than with malloc/free. It also didn't leak
memory with garbage collection, which is more than you can say
about the version with malloc/free.)

> >> e) Reduced development costs
> [skip]
> > But what does all this have to do with garbage collection?

> I told about it already... It will make language seemingly
> easier thus creating more projects written by amateurs that
> should be restarted every day to work.

That's the same argument I heard in favor of assembler thirty
years back. And about as relevant.

> And unfortunately such solutions has advantage over good
> software since majority of management has no necessary
> technical level to make right decision -- theey will choose
> cheapest solution in 70% of cases.

As they should. The cheapest solution which meets the
requirements (including maintainability, etc.). Are you saying
that we should introduce extra costs just because we can?

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

ka...@gabi-soft.fr

unread,
Jan 26, 2005, 2:19:43 PM1/26/05
to
david eng wrote:
> "Herb Sutter" <hsu...@gotw.ca> wrote in message
> news:7daav0tl216o43nj2...@4ax.com...
> > On 22 Jan 2005 21:42:34 -0500, davide...@yahoo.com wrote:

[...]


> > >C++ is not like Java or .Net which are based on virtual
> > >machine concept. C++ is compiled to native machine
> > >instruction and has a limited run time environment. This
> > >characteristics determines that

> > Of course, the virtual machine can be viewed as just another
> > target machine. C and C++ are very good at being able to
> > target many processors with the same source base.

> Yes. However, virtual machine concept is not just GC based.
> There are none GC based virtual machines. C++ work well with
> COM and CORBA. As a matt of fact, most heavy dirty and
> mission critical server applications are none GC based virtual
> machine (run-time environment), i.e., CORBA and COM
> applications. Please give me an example of GC based virtual
> machine applications in such production environment, if you
> can.

I'm not sure what you're trying to say here. The presence of a
virtual machine and of garbage collection are orthogonal. I've
used virtual machines without garbage collection (pCode, many,
many years back), and it's rare that I don't use garbage
collection with natively-compiled C++, although for political
reasons, I'm usually force to use an inefficient implementation
of it, based on smart pointers.

> > >As an average C++ programmer, I never feel there is problem
> > >to manage memory. I have great pleasure to write my own
> > >smart pointer and memory manager. GC not only is
> > >unnecessary but also violates the design philosophy of C++:
> > >elegant and efficient. I wonder why these C++ gurus want
> > >to add GC to C++ standard? Does it come from political
> > >reasons or they just cannot see the big picture?

> > Well, in Bjarne's talks about C++0x evolution, you'll always
> > find the bullet "optional garbage collection." I hope Bjarne
> > has the reputation of being a credible C++ guru without a
> > political axe to grind. :-)

> Bjarne only talks about ?optional garbage collection?. It
> feels like if you don?t talk about GC it is certainly that you


> are wrong. I feel it is more political than reality. From

> Bjarne?s heart, how much does he really want GC?

Ask him. He can speak for himself; he doesn't need you to tell
the world what he thinks. I myself wouldn't dare speak for him,
but I do find it significant that he proposed allowing optional
garbage collection in the last version of the standard, only to
have to withdraw the proposal due to opposition within the
committee. I don't quite see him fighting for something he
doesn't really want, or that he doesn't see as necessary.

> To me, Bjarne is the smartest people among language creators.
> He is the only one among programmer gurus has philosophy mind.

I'm not sure whether that is complementary or not. I generally
think that what sets him off from most language creators, people
like Dijkstra, etc., is that he doesn't loose sight of the
pratical aspects. He's more a pragmatic engineer than an
abstract philosopher worried about the number of angles on the
head of a pin. But that's just my vague impression; I've met
him a couple of times, but not enough to say that I know him in
any way.

> I do hope Bjarne can offer his opinion regarding this topic.

I don't doubt that he does. In the committee, where it makes a
difference.

> But I doubt that he can totally avoid politics. Remember,
> Microsoft just hosted the annual C++ standard committee
> meeting.

> {As a moderator, I have some concern about that last
> sentence. That MS has hosted several WG21 & J16 meetings is
> absolutely irrelevant to the decisions made by those
> committees. -mod/fwg}

As a moderator, I don't think that there's much you can say:-).
As a past member of the committee, I do think it worth pointing
out that *every* meeting of the committee is hosted by someone,
and if you get down to it, Microsoft has probably hosted a lot
less than their fair share. (That's certainly true if you
equate "fair share" with market share:-).)

I imagine that most of the members of the committee are elated
to have Microsoft working with them, instead of against them, as
it sometimes seemed in the past.

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

ka...@gabi-soft.fr

unread,
Jan 26, 2005, 2:23:18 PM1/26/05
to
Ben Hutchings wrote:
> Michael Pryhodko wrote:
[...]

> > 2. GC performance depends on number of threads in the
> > system... and size of their stacks! (since it needs to
> > traverse every ?registered? stack frame to find all roots
> > in the same way as it is done when exception thrown)

> Yes, and on the size of the static data segment and the size
> of the live heap (or at least the parts of those that contain
> pointers).

Typically, at least on the applications I've worked on, the
amount of stack was negligible compared to the amount of heap.
I suspect that this is true for most applications where garbage
collection, or even dynamic memory allocation, would be
appropriate.

It's hard to make general statements, because there are a lot of
different algorithms for garbage collection, and a lot of
different algorithms for malloc/free as well. If we consider
the non-compacting algorithms commonly used on general purpose
machines (likes Sparcs or PC's), then a rough first estimate is
that with garbage collection, allocation is very, very cheap,
and that the time spent in garbage collection depends on the
amount of memory actually allocated when garbage collection is
triggered; with malloc/free, it is a (fairly large) constant for
allocation and for each explicit free. Which leads to an
immediate back of the envelope estimate that applications which
allocate a few, very large blocks and hold them for long periods
of time will run slower with garbage collection, and that
applications which are constantly allocating small blocks which
are only used for a very short period of time will run faster
with garbage collection. This distinction is the driving force
behind the development of incremental collectors, which know how
to ignore large, long lived blocks most of the time.

[...]


> > And about memory visibility in multi-processor case -- it
> > should not be a big problem on x86, but what about other
> > architectures? what cost could incur GC in order to make
> > sure that its looking at "current" thread state and stack?

> It is necessary to suspend the threads before doing this. At
> least, this is what Boehm's GC does. It's open source and
> fairly well documented, so why not have a look at it rather
> than just speculating:
> <http://www.hpl.hp.com/personal/Hans_Boehm/gc/>

> > Unfortunately GC introduces additional costs all over the
> > generated code.

> The net additional time cost is generally low, and may
> sometimes be negative. If one can successfully manage memory
> without introducing leaks, then the additional memory cost of
> a GC may be substantial; otherwise it will become negative if
> the program runs for long enough.

Even without leaks, the total cost is often lower with garbage
collection. And of course, with garbage collection, it is
sometimes possible to "bunch" the cost into a time where you
aren't doing anything else -- a GUI will almost always be a lot
more responsive with garbage collection, for example. With
manual memory management, you have to pay the cost of freeing
the memory during the processing of each event; with garbage
collection, it is deferred until some later time, typically to a
moment when the GUI event queue is empty (and of course, you
trigger collection every time the queue is empty, so that when
the next event arrives, you're ready).

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

David Abrahams

unread,
Jan 26, 2005, 2:22:12 PM1/26/05
to
David Bradley <dbradleyX...@cinci.rr.com> writes:

> Sergey P. Derevyago wrote:
>> IMHO it's not the case, i.e. GC isn't the panacea.
>
> I'm still leary of the fact that GC allows developers to create overly
> complex object relationships.

It neither allows it nor prevents it. You can create overly complex
object relationships without GC. Because GC raises the level of
abstraction, it allows you to make certain kinds of complexity more
manageable than it would be otherwise, and that's a _good_ thing.
This is no different from any other high-level language construct,
like classes, exceptions, constructors, virtual functions... Whenever
someone argues against a language or library because it "allows"
people to do something that would otherwise be too complicated, it's
fishy.

Which isn't to say that using GC is always a good idea... ;-)

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

Hans Boehm

unread,
Jan 27, 2005, 8:00:04 AM1/27/05
to
Michael Pryhodko wrote:
> >> 1. optimizer is limited in the ways it could use registers (for
> >> example never load pointer into EAX since it is used for
arithmetic
> >> operations and GC could not at given time detect whether EAX holds
> >> pointer or not)
> >
> > No, it is not essential for a GC to know for sure whether a memory
> > location holds a pointer or not (this is called perfect GC).
>
> Lokks VERY strange to me.. could you prove it?
Our "conservative" collector typically has no idea which stack location
contains pointers and which do not. If it looks like a pointer, it's
treated like one. (Please try looking again at
http://www.hpl.hp.com/personal/Hans_Boehm/gc. If that doesn't work,
let me know, and use Google's cache instead.)
...

> >> 3. also there is a cost associated with registering and "upong
> >> entry initialization" of stack tables used to hold pointers. Thus
> >> making function calls more expensive.
> >
> > No, it should be possible to use range tables, as for exceptions.
> > In any case it is not essential to have that information.
>
> Range tables in exceptions handling is VERY expensive, the only
excuse
> for them that exceptions should not normally happens. But GC session
> will happen every N seconds, and tables for them could be bigger than
> for exceprions. That adds to GC session expensiveness.
>
This kind of GC descriptor can cost space. But from what I've seen,
good implementations tend to control that pretty well. The time cost
is usually quite small, since stacks are typically very small compared
to heap sizes.

>
> >> And I wonder how object could be moved if any thread started
> >> working with it? And which costs are associated with mechanism
that
> >> makes this safe?
> >
Many modern compacting collectors can collect only at "safe points",
points in the code at which they know exactly which stack slots and
registers contain pointers, so that those pointers can be updated when
the corresponding object is moved. If the collector has incomplete
information, it will usually have to "pin", i.e. refrain from moving,
at least a few objects. (Our collector doesn't move any.)

>
> >> And about memory visibility in multi-processor case -- it should
> >> not be a big problem on x86, but what about other architectures?
> >> what cost could incur GC in order to make sure that its looking at
> >> "current" thread state and stack?
> >
> > It is necessary to suspend the threads before doing this. At least,
> > this is what Boehm's GC does. It's open source and fairly well
> > documented, so why not have a look at it rather than just
> > speculating: <http://www.hpl.hp.com/personal/Hans_Boehm/gc/>
>
> My knowledge is small in this field and I am risking to say something
> stupid, but: for example on 'Cray's suspending threads will not make
> their memory visible to your thread.
> Thanks for link, but it does not work for me (timeout).
>
On SMPs or ccNUMA machines of which I'm aware this is typically not
much of an issue. Threads are normally suspended by some mechanism
that involves synchronization (e.g. sending a signal, and having the
handler acknowledge). This automatically ensures visibility. I think
most production collectors today need similar synchronization. There
are no doubt GC algorithms and architectures for which this requires
more thought.

>
> > Boehm claims that "[t]he overall processor time used by the
collector
> > for small objects allocation and collection is comparable to a good
> > malloc/free implementations. In a multi-threaded environment, it
may
> > be substantially less, since there are no calls that acquire lock
> > acquisitions; thus the number of lock acquisitions may be
effectively
> > halved." (But it does not perform so well with large objects.)
>
> Hmm... According to my knowledge there are plenty of new malloc/free
> libraries that scales well on multi-processor machines. Unfortunately
> old ones (with process-wide mutex) are having very deep roots... For
> example just look at ATL code that is used instead of CRT whenever
you
> declare ATL_MIN_CRT -- it tries to divide heaps between threads in
> order to avoid locking (with error unfortunately).
>
Much of the issue here doesn't have to do with scalability. Most of
the scalable malloc/free implementations still acquire a lock for each
allocation and for each deallocation. (Emery Berger's latest Hoard
release often does not. Earlier versions did.)

A garbage collector deallocates objects in large groups, and thus does
not need to acquire a lock per object deallocation. Most Java
collectors have a per-thread allocation cache which also avoids the
lock acquisition for small object allocations. (Ours optionally
provides that feature.) In my experience, small object allocation is
usually dominated by locking, so this tends to have a large impact.

Either malloc/free-style allocation or GC can be made
processor-scalable. GC has the advantage that it can easily be made
"thread hot": The GC time can decrease in proportion to processor
count, even if the user application is single-threaded. Hence it
potentially benefits from extra processors, where malloc/free usually
does not. (There are some practical obstacles here, in that starting a
second thread to support the GC tends to force other parts of the
run-time into thread-safe mode, which may cause slowdowns elsewhere.
But I think this is usually minor.)

Hans

Jean-Marc Bourguet

unread,
Jan 27, 2005, 8:05:17 AM1/27/05
to
ka...@gabi-soft.fr writes:

> > Unfortunately it creates a lot of other problems.
>
> Such as? To date, I've not seen anyone mention any, based on
> real experience or knowledge.

* The major problem with GC, is the attitude of some of its
proponents which seem to think that it is a silver bullet
for memory management problems. This set false expectations
and living the return to the real world can be hard. I've
seen this happen and sadly (sadly, because I think GC is the
adequate solution to some problems), the result was the
perception that "GC is crap": thoose burned people will
fight any use of GC and as the climb the hierarchy ladder,
they will be able to prevent more effectively its use.

* Most of my memory managements problems where crashes after
corruptions due to access to a dangling pointer. Tools like
purify showed me where I did the access and far more often
than not, the fix wasn't to lengenth the live of the memory
bloc but don't use *that* pointer because it has become
invalid.

What would happen with a GC? It would lengenth the live of
the memory, I'd access the wrong data, silently giving false
result, perhaps even not detected after too late. And I
don't know of a tool which would help me to find those
accesses to the wrong data (well, sometimes the use of a
weak pointer would do the job, but not in all cases).

* GC usually use more memory than manual handling of memory.
I've never seen this denied by proponents of GC and the
overhead estimation I've seen was from 50 to 100 percent.
We have pressure from customer to handle larger and larger
data set in the 32 bits version of our applications because
going to the 64 bits has a memory and CPU hit.

* I've written about dangling pointers: we can live with
them as long as we don't access them. But with a GC, we'll
want to track all non owning pointers to be able to clear
them so that the memory can be reused. Well, isn't it what
various form of weak pointers are for? Yes, but as far as I
know, the memory overhead of weak pointers is not
negligeable. And if you use weak pointers, you are no more
in the world where you use let the GC handle the memory: you
are back in my world where you decide of a memory policy
adequate for the subject at hand and use the more adequate
implementation.

When there are multiple long lived owning pointers and few
very short lived non owning one, GC is the obvious solution.
When there is only one owning pointer and multiple long
lived non-owning one, GC is obviously not the solution. For
other cases, you have to decide by yourself and look at your
problem.

Obviously, knowing that something is available in the
implementation (destructor in C++, GC in other langages)
will influence the choice of the memory policy and I'm for
having a GC in C++ because it'll widen the design space. I
just know that I'll have first to wait for it to be really
available, then fight some old hats to be able to use it,
and some new hires to keep its use where it make senses for
our applications...

Yours,

--
Jean-Marc

Sergey P. Derevyago

unread,
Jan 27, 2005, 4:19:51 PM1/27/05
to
ka...@gabi-soft.fr wrote:
> So. If that's the appropriate solution. Why do you think that
> there is so much use of garbage collection already in C++?
> Because that is the appropriate solution for many objects.
>
> The only problem is that at present, the usual garbage
> collection solution is purely a library solution, something like
> boost::shared_ptr.
>
IMHO shared_ptr is about object lifetimes rather than GC.
For example, a shared_ptr can decide to call a destructor which doesn't
actually delete the memory but relies on the collector.

[snip]

> Who said that GC should call destructors? (I think that this is
> still a much debated topic, but IMHO, garbage collection
> shouldn't call destructors.
>

Sure, it's the shared_ptr which calls the destructor.


--
With all respect, Sergey. http://ders.angen.net/
mailto : ders at skeptik.net

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

ka...@gabi-soft.fr

unread,
Jan 28, 2005, 11:43:43 PM1/28/05
to
Sergey P. Derevyago wrote:
> ka...@gabi-soft.fr wrote:
> > So. If that's the appropriate solution. Why do you think
> > that there is so much use of garbage collection already in
> > C++? Because that is the appropriate solution for many
> > objects.

> > The only problem is that at present, the usual garbage
> > collection solution is purely a library solution, something
> > like boost::shared_ptr.

> IMHO shared_ptr is about object lifetimes rather than GC.
> For example, a shared_ptr can decide to call a destructor
> which doesn't > actually delete the memory but relies on the
> collector.

Shared_ptr can be used for many things. I use it for managing
locks and transaction handling, for example -- my "destructor"
objects don't destruct anything (except maybe a copy of the
object used in case of rollback). But the most frequent use is
as a garbage collector; any time you use a shared_ptr to an
object with a trivial destructor, and you use the default
destruction routine, that's all it can be. (Note that even if
we add garbage collection to the language, I would be in favor
of keeping shared_ptr.)

> [snip]

> > Who said that GC should call destructors? (I think that
> > this is still a much debated topic, but IMHO, garbage
> > collection shouldn't call destructors.

> Sure, it's the shared_ptr which calls the destructor.

In this case, the problem is that we are using destructors for
memory management, as well as for destruction.

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

ka...@gabi-soft.fr

unread,
Jan 28, 2005, 11:44:45 PM1/28/05
to
Jean-Marc Bourguet wrote:
> ka...@gabi-soft.fr writes:

> > > Unfortunately it creates a lot of other problems.

> > Such as? To date, I've not seen anyone mention any, based
> > on real experience or knowledge.

> * The major problem with GC, is the attitude of some of its
> proponents which seem to think that it is a silver bullet for
> memory management problems. This set false expectations and
> living the return to the real world can be hard. I've seen
> this happen and sadly (sadly, because I think GC is the
> adequate solution to some problems), the result was the
> perception that "GC is crap": thoose burned people will fight
> any use of GC and as the climb the hierarchy ladder, they will
> be able to prevent more effectively its use.

That's a problem for any number of new features. Some years
ago, almost every other post in comp.lang.c++ was claiming that
exceptions were the silver bullet for error handling. Before
that, you heard things about OO, and way before that, Cobol was
the silver bullet which would get rid of the need for
programmers.

It's a reality we have to live with, and it has nothing to do
with the technology in question. We don't refuse exceptions,
because some people oversold them. We don't refuse OO, because
it has been oversold. We didn't refuse Cobol (although we've
gone beyond it) because it was oversold. If you refuse all
technology which has been oversold, we're back to entering
machine instructions in hex. (FWIW: garbage collection has
probably been less oversold in the C++ community than any of the
other stuff.)

> * Most of my memory managements problems where crashes after
> corruptions due to access to a dangling pointer. Tools like
> purify showed me where I did the access and far more often
> than not, the fix wasn't to lengenth the live of the memory
> bloc but don't use *that* pointer because it has become
> invalid.

> What would happen with a GC? It would lengenth the live of
> the memory, I'd access the wrong data, silently giving false
> result, perhaps even not detected after too late. And I don't
> know of a tool which would help me to find those accesses to
> the wrong data (well, sometimes the use of a weak pointer
> would do the job, but not in all cases).

Presumably, you do manage the lifetime of your objects, so that
you can recognize that the object's lifetime has ended.

In practice, while I've found dangling pointers to be more of a
problem than memory leaks, most of the time, extending the
lifetime of the pointer wouldn't have hurt. But I suspect that
it has a lot to do with your development process: in most of the
projects I've been involved with, the death of an "intelligent"
object required some sort of notification to the outside, and it
was trivial to arrange for the internal objects to use this, and
reset the pointers.

This is a general problem -- it's present without garbage
collection, and garbage collection doesn't do anything to solve
it. Garbage collection only manages memory, not the lifetime of
objects which have significant lifetimes.

> * GC usually use more memory than manual handling of memory.
> I've never seen this denied by proponents of GC and the
> overhead estimation I've seen was from 50 to 100 percent. We
> have pressure from customer to handle larger and larger data
> set in the 32 bits version of our applications because going
> to the 64 bits has a memory and CPU hit.

This is true. It's possible to make garbage collection run in
reduced space, but then it becomes very slow. (At least with
the general collectors I know.)

> * I've written about dangling pointers: we can live with them
> as long as we don't access them. But with a GC, we'll want to
> track all non owning pointers to be able to clear them so that
> the memory can be reused. Well, isn't it what various form of
> weak pointers are for? Yes, but as far as I know, the memory
> overhead of weak pointers is not negligeable. And if you use
> weak pointers, you are no more in the world where you use let
> the GC handle the memory: you are back in my world where you
> decide of a memory policy adequate for the subject at hand and
> use the more adequate implementation.

Why to you have the pointers? It's true that in cases like the
observer pattern, the observed object must be notified when an
object dies; otherwise, the object's memory isn't freed. But
the observed object has to be notified anyway, since you don't
want it notifying any state changes.

> When there are multiple long lived owning pointers and few
> very short lived non owning one, GC is the obvious solution.
> When there is only one owning pointer and multiple long lived
> non-owning one, GC is obviously not the solution. For other
> cases, you have to decide by yourself and look at your
> problem.

no one is proposing it as the only solution. But long lived
pointers aren't the strength of garbage collection. Garbage
collection shines when there are lots of short lived objects.
If all of the objects in your application are long lived, and
come and go in response to external input/stimuli, then garbage
collection isn't for you. Even in applications with such
objects, however, I find that there are also a lot of transient,
short lived objects (like the text in a string) which are best
handled by garbage collection.

> Obviously, knowing that something is available in the
> implementation (destructor in C++, GC in other langages) will
> influence the choice of the memory policy and I'm for having a
> GC in C++ because it'll widen the design space. I just know
> that I'll have first to wait for it to be really available,
> then fight some old hats to be able to use it, and some new
> hires to keep its use where it make senses for our
> applications...

That is, regretfully, the case for almost all technologies I've
ever encountered. One collegue insists that everything should
be a template, even if we only instantiate it over one type, and
another wants us to derive everything from a common base, so we
can implement vector without using templates.

I don't consider that a problem with templates, however.

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Larry Evans

unread,
Jan 29, 2005, 10:29:12 AM1/29/05
to
On 01/27/2005 06:05 AM, Jean-Marc Bourguet wrote:
[snip]

>
> * Most of my memory managements problems where crashes after
> corruptions due to access to a dangling pointer. Tools like
> purify showed me where I did the access and far more often
> than not, the fix wasn't to lengenth the live of the memory
> bloc but don't use *that* pointer because it has become
> invalid.
>
> What would happen with a GC? It would lengenth the live of
> the memory, I'd access the wrong data, silently giving false
> result, perhaps even not detected after too late. And I
> don't know of a tool which would help me to find those
> accesses to the wrong data (well, sometimes the use of a
> weak pointer would do the job, but not in all cases).
>
Would a sole ownership smart pointer with non-owning weak
pointers help? IOW, when the sole owner is destructed,
it checks the weak pointer count. If 0, it deletes, if not,
it destructs and sets a flag, is_destructed, in the memory
just destructed. The weak pointer throws if is_destructed
is set when operator* or operator-> is called.

I think this is what:

http://sourceforge.net/projects/noptrlib/

does. It's also what was used in the polaris parallelizing
compiler back in the mid-90's:

http://polaris.cs.uiuc.edu/

except the smart pointer's were LiveWrap and RefWrap (I think).
In fact, the very problem you refer to in sentence:

I'd access the wrong data, silently giving false
result, perhaps even not detected after too late.

occurred before a bug in the code was corrected.

Michael Pryhodko

unread,
Jan 31, 2005, 6:47:43 AM1/31/05
to
> Our "conservative" collector typically has no idea which stack
> location contains pointers and which do not. If it looks like a
> pointer, it's treated like one. (Please try looking again at
> http://www.hpl.hp.com/personal/Hans_Boehm/gc. If that doesn't work,
> let me know, and use Google's cache instead.)

Link is working now. I remember browsing this (or smth very similar)
quite long ago.

>From my point of view this collector basically is a sly heap. It does
not need changes to C++ standard, but here and there it has problems
with C++ features (most of them about 'untraceability').


> Much of the issue here doesn't have to do with scalability. Most of
> the scalable malloc/free implementations still acquire a lock for
> each allocation and for each deallocation. (Emery Berger's latest
> Hoard release often does not. Earlier versions did.)
>
> A garbage collector deallocates objects in large groups, and thus
> does not need to acquire a lock per object deallocation. Most Java
> collectors have a per-thread allocation cache which also avoids the
> lock acquisition for small object allocations. (Ours optionally
> provides that feature.) In my experience, small object allocation
> is usually dominated by locking, so this tends to have a large
> impact.

Indeed lock for each allocation/deallocation is a curse for current
heap implementations. But also that means that if you implement usual
allocation scheme without this locking -- GC's advantage will be
seriously undermined. And such allocator already exist. Just look at
lock-free allocator.

Personally I think:
1. GCs that do not change C++ fundamentals (i.e. object destruction
semantic) are not worthy to use (reasons: you steel need to delete your
objects in time; their main advantage -- "allocation speed" -- could be
diminished with good design of typical allocation scheme)
2. GCs that do change C++ fundamentals (like in C#/.NET) (i.e. changing
object lifetime principles thus allowing developer to ignore 'delete')
introduces too much logical problems (reasons: 80% of this thread is
about those reasons :) )

I can't believe this topic was started as 'yet another templatedef'
post! :))

Bye.
Sincerely yours, Michael.

Michael Pryhodko

unread,
Jan 31, 2005, 6:48:49 AM1/31/05
to
Well, from the agressive style of your answers I see that we are
talking about different things. And analyzing your answers I assume
that you have in mind following GC model:
1. GC does not call object destructor when collecting it
2. GC does not change any fundamental C++ feature -- i.e. object
considered destroyed after call to destructor and so on.

So basically what this GC does is the raw memory recycling. It could be
implemented for whole application by replacing malloc/free calls. Or it
could be implemented by overloading operator new/delete for certain
classes (providing you have necessary compiler and library support for
garbage collection). Or as 'special' factory functions. Neither variant
needs any changes to C++ standard.

My reasoning was mostly aimed against GCs which is similar to C#/.NET
GC -- I believe this is the reason why you do not understood most of my
statements. I used Java rarely. You do not know anything about C#/.NET.
So basically we have no common ground to discuss.

Now about this GC:
I think it conceptually nothing more that advanced memory allocator
with heap checking based on 'roots' idea:
1. It usually speeds up memory allocation/free but incurs costs
elsewhere. And it is questionable if this worth for every application.
Hans Boehm is far more cautios in estimating performance gains than
you. If you use smth like lock-free allocator these gains becomes even
more questionable.

2. You still need to manage all your objects, i.e .calling delete at
appropriate times. As a side effect this GC has nothing to do with
circular dependencies between objects -- you still need to resolve them
yourself.

3. Like any GC it suffers from being fooled by manual memory
management. I.e. vector<T*> will fool GC because of 'placement new'
usage. Do not tell me that STL should be redesigned in order to satisfy
GC -- I have my own code written long ago which uses placement new, it
is already used in many libraries as well.

4. What this allocator should do if it found object who's memory to be
collected but destructor was not called? I think it should call
'terminate()', since your application already experiences unexpected
behavior -- who knows what functionality was in that missed destructor
call? The main difference between current memory leak detection
techniques and GC is that you will know about leak sooner -- but in
release environment it is rarely of help.

Well, here I outlined my thoughts about THAT kind of GC. It is
acceptable to use whenever appropriate, but imho cannot be used as a
'safety net' in release environment due to point 4.

Now commenting your comments:

[a lot of heat stuff skipped]

> Even today, if you access objects which have been destructed, you
> have undefined behavior. GC would actually dramatically reduce the
> probabilities of errors here.

I do not understand how GC outlined above reduce undefined behavior in
such cases? Regardless whether object's memory is reused after 'delete'
or not -- you still will get undefined behavior.


[skip]

>> This is uniform approach for any type of resource and as I am
>> trying to prove breaking this uniformity leads to a lot of
>> problems.
>
> The problem is that you are trying to prove something that
> contradicts most peoples experience.

Since GC outlined above does not breaks resource management uniformity
this statement is irrelevant. And by the way I do not think you have
any right to represent "most people"s opinion.


> But why on earth would you put the burden of calling CloseHandle
> on garbage collection?

Because I was having in mind that GC will call destructors...


> (For that matter, CloseHandle is a pretty bad example, because it
> has a return code, and you wouldn't normally put it in a destructor
> anyway, since you need to be able to propagate the return code. But
> it doesn't matter.

CloseHandle is analog of destructor in Win32 -- it should not fail if
you pass here valid handle in valid context. If it fails (e.g. due to
system bug) -- you still can not do anything except logging error
message and (possibly?) terminate().


>> Hmmm... I was talking about unpredictiveness of GC session.
>> For example: system that every minute runs very expensive
>> analysis of data gathered for this minute (for example -- any
>> signal-based system for traders). You need to finish all
>> calculations very fast without any interruption, since every
>> miliseconds counts here. GC will really spoil the fun if it
>> will interrupt execution with its expensive garbage collection
>> session.
>
> I see that you don't know much about garbage collection, or
> memory management in general.

Thanks for insult. Unfortunately I can not return the favor --
moderator did not allowed it. :(


> A system like you describe is the very sort where garbage collection
> shines. Where you have a lot of free time overall, but on a specific
> event, you are very pressed for time. On such systems, it's usually
> pretty trivial to arrange for garbage collection to run when you
> don't have anything to do.

If:
1. GC has necessary functions to supress garbage collection
2. during those peak loads applications does not allocate too much
memory that could lead to memory swapping (because physical memory is
out).
then you are right, I am wrong.
And besides -- while this application generally has much time between
"end-of-minutes" it can not tolerate big delays from GC, since it still
processes trade messages and passes them to users.


>>> I don't know where you got this idea that garbage collection
>>> has any effect on destruction. Destruction works exactly
>>> like it always has, garbage collection or not.
>> If your GC will call object's destructor (if it was not called
>> yet) -- it has effect.
>
> Objects with non-trivial destructors shouldn't normally be
> garbage collected, so the problem doesn't occur.

You propose to garbage collect only objects with trivial destructors? I
do not believe it.
Then who will call special function that should perform non-trivial
task that normally everybody puts into destructor? If object created on
stack I should create nearby specific object that will call that
function in its destructor(if we reach end of scope by means of
exception or "normal" way)? Who will call this function if this object
is a subobject of another object? I think that destructor is the right
and natural place for this functionality...

[skip]


> The execution time of chained destructors is even more unpredictable
> than garbage collection.

No. Execution time of chained destructors is far more predictable than
garbage collection.


> If you are only concerned about pauses, modern garbage collection
> does a better job of avoiding them in most applications than manual
> memory management.

I doubt it. Even more -- I think we are talking about different things
again here, since for me it evident that manual management is better
than automatic.


> The comparisons I've seen all used the Boehm collector. On
> Solaris on a Sparc, in any case, this is faster overall than the
> default malloc/free for most programs. But the performance of
> either depends largely on memory use patterns.

Agreed.


>> And unfortunately such solutions has advantage over good
>> software since majority of management has no necessary
>> technical level to make right decision -- theey will choose
>> cheapest solution in 70% of cases.
>
> As they should. The cheapest solution which meets the
> requirements (including maintainability, etc.). Are you saying
> that we should introduce extra costs just because we can?

Unfortunately most such decision I sought was wrong over long time, and
developers who where providing support were not happy with it. Also
considering that "support developers" often have lower proficiency
level these projects often becomes a complete mess/spaghetti.
Bye.
Sincerely yours, Michael.

ka...@gabi-soft.fr

unread,
Jan 31, 2005, 3:13:00 PM1/31/05
to
Michael Pryhodko wrote:
> Well, from the agressive style of your answers I see that we
> are talking about different things. And analyzing your answers
> I assume that you have in mind following GC model:

> 1. GC does not call object destructor when collecting it
> 2. GC does not change any fundamental C++ feature --
> i.e. object considered destroyed after call to destructor and
> so on.

I'm trying not to limit myself too much, since I don't know what
will finally be adopted. Thus, I don't know whether GC will
call destructors when the object is collected or not. (I
suspect not, but some other finalization mechanism may be
offered.) I don't think it too relevant, because I don't see
much use in calling a classical destructor at that point in
time.

I am limiting myself to proposals that are really being made,
and that have some chance of being adopted. Thus, I don't
really consider much the Java model or the C# model, at least
not to the point where one would do away with local variables
and explicit calls to destruct.

> So basically what this GC does is the raw memory recycling.

That is more or less the definition of garbage collection.

> It could be implemented for whole application by replacing
> malloc/free calls. Or it could be implemented by overloading
> operator new/delete for certain classes (providing you have
> necessary compiler and library support for garbage
> collection). Or as 'special' factory functions. Neither
> variant needs any changes to C++ standard.

There are two points to consider:

1. An implementation which, say, defined free() as a no-op, and
implemented garbage collection behind the scenes, would not
be conform today. There are a few subtle issues which can
cause problems.

2. Garbage collection is really only useful if I know it will
be there, so that I can omit the destructors for objects
which only need them for memory management. (Note that
there is a big difference between knowing it will be
available, and requiring people to use it when it isn't the
correct answer.)

> My reasoning was mostly aimed against GCs which is similar to
> C#/.NET GC -- I believe this is the reason why you do not
> understood most of my statements. I used Java rarely. You do
> not know anything about C#/.NET. So basically we have no
> common ground to discuss.

What about the proposals which have been made? C++ is not C#
(nor Java -- from what I understand, C# and Java are similar in
so far that all user defined types are in fact pointers). And
garbage collection will obviously play a different role in it
than in a language without true local variables.

I sometimes have the impression that you are arguing about
getting rid of local variables and the delete operator, rather
than garbage collection per se. Neither of these things have
been proposed by anyone, and neither would stand the slightest
chance of being adopted, if only for reasons of backward
compatibility.

> Now about this GC: I think it conceptually nothing more that
> advanced memory allocator with heap checking based on 'roots'
> idea:

It's hard to talk about what it is, conceptually, since we don't
have a final standard concerning it. But I would expect it to
be defined in terms of memory access semantics, and what you can
and cannot do in a legal program. I certainly hope that there
will be no mention of "heap checking" and "roots", which are
implementation details.

> 1. It usually speeds up memory allocation/free but incurs
> costs elsewhere. And it is questionable if this worth for
> every application. Hans Boehm is far more cautios in
> estimating performance gains than you. If you use smth like
> lock-free allocator these gains becomes even more
> questionable.

It often results in a faster program, over all. For certain
categories of applications, it will almost always result in a
more responsive program.

As usual, there are trade-offs: it generally will result in more
memory usage, for example.

But of course, most of the time, the only performance we are
concerned about is that of the programmer. Anything you do for
him is positive.

> 2. You still need to manage all your objects, i.e .calling
> delete at appropriate times. As a side effect this GC has
> nothing to do with circular dependencies between objects --
> you still need to resolve them yourself.

You have to manage objects which need management. In my
experience, this is only a small percentage of all of the actual
types. (Note that with garbage collection, about the only class
in the standard library where a destructor would be necessary is
filebuf and its users. None of the STL would need destructors,
for example, unless the objects in the container needed
destructors. Which again is an exceptionally rare case.)

> 3. Like any GC it suffers from being fooled by manual memory
> management. I.e. vector<T*> will fool GC because of 'placement
> new' usage.

How so? The Boehm collector works perfectly well with
std::vector today, regardless of what the vector contains.
About the only thing: one would probably want to specialize
vector<T*> et al. so that elements removed from the collection
were zapped.

> Do not tell me that STL should be redesigned in order to
> satisfy GC -- I have my own code written long ago which uses
> placement new, it is already used in many libraries as well.

No one is proposing redesigning the STL. I don't know where you
got that idea, but it's completely false.

> 4. What this allocator should do if it found object who's
> memory to be collected but destructor was not called?

That's one of the things which have to be defined. Supposing
the object has a non trivial destructor, of course. With
garbage collection, most objects don't have non-trivial
destructors. That's the whole point of using it.

I would rather prefer a mechanism to leave it up to the user. I
suspect, however, that this will be judged to complex, and that
the destructor will simply be called. About like happens in
filebuf today, if the file hasn't been closed before the
destructor is called. It's a programming error (at least on
output), but the standard library just cleans up, better late
than never, and goes on.

> I think it should call 'terminate()', since your application
> already experiences unexpected behavior -- who knows what
> functionality was in that missed destructor call?

I sort of agree with you (although I'd like a bit more
information about why terminate() is being called). But it goes
against the tradition of C++ -- we don't call terminate in case
of arithmetic overflow, for example, nor if you dereference a
null pointer (although an implementation can in the latter
case).

> The main difference between current memory leak detection
> techniques and GC is that you will know about leak sooner --
> but in release environment it is rarely of help.

The main difference between garbage collection and the current
situation is that there is a large amount of code that I will
not have to write. That's all that really interests me.

> Well, here I outlined my thoughts about THAT kind of GC. It is
> acceptable to use whenever appropriate, but imho cannot be
> used as a 'safety net' in release environment due to point 4.

I don't follow your argument about a "safety net". If I don't
explicitly free memory today, it is a programming error. A
fatal error in most of the applications that I write (servers
which turn 24 hours a day, 7 days a week, for years at a time).

> Now commenting your comments:

> [a lot of heat stuff skipped]

> > Even today, if you access objects which have been
> > destructed, you have undefined behavior. GC would actually
> > dramatically reduce the probabilities of errors here.

> I do not understand how GC outlined above reduce undefined
> behavior in such cases? Regardless whether object's memory is
> reused after 'delete' or not -- you still will get undefined
> behavior.

I've lost context. It would be necessary to see a concrete
example. However:

-- If delete is explicitly called, any access to the object
after that would be undefined behavior. Similarly, any
access through a pointer to a local variable which has gone
out of scope would be undefined behavior. In this respect,
garbage collection changes very little with regards to the
current situation -- it might mean that the memory
containing the object won't be recycled, so if the
destructor scribbles all over the object, it will stay
scribbled as long as pointers to it exist, which could
possibly be used for error handling. (In practice. I don't
expect the standard to give any formal support for this.)

-- If no delete is necessary, say because the object represents
a value (e.g. the text of a string), then the object hangs
aroung as long as anyone can access it. No problem here,
because in fact, the object remains valid as long as it
hangs around.

In the first case, we have basically the same situation as
today. In the second, we have an improvement.

> [skip]

> >> This is uniform approach for any type of resource and as I
> >> am trying to prove breaking this uniformity leads to a lot
> >> of problems.

> > The problem is that you are trying to prove something that
> > contradicts most peoples experience.

> Since GC outlined above does not breaks resource management
> uniformity this statement is irrelevant. And by the way I do
> not think you have any right to represent "most people"s
> opinion.

Read a little, then. I'm not making it up. I've been unable to
find any papers what so ever from the last ten years which find
any of the problems you claim present in garbage collection.
And the issue has been intensely studied.

> > But why on earth would you put the burden of calling
> > CloseHandle on garbage collection?

> Because I was having in mind that GC will call destructors...

It might. I'm not familiar with the details of CloseHandle, but
I suppose that it is something similar to the close() function
of Posix. If so, you certainly aren't going to wait until a
destructor to call it. (A quick check with the on-line
documentation shows that it has a return code with error
status. Which pretty much means that it can only be called from
a destructor in exceptional cases.)

Even if it could reasonably be called from a destructor, all
that would mean is that you don't wait for garbage collection to
call that destructor. I failt to see where this is a problem.
I have classes already which must be destructed at a
deterministic point in time. They're a minority, but they
exist. So I ensure that they are destructed when they must be.
Garbage collection doesn't change anything here.

What garbage collection means is that I don't have to do the
same work for classes where it is irrelevant.

> > (For that matter, CloseHandle is a pretty bad example,
> > because it has a return code, and you wouldn't normally put
> > it in a destructor anyway, since you need to be able to
> > propagate the return code. But it doesn't matter.

> CloseHandle is analog of destructor in Win32 -- it should not
> fail if you pass here valid handle in valid context. If it
> fails (e.g. due to system bug) -- you still can not do
> anything except logging error message and (possibly?)
> terminate().

The documentation that I have isn't that complete. Doesn't it
possibly flush buffers, say if the handle is for a file? If
there is a write error then, You certainly need to propagate
that error. (As I say, what I know is Posix, and I don't wait
for the destructor to close a file in Posix, except in
exceptional cases.)

> >> Hmmm... I was talking about unpredictiveness of GC session.
> >> For example: system that every minute runs very expensive
> >> analysis of data gathered for this minute (for example --
> >> any signal-based system for traders). You need to finish
> >> all calculations very fast without any interruption, since
> >> every miliseconds counts here. GC will really spoil the fun
> >> if it will interrupt execution with its expensive garbage
> >> collection session.

> > I see that you don't know much about garbage collection, or
> > memory management in general.

> Thanks for insult. Unfortunately I can not return the favor --
> moderator did not allowed it. :(

Sorry about that:-). I'll admit that I got out of hand. But it
is frustrating, since you seem to continually ignore what one is
saying, and bring up points that haven't been true for ages.

The first systems I used with garbage collection had noticeable
pauses. Which isn't good. I seem to have recall reading
somewhere (I can't remember where) that Stroustrup had a similar
experience, and that this was why garbage collection wasn't in
C++ to begin with.

Technologies evolve, and this is simply not a problem with
modern garbage collection. Or at least, it is no more of a
problem than when an explicit call to a destructor triggers a
chain of destructors -- for many applications, such as GUI's, it
may even be less of a problem, since the actual work will be
deferred to moment when the system would otherwise be idle.

> > A system like you describe is the very sort where garbage
> > collection shines. Where you have a lot of free time
> > overall, but on a specific event, you are very pressed for
> > time. On such systems, it's usually pretty trivial to
> > arrange for garbage collection to run when you don't have
> > anything to do.

> If:
> 1. GC has necessary functions to supress garbage collection

Probably not possible:-). But you can limit it.

> 2. during those peak loads applications does not allocate too
> much memory that could lead to memory swapping (because
> physical memory is out). then you are right, I am wrong.

That's an easy condition. You can allocate as much memory as
you want, as long as there is more there. So just add memory
until it stops pausing:-).

This is where my greatest experience with garbage collected
systems lies, and I can't imagine doing an interactive system
otherwise. With manual memory management, the first thing I do
is install a user defined operator delete which simply marks the
memory as freed, and returns, and then a low priority thread
which runs when there is nothing else to do which does the
actual free.

> And besides -- while this application generally has much time
> between "end-of-minutes" it can not tolerate big delays from
> GC, since it still processes trade messages and passes them to
> users.

The whole point is that with the typical implementations of
garbage collection, the time spent in a pass through the
collector depends on the amount of memory actually allocated.
If you do it in the pauses, there will typically be very little
allocated, and so it will run quickly.

> >>> I don't know where you got this idea that garbage
> >>> collection has any effect on destruction. Destruction
> >>> works exactly like it always has, garbage collection or
> >>> not. > >> If your GC will call object's destructor (if it
> >>> was not called > >> yet) -- it has effect.

> > Objects with non-trivial destructors shouldn't normally be
> > garbage collected, so the problem doesn't occur.

> You propose to garbage collect only objects with trivial
> destructors?

No, but I do think that if the destructor must be run at a
specific time, and cannot wait, waiting for garbage collection
to run it is a programming error.

> I do not believe it. Then who will call special function that
> should perform non-trivial task that normally everybody puts
> into destructor?

Such as? In about 90% of my destructors, the only non-trivial
task is freeing memory. With garbage collection, these
destructors become trivial.

There are two major exceptions to this:

-- RAII classes. 99% of the time, these are local variables,
so garbage collection is irrelevant. In the rare
exceptions, today, the object is managed by an auto_ptr -- I
suspect that this will still be the case with garbage
collection. (Most of the time, the ownershipflow is obvious
in these cases, and I could easily do the management
manually. But auto_ptr is much easier.)

-- Entity classes, whose lifetimes are a function of the
application. Typically, the non-trivial work in the
destructor isn't so much cleaning up as informing the rest of
the application that the object is no longer there. Which
has to be done, garbage collection or not. But I can't
imagine these classes being a problem; when they disappear is
a design decision, usually the result of an external stimulus
(and very often, a delete this). You do have to do the
design, and garbage collection isn't going to change that.

> If object created on stack I should create nearby specific
> object that will call that function in its destructor(if we
> reach end of scope by means of exception or "normal" way)?
> Who will call this function if this object is a subobject of
> another object? I think that destructor is the right and
> natural place for this functionality...

Totally agreed. In fact, I suspect that this is one point where
there is a total concensus. At any rate, no one that I can see
is even hinting of changing this.

> [skip]

> > The execution time of chained destructors is even more
> > unpredictable than garbage collection.

> No. Execution time of chained destructors is far more
> predictable than garbage collection.

Could you show me some studies which support this. Everything
I've seen says the contrary. Chained destructors depend on the
number of items chained (which can be quite high), and has no
upper limit. Real time garbage collectors have hard upper
limits.

> > If you are only concerned about pauses, modern garbage
> > collection does a better job of avoiding them in most
> > applications than manual memory management.

> I doubt it. Even more -- I think we are talking about
> different things again here, since for me it evident that
> manual management is better than automatic.

How so. There is no upper bound for the execution time of
chained destructors. There can be for garbage collection. So
as a minimum, garbage collection has a better worst case. In
practice, a lot depends on the application, but if you can
arrange for the collector to run at moments when you are using a
minimum of memory, the pauses will be less than those caused by
manual management, at least if you use any complicated data
structures.

> > The comparisons I've seen all used the Boehm collector. On
> > Solaris on a Sparc, in any case, this is faster overall than
> > the default malloc/free for most programs. But the
> > performance of either depends largely on memory use
> > patterns.

> Agreed.

> >> And unfortunately such solutions has advantage over good
> >> software since majority of management has no necessary
> >> technical level to make right decision -- theey will choose
> >> cheapest solution in 70% of cases.

> > As they should. The cheapest solution which meets the
> > requirements (including maintainability, etc.). Are you
> > saying that we should introduce extra costs just because we
> > can?

> Unfortunately most such decision I sought was wrong over long
> time, and developers who where providing support were not
> happy with it. Also considering that "support developers"
> often have lower proficiency level these projects often
> becomes a complete mess/spaghetti.

If a project is not well managed, there will be problems. With
or without garbage collection. Garbage collection is not a
silver bullet, and if the process used is not capable of making
a working program without garbage collection, it won't be
capable with. Or vice versa. The difference is, given an
organisation capable of developing good software, garbage
collection will result in lower total costs for the same
quality. (Given a good organization, this is almost a
pleonasm. Anything that gets used results in lower cost -- if
not, it wouldn't be used. And no body is proposing requiring
garbage collection in the cases where it isn't the appropriate
solution.)

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Hans Boehm

unread,
Jan 31, 2005, 3:57:02 PM1/31/05
to

ka...@gabi-soft.fr wrote:
> Jean-Marc Bourguet wrote:
...

> > * GC usually use more memory than manual handling of memory.
> > I've never seen this denied by proponents of GC and the
> > overhead estimation I've seen was from 50 to 100 percent. We
> > have pressure from customer to handle larger and larger data
> > set in the 32 bits version of our applications because going
> > to the 64 bits has a memory and CPU hit.
>
> This is true. It's possible to make garbage collection run in
> reduced space, but then it becomes very slow. (At least with
> the general collectors I know.)
>
If you compare applications that are already written to use explicit
deallocation, that's certainly true, at least for tracing GC. And
that's generally the easiest comparison to make.

(If you view classic reference counting as GC, then the overhead will
clearly vary greatly with the average object size. If you use Boost
shared_ptr with 8 byte objects, it will be huge. If you reference
count files in a file system, it will be near zero.)

If you compare an application that benefits from GC, to a version that
was forced to work with new/delete, things are much less clear, and
harder to measure. There seems to be at least some temptation to
duplicate data to preserve single ownership. I don't know how often
people succumb to that temptation. And in large systems people often
seem to design various other custom allocation schemes which often have
the effect of making garbage of one kind unusable for other kinds of
allocation.

I witnessed a couple of vaguely comparable projects like this many
years ago that suggested that the garbage-collected version might
sometimes use less space. But there were way too many variables to
make this a scientific experiment. And clearly this only applies for
applications that can benefit substantially from GC.

Hans

Hans Boehm

unread,
Jan 31, 2005, 3:57:41 PM1/31/05
to
Michael Pryhodko wrote:
> Indeed lock for each allocation/deallocation is a curse for current
> heap implementations. But also that means that if you implement usual
> allocation scheme without this locking -- GC's advantage will be
> seriously undermined. And such allocator already exist. Just look at
> lock-free allocator.

The performance advantage for small objects, perhaps. But so far, I
think there are still no malloc/free implementations that avoid
per-object synchronization as well as most garbage collectors do.

You have to be careful to distinguish lock-free techniques based on a
compare-and-swap-like primitive from those using thread-local
allocation buffers, and hence no compare-and-swap. Particularly on
Pentium 4 machines, the lion's share of the locking overhead is often
accounted for by the cmpxchg or xchg instructions need to implement the
lock. It can easily cost more than 100 cycles in the best (cached)
case. This kind of lock-free technique can reduce the small object
allocation overhead appreciably, but not enough to compete with a good
GC with thread-local allocation, which uses no per-object
compare-and-swap operations.

I believe the latest version of Hoard does effectively use thread-local
allocation buffers, but only for certain (admittedly common) allocation
patterns. If I understand correctly, if you allocate and then
deallocate data structures containing sufficiently many objects, you
will still have to resort to locks.

And this is not too surprising. You would like "free"d memory to be
available for use in another thread asap. But once you start trying to
do that, inter-thread communication, and hence synchronization, is
involved. The obvious way out of this is to batch the allocation and
deallocation requests, so that you only need one synchronization
operation for many allocations/deallocations. But that also means you
end up with much more gc-like behavior.

Hans

Michael Pryhodko

unread,
Feb 1, 2005, 3:02:42 PM2/1/05
to
> You have to be careful to distinguish lock-free techniques based on
> a compare-and-swap-like primitive from those using thread-local
> allocation buffers, and hence no compare-and-swap. Particularly on
> Pentium 4 machines, the lion's share of the locking overhead is
> often accounted for by the cmpxchg or xchg instructions need to
> implement the lock. It can easily cost more than 100 cycles in the
> best (cached) case.

According to IA-32 manual Volume 3, 7.1.*:
"For the Pentium 4, Intel Xeon, and P6 family processors, if the memory
area being accessed is cached internally in the processor, the LOCK#
signal is generally not asserted; instead, locking is only applied to
the processor's caches"

"The LOCK semantics are followed for as many bus cycles as necessary to
update the entire operand."

"For the P6 family processors, locked operations serialize all
outstanding load and store operations (that is, wait for them to
complete). This rule is also true for Pentium 4 and Intel
Xeonprocessors, with one exception: load operations that reference
weakly ordered memory types (such as WC memory type) may not be
serialized."

So:
1. main performance hit is pipeline serialization
2. as result there is not much difference if operands is cached or not
in terms of given processor time, however locking the bus in later case
can cost since other processors effectively stopped from accessing
anything except their caches.
3. Intel is doing its best to reduce LOCK cost in newer processors
4. I did not found in that manual explicit estimation of how LOCK
CMPXCHG is expensive.

Where did you take that number?


Well, lets dissect allocation/deallocation in case GC and lock-free
heap (LFH) built on the principles from
http://www.cs.utah.edu/~wilson/compilers/papers/pldi04-michael.pdf.

Core:
. allocation:
1. LFH -- N CAS operations (N depends on contention level)
2. GC -- M CAS operation ! (M normally < N). At the core level you need
some king of locking mechanism (or CAS) in order to allocate memory --
it is not just a matter of 'InterlockedAdd()', because even in case of
compacting heap some object could unmoveable and you need to ensure not
to "bump" into already allocated chunk -- it is impossible to do it
without some sort of synchronization, it seems.

. deallocation:
1. LFH -- the same N CAS operations
2. GC -- usually 0.


Now lets try to amortize synchronization costs by introducing
thread-local memory cache(TLMC):
1. LFH -- whenever threads allocates memory it first taken from TLMC
and if no memory available it goes to Core allocation (whether for one
chunk or to refill entire TLMC -- it does not matter). If thread frees
memory -- it goes to TLMC and if it is already too big -- drops chunk
into Core.
2. GC -- allocation is the same as in case of LFH. Deallocation is 0.

As you can see LFH deallocation cost is offset'ed by the fact that LFH
could reuse memory deallocated by this thread, while GC always working
in only one direction -- getting memory from core (until garbage
collection session). It is hard to estimate however how much LFH will
receive from this fact.

Well... Theoretically, from the my point of view, GC (not counting
garbage collection session) and LFH has equivalent speed
characteristics -- difference becomes insignificant in comparison with
other application's functionality:
1. allocation/deallocation at the core level is different in few CAS
operations
2. GC will access core more often
3. LFH could reuse TLMC (it is especially good for apps that deallocate
on the same thread)

However LFH has no costly garbage collection session. And does not
incur any performance lossess due to root tracing issues (or in case of
compacting collector -- "safe points" support).

Also it is worth noticing that on single-processor x86 (which is
majority, I think) CAS could be implemented without LOCK prefix giving
small boost, and needs to be repeated VERY rarely in comparison with
multi-processor PCs. This positively affects both LFH and GC.

Well, in order to avoid accusation of speaking without real experience:
About 2 years ago I implemented simple LFH (without TLMC) on shared
memory (as part of IPC (interprocess communication) library). I do not
remember exact figures of that library performance, but it was really
impressive for that time... Smth around 400Mb/s in 1kb chunks on Duron
1MHz, and around 650Mb/s on dual P2-400. Allocations was the least part
of these operations in terms of timing.

As the result of all these speculations I do not think that any GC
design (currently known to me) has significant advantage over
well-designed LFH.

Bye.
Sincerely yours, Michael.

Hans Boehm

unread,
Feb 1, 2005, 9:05:45 PM2/1/05
to
Michael Pryhodko wrote:
...

> 4. I did not found in that manual explicit estimation of how LOCK
> CMPXCHG is expensive.
>
> Where did you take that number?
I tried the trivial microbenchmark on a 2-way hyperthreaded 2GHz P4
Xeon, running lock;cmpxchg in a loop, always on the same location. I
got 124 cycles per operation. The numbers for various memory barriers
were on the same order.

This varies quite a bit across processors. I measured 10 cycles on an
Itanium 2, 25 for a PIII/500. And those are all Intel processors, and
all without cache misses. Repetition of the experiment is strongly
encouraged. I'd like to know the results on some other machines.


>
> Well, lets dissect allocation/deallocation in case GC and lock-free
> heap (LFH) built on the principles from
> http://www.cs.utah.edu/~wilson/compilers/papers/pldi04-michael.pdf.
>
> Core:

...
GC tends to win mostly in cases in which core allocation doesn't matter
much. It only dominates when objects are large. And, all other things
being equal, malloc/free will always outperform malloc/GC in that case.


>
> Now lets try to amortize synchronization costs by introducing
> thread-local memory cache(TLMC):
> 1. LFH -- whenever threads allocates memory it first taken from TLMC
> and if no memory available it goes to Core allocation (whether for
one
> chunk or to refill entire TLMC -- it does not matter).

I think it does matter. Oversimplifying greatly, let's say there are
"class 1" programs that alternately allocate and drop small amounts of
memory, for which it does not matter. And then there are "class 2"
programs that alternately allocate and drop huge data structures, with
not too much other allocation. In the latter case, a "TLMC" is only
going to help if you can get the core allocator to give you large
chunks (or lists), which are then distributed in small pieces by the
threas-local allocator.


> If thread frees
> memory -- it goes to TLMC and if it is already too big -- drops chunk
> into Core.

For "class 2" programs, it'll always be too big.


> 2. GC -- allocation is the same as in case of LFH. Deallocation is 0.

Allocation is similar, if you can handle class 2 programs correctly. In
the GC case, the thread-local allocator has a much easier time getting
either already sorted free lists (non-compacting) or contiguous memory
(compacting) from the core allocator. I don't how much that buys you
in terms of cache locality.


>
> As you can see LFH deallocation cost is offset'ed by the fact that
LFH
> could reuse memory deallocated by this thread, while GC always
working
> in only one direction -- getting memory from core (until garbage
> collection session). It is hard to estimate however how much LFH will
> receive from this fact.
>
> Well... Theoretically, from the my point of view, GC (not counting
> garbage collection session) and LFH has equivalent speed
> characteristics -- difference becomes insignificant in comparison
with
> other application's functionality:
> 1. allocation/deallocation at the core level is different in few CAS
> operations
> 2. GC will access core more often

With smallish heap sizes probably. With low heap occupancies, if you
need to traverse data structures to deallocate them, maybe not.


> 3. LFH could reuse TLMC (it is especially good for apps that
deallocate
> on the same thread)

Malloc/free probably has a cache-locality advantage if memory can be
quickly reused, which is the case for "class 1" programs. That's
especially true if the whole working set in the heap fits in cache, so
that a sorted freelist doesn't help.


>
> However LFH has no costly garbage collection session. And does not
> incur any performance lossess due to root tracing issues (or in case
of
> compacting collector -- "safe points" support).

It depends. Thread-local caches are likely to be appreciably less
effective, and are rarely available. When they are ineffective ("class
2" programs), they add useless overhead. In the garbage-collected
case, they win almost universally. My guess is that on average they
will be a significant win, even in the malloc/free case, for the same
reasons that generational GC is. But that won't always be the case.

If you look at the current behavior with the more widely used good (not
lock-free) allocators, in this case the one in glibc, you get a
somewhat different picture. For one toy benchmark result, see
http://www.hpl.hp.com/personal/Hans_Boehm/ismm/04tutorial.pdf, starting
around slide 53.


>
> Also it is worth noticing that on single-processor x86 (which is
> majority, I think) CAS could be implemented without LOCK prefix
giving
> small boost, and needs to be repeated VERY rarely in comparison with
> multi-processor PCs. This positively affects both LFH and GC.

I think that eliminating the lock prefix actually results in a large
boost. But uniprocessors are rapidly getting less interesting. I
believe this isn't safe even on a single hyperhtreaded P4, and
certainly not on a multi-core machine.

I'm not sure that contention is a real issue here for mainstream
machines. Hoard avoids it quite well, I think. So does maged
Michael's allocator. So do many garbage collectors.


>
> Well, in order to avoid accusation of speaking without real
experience:
> About 2 years ago I implemented simple LFH (without TLMC) on shared
> memory (as part of IPC (interprocess communication) library). I do
not
> remember exact figures of that library performance, but it was really
> impressive for that time... Smth around 400Mb/s in 1kb chunks on
Duron
> 1MHz, and around 650Mb/s on dual P2-400. Allocations was the least
part
> of these operations in terms of timing.

For 1KB chunks, a GC would probably lose. For 16 byte objects, I doubt
it.

Hans

Michael Pryhodko

unread,
Feb 4, 2005, 6:22:47 AM2/4/05
to
Well... Now we have detailed model of "your" GC:
-- performs raw memory recycling using 'roots' approach
-- it does not own objects (i.e. does not call destructors, and not
calling destructor in time considered error)


Basically that means that for application with that kind of GC:
1. will have GC-realated runtime (dis)advantages (i.e. improved
alloc/free speed, GC session and so on)
2. GC owns MEMORY allocated with it
3. GC has no ownership of OBJECTS
4. Lets define "composite destructor" as sequence of actions performed
in "~ClassName()" (i.e. if your object1 owns (shared or not) another
object2 then actions needed to "close" ownership (delete p (and
objects2's destructor as a consequence), release_ref and so on) are
included in object1's composite destructor).
Now, we can omit from composite destructor all calls that does nothing
due to GC specific (e.g. 'free()', releasing memory shared between
instances of std::string with shared buffer semantic). Lets call
objects with resulting composite destructor empty 'trivial'. Ownership
of trivial object is equivalent to ownership of that object's memory.
As a consequence trivial objects does not need to be 'delete'd if
created on GC heap. It could be stated that GC (or nobody) owns such
object.

I will state that in my working experience trivial objects are VERY
rare. I'll try to explain:
If you draw ownership relations between object instances with arrows
(if o1 owns/contains o2 draw arrow from o2 to o1) you'll get a graph.
Obviously that graph could be complex and could have cycles. Suppose
that every node in that graph is a trivial object. Now if we add any
non-trivial object to the graph every object that could be reached
along arrows becomes non-trivial. This could turn every object in the
graph into non-trivial. Consecuences is dramatic: if before shared
objects (i.e. nodes with two or more arrows going from them) where
referenced by simple pointer, now every such pointer should be replaced
by shared_ptr (or whatever necessary mechanism that express shared
ownership with deterministic destruction).

Here are consequences:
a) in complex systems such objects are rare (my experience confirms
this)
b) you need to know exactly if your subobjects trivial or not (it is
not easy, especially with templates usage)
c) one small addition could affect entire application (development
easier but maintenance and extensibility are harder)

Especially I do not like point c) -- small addition that makes trivial
class non-trivial could break whole application in very subtle ways
(not mentioning that you will need to traverse whole ownership graph in
order to find objects that owns that one -- it could be erroneous).
However I understand that there are application types that will benefit
from this GC model. Imho, these application either not use p4. (i.e. GC
is just fast allocateor for them) or trying to avoid consequences (by
minimizing ownership graph size or dividing graph in a way that will
localize "dangerous" non-trivial objects in small self-contained
clusters with as few links to outside as possible).


In the light of these words -- that is why C#/.NET and Java GCs has
"object ownership" semantic (and I have already said enough about
problems of this approach).

And by the way with "your" GC it is not a problem to garbage collect
objects from stack: compiler could create local objects on GC heap and
call their destructor when they are out of scope. GC will later reuse
memory. This is what will be done in C++/.NET soon (imho).


Now answer to your answers:


> 1. An implementation which, say, defined free() as a no-op, and
> implemented garbage collection behind the scenes, would not be
> conform today. There are a few subtle issues which can cause
> problems.

Which issues?


> 2. Garbage collection is really only useful if I know it will
> be there, so that I can omit the destructors for objects
> which only need them for memory management. (Note that
> there is a big difference between knowing it will be
> available, and requiring people to use it when it isn't the
> correct answer.)

Agreed! And at all -- I think that all heat and misunderstanding in our
conversation comes from the fact that I was trying to speak about every
possible GC model at the same post, while you always had in mind
specific one.


> You have to manage objects which need management. In my experience,
> this is only a small percentage of all of the actual types.

You are lucky :). In my experience (especially COM-related applications
and so on) it is not.


> None of the STL would need destructors, for example, unless the
> objects in the container needed destructors. Which again is an
> exceptionally rare case.

Well, not in my case :). However you have the point here. Since it is
hard to express ownership semantic inside of objects that could be
copied in unpredictable times people tends to avoid putting such
objects into STL containers. However it could be changed dramatically
with introduction of move semantic, or move-traits (I was proposing
this idea around 2-3 years ago on comp.lang.c++; almost nobody noticed
however :~(. I use this approach extensively and my 'std::vector's
contains ownership pointers to non-trivial objects often. For example:

// this construction represents vector of 'int*' which
// will be 'delete'd in destructor
holder< stl_cont<std::vector<int*>, ptr< type<int> > > > v;

)


>> 3. Like any GC it suffers from being fooled by manual memory
>> management. I.e. vector<T*> will fool GC because of 'placement
>> new' usage.
>
> How so? The Boehm collector works perfectly well with
> std::vector today, regardless of what the vector contains.
> About the only thing: one would probably want to specialize
> vector<T*> et al. so that elements removed from the collection
> were zapped.

Well, I think not perfectly well :). Here is quote:

--=-=-=-=- begin --=-=-=-=-
Often the collector has no real information about the location of
pointer variables in the heap, so it views all static data areas,
stacks and registers as potentially containing pointers. Any bit
patterns that represent addresses inside heap objects managed by the
collector are viewed as pointers. Unless the client program has made
heap object layout information available to the collector, any heap
objects found to be reachable from variables are again scanned
similarly.
--=-=-=-=- end --=-=-=-=-

I assume that after call to 'pop_back()' GC will still consider that
last pointer int vector's internal buffer to be live (unless somebody
zero it out). This is not a big problem, but vector can live without
reallocation for long time.


>> I think it should call 'terminate()', since your application
>> already experiences unexpected behavior -- who knows what
>> functionality was in that missed destructor call?
>
> I sort of agree with you (although I'd like a bit more
> information about why terminate() is being called). But it goes
> against the tradition of C++ -- we don't call terminate in case
> of arithmetic overflow, for example, nor if you dereference a
> null pointer (although an implementation can in the latter case).

Imho, this is not tradition -- there are plenty of places where C++
demands 'terminate()' call. It is not demanded in cases where it would
be not appropriate, e.g.:
1. arithmetic overflow -- developer may wanted this explicitly, and it
will produce significant run-time checking overhead
2. dereferencing null_ptr -- it will produce significant run-time
checking overhead

C++ tradition is to give you an option 'not to pay for what you are not
using'. :))


>> Well, here I outlined my thoughts about THAT kind of GC. It is
>> acceptable to use whenever appropriate, but imho cannot be
>> used as a 'safety net' in release environment due to point 4.
>
> I don't follow your argument about a "safety net". If I don't
> explicitly free memory today, it is a programming error. A
> fatal error in most of the applications that I write (servers
> which turn 24 hours a day, 7 days a week, for years at a time).

Somebody in this thread mentions "safety net" feature as GC's advantage
(i.e. calling objects destructors if developer forgot).


[a lot of wise stuff skipped. Most of it already answered in
speculations above]


[about CloseHandle() in destructor]

Well, you statements were more precise. Whenever class wraps HANDLE
that could return error it should provide explicit function for
removing error possibility (i.e. void CloseUnderlyingHandle()
throw(smth) or Flush() or whatever). And if destructor's CloseHandle()
returns error and 'std::uncaught_exception() == false' (or we are
unwinding due to simple error code return) -- that means serious error
in program.


>> 2. during those peak loads applications does not allocate too
>> much memory that could lead to memory swapping (because
>> physical memory is out). then you are right, I am wrong.
>
> That's an easy condition. You can allocate as much memory as
> you want, as long as there is more there. So just add memory
> until it stops pausing:-).

No way :)). That app was too flexible for this. It had plugin
architecture, where plugin -- is the trader's
signal/strategy/analyzer/whatever. I could not predict number of
plugins (it was constantly changing), their complexity or memory usage;
and I'd be very reluctant to introduce any mechanism that could handle
memory estimation because plugins were written by people with
significantly lower C++ expertise level (mostly with math background)
-- this would add unnecessary complexity for them. However I admit that
at the time when I left that company plugins were using quite
predictable amount of memory and what you are proposing could be
implemented at THAT point. I'd say that this engine flexibility makes
it harder to use GC (usual problem -- you always pay with speed for
another level of genericity).


>> No. Execution time of chained destructors is far more
>> predictable than garbage collection.
>
> Could you show me some studies which support this. Everything
> I've seen says the contrary. Chained destructors depend on the
> number of items chained (which can be quite high), and has no
> upper limit. Real time garbage collectors have hard upper limits.

Yes, chained destructors directly depends on number of items chained,
complexity could be estimated on the compile-time stage! GC session
depends on memory used which is totally unpredictable especially in MT
app (I am talking about generic GC -- certainly you could impose upper
time limit at which GC will stop collecting, but that in turn means
that colleciton will not finish and if it will keep happening GC could
blow application working set until system resources run out).


[skip]

> If a project is not well managed, there will be problems.

I do not like to give to "badly managed" projects yet another
possiblity to live. Lets let it rot. Or I could be the next who will be
providing support for this nightmare.


Bye.
Sincerely yours, Michael.

Michael Pryhodko

unread,
Feb 4, 2005, 6:26:52 AM2/4/05
to
>> Where did you take that number?
>
> I tried the trivial microbenchmark on a 2-way hyperthreaded 2GHz P4
> Xeon, running lock;cmpxchg in a loop, always on the same location.
> I got 124 cycles per operation. The numbers for various memory
> barriers were on the same order.
>
> This varies quite a bit across processors. I measured 10 cycles on
> an Itanium 2, 25 for a PIII/500. And those are all Intel
> processors, and all without cache misses. Repetition of the
> experiment is strongly encouraged. I'd like to know the results on
> some other machines.

Well, I need to do some benchmarking myself to get exact figures.
However, I expect:
1. in typical CAS loop first 'LOCK CMPXCHG' is most expensive (it has
to flush read/writes
that are currently on pipeline -- for subsequent operations pipeline
is flushed already)
2. cache miss should not add too much to 'LOCK CMPXCHG' cost


>> 1. LFH -- whenever threads allocates memory it first taken from
>> TLMC and if no memory available it goes to Core allocation
>> (whether for one chunk or to refill entire TLMC -- it does not
>> matter).
>
> I think it does matter. Oversimplifying greatly, let's say there are
> "class 1" programs that alternately allocate and drop small amounts
> of memory, for which it does not matter. And then there are "class
> 2" programs that alternately allocate and drop huge data structures,
> with not too much other allocation. In the latter case, a "TLMC" is
> only going to help if you can get the core allocator to give you
> large chunks (or lists), which are then distributed in small pieces
> by the threas-local allocator.

I still think it does not matter here. Whatever approach you take to
implement TLMC -- since it will be used for both GC and LFH -- it does
not matter which approach you will take.


>> 2. GC -- allocation is the same as in case of LFH. Deallocation
>> is 0.
>
> Allocation is similar, if you can handle class 2 programs correctly.
> In the GC case, the thread-local allocator has a much easier time
> getting either already sorted free lists (non-compacting) or
> contiguous memory (compacting) from the core allocator. I don't how
> much that buys you in terms of cache locality.

Sorry, I do not understand your point here. What I was trying to say
is:
since TLMC approach is the same for GC and LF -- allocation will be the
same, i.e.:
a) try to use TLMC (overhead is the same)
b) if unsuccessful -- call Core (see above for Core allocation
speculations)

and deallocation is 0 since GC can not reuse TLMC due to its nature and
simply "forgots" deallocated memory until GC session will return it to
Core.


>> 1. allocation/deallocation at the core level is different in few
>> CAS operations
>> 2. GC will access core more often
>
> With smallish heap sizes probably. With low heap occupancies, if
> you need to traverse data structures to deallocate them, maybe not.

Sorry, do not see how heap size could influence here. Since LFH has
feature of reusing TLMC (for cost of non-zero deallocation) it will be
calling Core less that GC regardless of heap size.
Also I'd like to refine point 1.:
1a. allocation in LFH Core is different from GC Core in few
non-expensive 'LOCK XXXX' (since both GC and LFH will have first
expensive 'LOCK XXXX'). Can not estimate 'cache miss' impact however
yet.
1b. deallocation in LFH = 1 expensive 'LOCK XXX' + few non-expensive.
But this is offset'ed by 'TLMC reuse' effect.

So, basically GC should have GS session fast enough to keep its
performance advantage. (not mentioning other run-time overhead)


> Thread-local caches are likely to be appreciably less effective,
> and are rarely available. When they are ineffective ("class2"
> programs), they add useless overhead. In the garbage-collected
> case, they win almost universally.

Hmm... Why? In described scheme above the only difference between GC
and LFH -- is that Core is different and (since LFH could reuse TLMC)
TLMC should more effective with LFH.


> or one toy benchmark result, see
> http://www.hpl.hp.com/personal/Hans_Boehm/ismm/04tutorial.pdf,
> starting around slide 53.

These benchmarks looks unfair to me :)). Shared_ptr has two prices:
1. shared object ownership management -- 1 expensive 'LOCK XXX'
2. raw memory management

it basically pays twice -- so no surprise that shared_ptr's bar usually
two time higher.

Did you measured this scheme:
1. object ownership is shared with shared_ptr
2. underlying object memory managed by GC (i.e. shared_ptr calls
GC_FREE and object factory calls GC_ALLOC)
?


> I think that eliminating the lock prefix actually results in a large
> boost.

Sigh... need to measure it again. I remember having explicit
benchmarking for it around 1.5 years ago. I found that plcaing NOP
instead of LOCK prefix does not give significant boost on one-processor
PC.


[skipped my experince description]


> For 1KB chunks, a GC would probably lose. For 16 byte objects, I
> doubt it.

That was just a proof that I have experience in this area, it should
not be looked upon as an argument whether GC faster or not.


Bye.
Sincerely yours, Michael.

Seungbeom Kim

unread,
Feb 5, 2005, 5:33:47 AM2/5/05
to
david eng wrote:

> For this point, I disagree with Bjarne’s claim that C++ is
> better than C. Sometimes, function abstraction is more suitable for a
> defined problem while sometimes class abstraction is the best choice. This
> relationship is like the relationship between physics and mathematics. We
> use both abstraction model to solve really problem. But mathematics is the
> foundation of physics. That's why every modern language claims it is
> derived from C, I guess.

That's actually what makes C++, as a multi-paradigm language, strong and
successful. I agree with you that object-oriented is not necessarily
better than procedural, but I would certainly say that C++ is better than
C because it allows you to do things in more various ways without forcing
you to stick to a particular style.

> In this regard, I also don’t agree with most C++ gurus that people should
> learn class instead of function first. We study calculus and
> differentiation equation first before we study relative and quantum
> theories. There is no other way around. To me, to be a better programmer,
> people should learn C first, then C++. The first listen they should learn
> is abstraction, then function abstraction, class abstraction, generic
> abstraction (another great abstraction model but it is abused in the name of
> "meta programming").

> [...] but that doesn’t mean OO is better then C.

Certainly not, but I'm afraid you're confused: OO is not anything
directly comparable to C or C++, but one of the programming paradigms
that C++ supports but C doesn't. Procedural programming (functions) is
supported by both, but it's not equal to C. I hope you also agree that
"Programmers should learn function abstraction before class abstraction"
does not necessarily mean "They should learn C before C++", being aware
of the distinction between paradigms and languages.

--
Seungbeom Kim

Hans Boehm

unread,
Feb 5, 2005, 11:53:33 AM2/5/05
to
Michael argues that "trivial" objects, i.e. those whose destructor
needs to do nothing are rare, because most objects may "own" opaque
objects, which may or may not have a nontrivial destructor. Hence very
few objects can be handled entirely by the GC.

I disagree a bit with the statistics here; in my experience for many
applications a large fraction of the heap consists of "leaf objects",
e.g. simple strings or pointer-free arrays, which are clearly trivial
by this definition. But this isn't the important issue, and I do agree
that it may leave out a significant fraction of the heap.

More fundamentally: This isn't the right approach to programming with a
garbage collector. If you need to provide explicit destructors just
because you may eventually own an opaque object that needs its
destructor invoked, you're probably doing something wrong.

You do want to make sure that destructors are invoked at the expected
times for objects that you expect to disappear at a well-known point,
especially if timing matters (e.g. locks, closing windows, ...). But
if you own an object that's opaque to you (e.g. because you have a
property list in which your clients stored arbitrary data for later
retrieval), I think it's almost certainly a mistake to provide a
destructor that walks the property list, destroying its elements. If
the elements need clean-up, you need to use the GC's finalization
mechanism for that.

There are at least two reasons for using finalization here:

1) It gets you out of the business of repeating the garbage collector's
work.
2) At least in the most general cases, it is unsafe to call the
destructors synchronously. Hence you have to give up timing guarantees
anyway. (And especially in the case of opaque objects, it seems
unlikely to me that timing matters. If abstraction boundaries prevent
you from knowing what you are destroying, why is it critical to know
when it happens?)

The quick reasoning for (2) is:

- If I'm being called at an unknown time, I have no idea what locks my
thread currently owns, or what data structures are currently in an
inconsistent state.

- If I invoke destructors on opaque objects I own, those destructors
almost certainly will have no idea when they're being invoked.

- The job of a garbage collector (including something like Boost
shared_ptr) is to free me from having to worry about when things may be
deallocated. Thus garbage-collected objects by definition become
unreachable at "unknown" times.

- Nontrivial destructors (or finalizers!) generally update global data
structures (admittedly possibly inside the OS kernel). There is very
little point in updating just the object, since it's about to go away.

- Those global data structures may be inconsistent or locked when the
destructors are invoked. (Admittedly this is generally not an issue
for kernel data structures.)

- Such destructors need synchronization, or explicit queuing, to run
safely. I can't do either if I insist on specific destructor timing.

For a more detailed argument, see
http://www.hpl.hp.com/techreports/2002/HPL-2002-335.html

This means that:

- You can't ignore finalization. In my mind, it's necessary. I'm not
sure whether you should have it invoke destructors, as in the
Ellis-Detlefs proposal. Technically that may be optimal. But it does
seem to confuse people, in that synchronously invoked destructors and
finalizers are not at all the same thing; at best you're overloading
the same syntax. And most finalizers will need synchronization, where
synchronously invoked destructors might not.

- The number of "trivial" objects increases drastically, since you
don't have to worry about the possibility of owning a "nontrivial"
object. Based on what I've seen usually to > 99%. The number of
finalizers in well-written Java code is some indication of that. If
you look at the gcj Java library (basically Classpath), you find fewer
than 50 out of 2700 classes with finalization. And I believe that
they're all dynamically rare in most applications. And some of those
probably shouldn't really be using finalization. (The one good thing
about finalization is that you should very rarely have to deal with
it.) (Statistics from Xerox Cedar are consistent with this. The
numbers I've heard from .NET are not, presumably due to a different
dominant programming style, which I don't yet understand.)


Hans

Alexander Terekhov

unread,
Feb 5, 2005, 8:46:51 PM2/5/05
to

Hans Boehm wrote:
[...]

> - The job of a garbage collector (including something like Boost
> shared_ptr) is to free me from having to worry ...

Not quite. Applications control the lifetime of Boost shared_ptrs
and applications must be "prepared" that the managed object's dtor
can fire at any point where the lifetime of last Boost shared_ptr
owning this or that object ends. Post-dtors is another story. ;-)

regards,
alexander.

Hans Boehm

unread,
Feb 8, 2005, 6:42:51 AM2/8/05
to
Alexander Terekhov wrote:
> Hans Boehm wrote:
> [...]
> > - The job of a garbage collector (including something like Boost
> > shared_ptr) is to free me from having to worry ...
>
> Not quite. Applications control the lifetime of Boost shared_ptrs
> and applications must be "prepared" that the managed object's dtor
> can fire at any point where the lifetime of last Boost shared_ptr
> owning this or that object ends. Post-dtors is another story. ;-)
>
I agree that they must be, but I don't think they reasonably can be,
unless either:

- The application is relatively simple and can be entirely understood
by an individual, or

- Destructors accessing shared data structures really defer the
interesting part of the work so that it runs asynchronously.

[Herb Sutter and I had an earlier related email discussion. I'm
borrowing some from that here, though I'm not sure he agrees with all
of this.]

Consider a multithreaded non-reentrant-lock-based application that
consistently uses shared_ptr and friends for memory management. None
of this is essential, but it makes the problems clearer.

If I want to guard against deadlock, the usual methodology is to be
aware of which locks can be acquired by each method, and to ensure that
they are always acquired in a consistent order.

But now consider a method M using shared_ptr<T> for various T. This
method can acquire any lock needed not only by any of T's destructors,
but by the destructor associated with anything reachable from T, or by
any, yet-to-be-implemented, subclass of T. It seems to me, I have no
business knowing about that. Even if I wanted to, I would need an
awfully weird specification for T to prevent such future subclassing,
since I would have to restrict what it can reference via shared_ptr's.

If I simply state that M can acquire any possible lock, I can't call
any such M while I'm holding a lock, which is clearly untenable. (And
actually not even sufficient, since the destructors themselves may
acquire locks in the wrong order.)

Hans

Hans Boehm

unread,
Feb 8, 2005, 12:08:28 PM2/8/05
to
Michael Pryhodko wrote:
> Well, I need to do some benchmarking myself to get exact figures.
> However, I expect:
> 1. in typical CAS loop first 'LOCK CMPXCHG' is most expensive (it has
> to flush read/writes
> that are currently on pipeline -- for subsequent operations
pipeline
> is flushed already)
Probably. I was actually measuring pretty much the easiest case in
which there was not much else in-flight.

> 2. cache miss should not add too much to 'LOCK CMPXCHG' cost
I would be surprised if that were correct. Since I was timing the
instruction in a loop, presumably the processoralready had exclusive
access to the cache line. I suspect you need to add any overhead
required in getting exclusive cache-line access.
> ... [Thread-local allocation caches with or w/o GC]

> I still think it does not matter here. Whatever approach you take to
> implement TLMC -- since it will be used for both GC and LFH -- it
does
> not matter which approach you will take.
The real issue seems to be whether you are willing to defer some work,
so
that you can do it in larger batches to minimize synchronization. With
a GC,that's natural. Without a GC, consider, a program with two
threads, where both threads alternately build up and tear down a 2GB
linked structure. Assume they are out of sync, so that one thread
allocates, while the other deallocates.

Keeping track of a few recently deallocated objects in each thread
won't help. Keeping all objects local to each thread will require 4
GB. Deallocating objects in one thread so that they can be reallocated
in the other requires synchronization. The only way to get out of
per-object synchronization while reusing objects from one threaad in
the other is to "transfer" objects in batches.
> ...


> > or one toy benchmark result, see
> > http://www.hpl.hp.com/personal/Hans_Boehm/ismm/04tutorial.pdf,
> > starting around slide 53.
>
> These benchmarks looks unfair to me :)). Shared_ptr has two prices:
> 1. shared object ownership management -- 1 expensive 'LOCK XXX'
> 2. raw memory management
>
> it basically pays twice -- so no surprise that shared_ptr's bar
usually
> two time higher.

I agree that it pays twice. But if you are using it as a GC
replacement (at least with no destructor invocations for non-memory
management, see the separate discussion on that), I'm comparing normal
usage for both memory management styles, I think.


>
> Did you measured this scheme:
> 1. object ownership is shared with shared_ptr
> 2. underlying object memory managed by GC (i.e. shared_ptr calls
> GC_FREE and object factory calls GC_ALLOC)
> ?

I don't think it would be much different. At least our GC doesn't use
the thread-local cache for GC_FREE. To really benefit from that, you
currently need to explicitly enable it and rely onthe collector for
reclammation. Using the most recent version of Hoard as the underlying
malloc would be interesting, though.

You can get some idea of where the costs are coming from by looking at
the "C expl. free" bar, which is the C version with an explicit tree
walk for deallocation. It includes the malloc synchronization cost,
but not the reference count updates, or the extra object allocations
needed for "shared_ptr", but not "custom".

Hans

Larry Evans

unread,
Feb 8, 2005, 7:35:57 PM2/8/05
to
On 02/08/2005 04:42 AM, Hans Boehm wrote:
[snip]

> But now consider a method M using shared_ptr<T> for various T. This
> method can acquire any lock needed not only by any of T's destructors,
> but by the destructor associated with anything reachable from T, or by
> any, yet-to-be-implemented, subclass of T. It seems to me, I have no
> business knowing about that. Even if I wanted to, I would need an
> awfully weird specification for T to prevent such future subclassing,
> since I would have to restrict what it can reference via shared_ptr's.

Would it be any help in solving the problem if you had:

descriptor_gc<T>
descriptor_lock<T>

for each type, T? IOW, just like the GC_descr (corresponding to
descriptor_gc<T> described here:

http://www.hpl.hp.com/personal/Hans_Boehm/gc/gc_source/gc_typedh.tx

there could be a similar descriptor for types containing locks. Then
"anything reachable from T", e.g. an instance of type S, could be found
using descriptor_gc<T> and any locks contained by S could be found
using descriptor_lock<S>.

Pardon if this is a dumb question. I've never (or hardly ever)
used threads.

TIA.
Larry

and then descri,

Michael Pryhodko

unread,
Feb 9, 2005, 6:06:08 AM2/9/05
to
>> 2. cache miss should not add too much to 'LOCK CMPXCHG' cost
>
> I would be surprised if that were correct. Since I was timing the
> instruction in a loop, presumably the processoralready had exclusive
> access to the cache line. I suspect you need to add any overhead
> required in getting exclusive cache-line access.

Agreed, but I did this assumptions thinking that "flush the pipeline"
is the lion share of all time 'LOCK XXXX' takes. Well, this also needs
measuring.


>> I still think it does not matter here. Whatever approach you take
>> to implement TLMC -- since it will be used for both GC and LFH --
>> it does not matter which approach you will take.
>
> The real issue seems to be whether you are willing to defer some
> work, so that you can do it in larger batches to minimize
> synchronization.

The whole purpose of TLMC was to amortize cost of calls to core in both
cases (LFH and GC). TLMC is pretty the same for them both, except that
for LFH it tries to retain some 'free'd memory (but not all) and for GC
there is no 'deallocation'.


> With a GC,that's natural. Without a GC, consider, a program with
> two threads, where both threads alternately build up and tear down
> a 2GB linked structure. Assume they are out of sync, so that one
> thread allocates, while the other deallocates.

There is no problem with naturality -- so far allocation/deallocation
mechanism is the same for user. About 2 GB stuff -- remember I was
speaking about TLMC for LFH:

>> If thread frees memory -- it goes to TLMC and if it is already too
>> big -- drops chunk into Core.

Well, you could drop let say 10% of TLMC for further amortization. (You
could add any number of elements into single-linked list with only one
CAS operation). So with described above TLMC+LFH this process won't
take 4Gb.

Well, it seems that I need to implement this LFH and run practical
tests. :)))


> I agree that it pays twice. But if you are using it as a GC
> replacement (at least with no destructor invocations for non-memory
> management, see the separate discussion on that), I'm comparing
> normal usage for both memory management styles, I think.

You cannot use GC as a replacement for shared_ptr. Shared_ptr provides
OBJECT sharing mechanism (not memory) with deterministic destruction.
To do so you need to replace C++ destructor's mechanism with GC
finalization -- and this is not so easy.


Bye.
Sincerely yours, Michael.

Michael Pryhodko

unread,
Feb 9, 2005, 6:05:25 AM2/9/05
to
> Michael argues that "trivial" objects, i.e. those whose destructor
> needs to do nothing are rare, because most objects may "own" opaque
> objects, which may or may not have a nontrivial destructor. Hence
> very few objects can be handled entirely by the GC.
>
> I disagree a bit with the statistics here; in my experience for many
> applications a large fraction of the heap consists of "leaf
> objects", e.g. simple strings or pointer-free arrays, which are
> clearly trivial by this definition. But this isn't the important
> issue, and I do agree that it may leave out a significant fraction
> of the heap.

Ok, here is what comes to my mind about this that makes object
'non-trivial' (my experience):
1. any HANDLE to system resources
2. any COM interface (which by the way can not be released in Finalizer
due to apartment issues)
3. basically any "handle" to resource "external" to C++ environment if
you can not make assumption "I could defer it's release until later" --
i.e. 'file close', DB logon session and so on.
4. any logic that relies on deterministic destruction, i.e. unroll smth
if 'enclosing object is destroyed'/'if we leave enclosing scope'.
Standard C++ features makes it ideal to place such things into
destructors.


> More fundamentally: This isn't the right approach to programming
> with a garbage collector. If you need to provide explicit
> destructors just because you may eventually own an opaque object
> that needs its destructor invoked, you're probably doing something
> wrong.

[skip]

Agreed! To program with garbage collector you need to use another
approach -- one used in C#/Java.

1. there is not much of value in GC that simply replaces malloc/free
2. every object is trivial, destruction mechanism is replaced by
finalizing mechanism (C#/Java)
3. mixing approaches is dangerous, especially in large projects where
GC supposed to bring relief for developers (see my previous answer to
James up this thread)

Personally I think that 'T* p;' is a HANDLE with value =
'reinterpret_cast<size_t>(p)', this value is used to manage resource
'chunk of memory located somewhere'. GC makes it unnecessary to worry
about its release. People who where using/inventing GC understood that
memory is the only one of myriad possible resources, and you need to
change entire 'resource ownership' paradigm for whole application in
order to use GC naturally and uniformly (by the way what is an object?
it is a HANDLE to resources associated with it, value is object's bit
representation, resource -- is a composition of all subobject's
resources; destructor -- is the way to release those resources). So
they invented Finalization() -- you can not use destructor approach and
Finalize approach in the same app -- these paradigms (is it the right
word?) are opposite.
Unfortunately Finalization mechanism is full of flaws:
-- it is "messy" in comparison with simple and elegant destructors
mechanism (just look at "more detailed arguments"
http://www.hpl.hp.com/techreports/2002/HPL-2002-335.html ;) )
-- you are in trouble whenever you need deterministic resource release
-- early scope leaving (exceptions/return) starts working against you
(whether on stack or during object's construction)
-- finalizers should be ran on another thread (a lot of multithreading
troubles here)

any other problems? There should be a lot of publication on this topic.
I've said a lot about problems in C#/Java approach in my prevoius
posts.

And all those problems for what? To "remove burden of releasing
resources" from developers? The only positive outcomes are 'shared
ownership is easier' and questionable 'speedup due to faster
allocation/deallocation' (you didn't convinced me yet that it is
significantly faster :)).
Given those arguments it is understandable why I call GC as a
'testament to average developer's inability to manage resources'.


Personally I found myself "enlightened" by this long conversation with
you, James and others -- it organized my thoughts in order. After all
these debates I prefer GC approach described in my previous post (i.e.
mixing GC for trivial objects and destructors for non-trivial) could
have very nice usage (but unfortunately could create nasty problems and
overall complicates things).


[skip some arguments that are going to convert C++ into Java :)]

> - The number of "trivial" objects increases drastically, since you
> don't have to worry about the possibility of owning a "nontrivial"
> object. Based on what I've seen usually to > 99%. The number of
> finalizers in well-written Java code is some indication of that.

If you change every opaque object's destruction model to 'finalize
model' it will be so. Java environment does this wrapping files,
handles and so on in classes that could be released with finalization
logic.


> The numbers I've heard from .NET are not, presumably due to a
> different dominant programming style, which I don't yet understand.

No, there is no conceptual/fundamental difference between .NET and
Java. Simply because .NET has to deal with COM, windows and other stuff
that can not be wrapped into 'finalizable' class.


Bye.
Sincerely yours, Michael.

sanjay

unread,
Feb 9, 2005, 5:25:10 PM2/9/05
to
As an average C++ programmer what I really want is the ability to do
other resource management such as closing a file or database connetion.
We can leave the memory management to GC. Till the time I doesn't know
when the file will be closed, Its really bad. So we should separate out
several parts of GC and argue what should be in and what should be out.
Rather than arguing If GC in or GC out.

Thanks,

Sanjay.

Danil Shopyrin

unread,
Feb 9, 2005, 7:50:48 PM2/9/05
to
Hi!

There are lot of wishes and suggestions to C++. Most of them aren't
described well, because they lives in a newsgroups threads. And it's
not a perfect place for them :-).

I suggest to collect ideas in a C++ wiki. See the
http://en.cpp.wikicities.com

--
Danil Shopyrin

John Smith

unread,
Feb 9, 2005, 8:04:09 PM2/9/05
to
sanjay wrote:
> As an average C++ programmer what I really want is the ability to do
> other resource management such as closing a file or database connetion.
> We can leave the memory management to GC. Till the time I doesn't know
> when the file will be closed, Its really bad. So we should separate out
> several parts of GC and argue what should be in and what should be out.
> Rather than arguing If GC in or GC out.

Its bad enough people forgot first principles (so-called) when
non-deterministic memory management became normal. When they are allowed
to not worry about any resources, I dread the day.

Hans Boehm

unread,
Feb 10, 2005, 9:58:45 AM2/10/05
to
Michael Pryhodko wrote:
> Ok, here is what comes to my mind about this that makes object
> 'non-trivial' (my experience):
> 1. any HANDLE to system resources
Yes. Those are typically dynamically rare. I think you can design a
system in which most of those are handled by finalization. Either you
make the resources plentiful enough that a delay is acceptable. Or you
force GC/finalization when you run out. I will admit that in current
systems this can be problematic, for multiple reasons.
...

> 4. any logic that relies on deterministic destruction, i.e. unroll
smth
> if 'enclosing object is destroyed'/'if we leave enclosing scope'.
> Standard C++ features makes it ideal to place such things into
> destructors.
I agree. But most of these objects are typically stack allocated
anyway, and they have trivially understood lifetimes. You clearly want
either destructors, or at least try-finally, to handle such clean-up.
It doesn't have much to do with GC. And these objects typically don't
account for much of the heap.

>
>
> > More fundamentally: This isn't the right approach to programming
> > with a garbage collector. If you need to provide explicit
> > destructors just because you may eventually own an opaque object
> > that needs its destructor invoked, you're probably doing something
> > wrong.
>
> [skip]
>
> Agreed! To program with garbage collector you need to use another
> approach -- one used in C#/Java.
I agree. You end up with a somewhat different style.
>
> ...

> 2. every object is trivial, destruction mechanism is replaced by
> finalizing mechanism (C#/Java)
I'm not sure that's quite the right view. Destructors are in a sense
closer to try-finally in Java.

> 3. mixing approaches is dangerous, especially in large projects where
> GC supposed to bring relief for developers (see my previous answer to
> James up this thread)
I agree that mixing styles within an application is often dangerous.
But I think the question is really whether C++ should support multiple
styles.
> ...

> Unfortunately Finalization mechanism is full of flaws:
> -- it is "messy" in comparison with simple and elegant destructors
> mechanism (just look at "more detailed arguments"
> http://www.hpl.hp.com/techreports/2002/HPL-2002-335.html ;) )
> -- you are in trouble whenever you need deterministic resource
release
> -- early scope leaving (exceptions/return) starts working against you
> (whether on stack or during object's construction)
> -- finalizers should be ran on another thread (a lot of
multithreading
> troubles here)
>
> any other problems? There should be a lot of publication on this
topic.
> I've said a lot about problems in C#/Java approach in my prevoius
> posts.
That's a common reaction to that TR. But I don't think that's quite
the
right interpretation. Basically, this is just a fact of life:
1) People try to avoid explicitly thinking about object deallocation
and clean-up times. In many (not all) applications, it's too hard.
Abstraction or information hiding gets in the way. If they can't use a
tracing GC, they will use something like shared_ptr reference counting.
2) If you don't know precisely when something will happen, you
effectively have to treat it as a concurrent action.
3) Concurrency isn't easy. Therefore cleanup of objects where you've
abstracted away the lifetimes isn't easy. The same is true for
shared_ptr. (If you ignore the synchronous destruction issues with
shared_ptr, things rarely break. There's tons of Java code with
incorrect finalizers that rarely breaks. Early Java implementations
that ran finalizers synchronously during allocation calls rarely broke.
None is correct.)
4) Premature finalization, as described inthe paper, is really an
orthogonal issue. Destructors in C++ are pervasive. Finalizers in
Java are not. C++ made the choice to provide some guarantees about
lifetimes of temporaries and timing of destructor invocation, at the
expense of performance. This removes one set of problems with
destruction based on reference counting that exists with Java
finalization. Java made the other choice for pointer visibility to the
GC, which is the aproximate analog. That choice made sense because
finalizers are rare, and it improves performance. At least
theoretically, you could make either choice in either language.

>
> And all those problems for what? To "remove burden of releasing
> resources" from developers? The only positive outcomes are 'shared
> ownership is easier' and questionable 'speedup due to faster
> allocation/deallocation' (you didn't convinced me yet that it is
> significantly faster :)).
> Given those arguments it is understandable why I call GC as a
> 'testament to average developer's inability to manage resources'.
In my experience, the time saving for the programmer is a big deal for
many applications, and has no impact for others. If memory management
issues prevent you from having a working product in a reasonable time
frame, that clearly matters. If they prevent you from spending another
30% of programmer time on performance optimization, that will probably
have a far greater impact on performance than any direct memory
management overhead.

Hans

ka...@gabi-soft.fr

unread,
Feb 10, 2005, 3:06:14 PM2/10/05
to
Hans Boehm wrote:

> In my experience, the time saving for the programmer is a big
> deal for many applications, and has no impact for others. If
> memory management issues prevent you from having a working
> product in a reasonable time frame, that clearly matters. If
> they prevent you from spending another 30% of programmer time
> on performance optimization, that will probably have a far
> greater impact on performance than any direct memory
> management overhead.

I think this point is worth isolating, because it applies to so
many things, not just garbage collection. Anything you do to
reduce programmer workload improves performance, IF performance
is what you need. In fact, it gives you options: improve
performance, reduce time to market, reduce price, add features,
document the code so that features can be added later... If you
need the performance, you can choose that. If you don't, you
can choose one of the others. Either way, it's a no lose
situation.

And this argument really does apply to anything. Note that it's
really the argument that Thomas Mang brought up in the order of
evaluation thread, for example.

--
James Kanze GABI Software

Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Hans Boehm

unread,
Feb 15, 2005, 6:52:23 PM2/15/05
to

Larry Evans wrote:

> Would it be any help in solving the problem if you had:
>
> descriptor_gc<T>
> descriptor_lock<T>
>

> for each type, T? ...


I'm not sure I understand the suggestion correctly. The problem is
that

- You normally would like to know statically which locks might be
acquired by a given piece of code. If you determine this only
dynamically, it's really too late. You might determine that you need
to reacquire a lock you already own, or that you will need to acquire
them in the wrong order, and thus risk deadlock, but you can't really
do anything about it. You really needed that information when you
designed the code. (You could throw an "I'm about to deadlock"
exception. But it would take a lot of infrastructure to recover from
that.)

- With shared_ptr, the lock acquisitions happen implicitly as part of
an assignment. Checking this at every assignment probably more than
defeats any advantage of automatic memory management.

Hans

Larry Evans

unread,
Feb 16, 2005, 5:47:07 AM2/16/05
to
On 02/15/2005 04:52 PM, Hans Boehm wrote:
> Larry Evans wrote:
>
>>Would it be any help in solving the problem if you had:
[snip]
>> descriptor_lock<T>
[snip]

>
> - You normally would like to know statically which locks might be
> acquired by a given piece of code. If you determine this only

Then, as you've surmised descriptor_lock<T> wouldn't help.

> dynamically, it's really too late. You might determine that you need
> to reacquire a lock you already own, or that you will need to acquire
> them in the wrong order, and thus risk deadlock, but you can't really
> do anything about it. You really needed that information when you
> designed the code. (You could throw an "I'm about to deadlock"
> exception. But it would take a lot of infrastructure to recover from
> that.)

That's what I had in mind. A few times in mozilla, the mozilla locks
up, and I'm guessing it's because of a deadlock. Hence, I thought that
if the programmer's had a descriptor_lock<T>, they might be able to
either recover or at least report the deadlock with useful information
at least partly provided with descriptor_lock.

Sergey P. Derevyago

unread,
Feb 17, 2005, 10:20:35 PM2/17/05
to
Hans Boehm wrote:
> - With shared_ptr, the lock acquisitions happen implicitly as part of
> an assignment. Checking this at every assignment probably more than
> defeats any advantage of automatic memory management.
>
IMHO locking during the refcounting copying is really bad idea.
One can argue that plain refcounting copying is relatively expensive but the
copying that locks IS expensive (and violates the zero overhead rule in some
respect).

Really, sh_ptr-s that are shared between threads are pretty rare. And the
shared data requires so much attension that it's relatively free to look at
the copying issues which are nothing more than yet another ordinary detail...

I believe, boost::shared_ptr violates the "Always endeavor to give each piece
of code-each module, each class, each function-a single, well-defined
responsibility" guideline in so many ways that it's almost unusable. In
particular, I use my own sh_ptr:
http://ders.angen.net/cpp3/intmsg/doc/sh__ptr_8hpp-source.html
--
With all respect, Sergey. http://ders.angen.net/
mailto : ders at skeptik.net

Ben Hutchings

unread,
Feb 19, 2005, 1:24:37 AM2/19/05
to
Sergey P. Derevyago wrote:
> Hans Boehm wrote:
>> - With shared_ptr, the lock acquisitions happen implicitly as part of
>> an assignment. Checking this at every assignment probably more than
>> defeats any advantage of automatic memory management.
>>
> IMHO locking during the refcounting copying is really bad idea.

That's not what Hans Boehm was referring to. Ref-counting can use
lockless atomic operations. However, assignment to the last
shared_ptr (or similar ref-counting pointer) to an object
automatically calls its destructor, which might involve locking.

> One can argue that plain refcounting copying is relatively
> expensive but the copying that locks IS expensive (and violates the
> zero overhead rule in some respect).

What do you mean by the zero overhead rule?

> Really, sh_ptr-s that are shared between threads are pretty
> rare. And the shared data requires so much attension that it's
> relatively free to look at the copying issues which are nothing more
> than yet another ordinary detail...

shared_ptr instances should never be shared between threads. The
original Boost implementation does support multiple threads having
shared_ptrs to the same object, though.

> I believe, boost::shared_ptr violates the "Always endeavor to
> give each piece of code-each module, each class, each function-a
> single, well-defined responsibility" guideline in so many ways that
> it's almost unusable.

It does rather more than the bare minimum, but I don't see that its
responsibility is poorly defined.

> In particular, I use my own sh_ptr:
> http://ders.angen.net/cpp3/intmsg/doc/sh__ptr_8hpp-source.html

--

Ben Hutchings
Having problems with C++ templates? Your questions may be answered by
<http://womble.decadentplace.org.uk/c++/template-faq.html>.

Alexander Terekhov

unread,
Feb 19, 2005, 11:11:44 AM2/19/05
to

Ben Hutchings wrote:
[...]

> shared_ptr instances should never be shared between threads.

They can be shared between threads. Same as with "int" instances
(taking memory isolation for granted). Basic thread safety, y'know.

regards,
alexander.

--
http://www.google.de/groups?selm=4215E150.1856EF9A%40web.de

Sergey P. Derevyago

unread,
Feb 21, 2005, 3:33:12 PM2/21/05
to
Ben Hutchings wrote:
> > IMHO locking during the refcounting copying is really bad idea.
>
> That's not what Hans Boehm was referring to. Ref-counting can use
> lockless atomic operations.
>
Nevertheless, atomic operations do not also come for free.
Most of the time, intensive copying (BTW shared_ptr is designed for intensive
copying) that locks is a sing of a bad design.

> However, assignment to the last
> shared_ptr (or similar ref-counting pointer) to an object
> automatically calls its destructor, which might involve locking.
>

Might but (generally speaking) should not.
Some people think that MT is about locking. While the original idea is MT is
about parallel simultaneous execution.

> > One can argue that plain refcounting copying is relatively
> > expensive but the copying that locks IS expensive (and violates the
> > zero overhead rule in some respect).
>
> What do you mean by the zero overhead rule?
>

In a carefully designed application there is no need for general purpose
refcounting that locks. I.e. this locking is the overhead.


--
With all respect, Sergey. http://ders.angen.net/
mailto : ders at skeptik.net

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

Hans Boehm

unread,
Feb 22, 2005, 4:45:17 PM2/22/05
to
Sergey P. Derevyago wrote:

> Ben Hutchings wrote:
> > However, assignment to the last
> > shared_ptr (or similar ref-counting pointer) to an object
> > automatically calls its destructor, which might involve locking.
> >
> Might but (generally speaking) should not.
> Some people think that MT is about locking. While the original idea
is MT is
> about parallel simultaneous execution.
>
Could you expand on this?

Multithreaded programming is clearly about parallel execution. But
we'd also like to preserve program correctness in the process, which
means we need a way to prevent threads from operating on intermediate,
inconsistent versions of data structures that are being modified by
other threads. That can be done in several ways, but the most
widespread, and currently arguably the most practical for general
purpose use, is by locking. (I think some of the work on transactional
memory may eventually lead to something better, but I don't think we're
quite there yet.)

Destructors are only meaningful if they access data structures other
than the object itself. If you are ever unaware of precisely when a
destructor is executed (e.g. because it's run implicitly as part of an
arbitrary assignment), or if the data structures accessed by a
destructor are shared between threads, you need to lock (or possibly
use a clever lock-free data structure). It may be that the lock is
implicit, and possible benign, because it's hidden inside system
services. But it's still there.

For some applications,memory management is straight-forward enough that
you can avoid all of this. But for the sort of applications I have
dealt with, that was not the common case.

Hans

Sergey P. Derevyago

unread,
Feb 23, 2005, 4:44:19 PM2/23/05
to
Hans Boehm wrote:
> > Some people think that MT is about locking. While the original idea
> > is MT is about parallel simultaneous execution.
> >
> Could you expand on this?
>
Please let me cite a David Butenhof's posting: "multithreading is defined by
an application design that ALLOWS FOR concurrent or simultaneous execution".
Obviously, (excessive) locking does NOT allow for simultaneous execution.

A lot of people are used to write old good ST code and "add some locks to
make it thread-safe" _afterwards_. The realworld result is always frustrating:
the application is terribly slow and doesn't scale.
Sure, we need some synchronization primitives (such as mutexes) to implement
inter-thread communication. As a rule, thoroughly designed MT application is a
set of fully independent threads which communicate via the message queues. The
synchronization primitives are used inside these queues and virtually no locks
are supposed outside the queues.
But what we see in real life is herds of mutexes spread across the code.
"MT-aware classes" try to lock almost every method... And even more, some
"industrial-strength" PLs have the built-in mutex per every object! IMHO there
is no excuse for this madness.

> Multithreaded programming is clearly about parallel execution. But
> we'd also like to preserve program correctness in the process, which
> means we need a way to prevent threads from operating on intermediate,
> inconsistent versions of data structures that are being modified by
> other threads.
>

The data that really has to be modified from several threads simultaneously
is very rare. Yes, I'm talking about properly designed applications.


--
With all respect, Sergey. http://ders.angen.net/
mailto : ders at skeptik.net

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

David Schwartz

unread,
Feb 23, 2005, 6:04:55 PM2/23/05
to

"Sergey P. Derevyago" <non-ex...@iobox.com> wrote in message
news:421C6859...@iobox.com...

> A lot of people are used to write old good ST code and "add some locks to
> make it thread-safe" _afterwards_. The realworld result is always
> frustrating:
> the application is terribly slow and doesn't scale.

That is, if it works at all. Deadlocks often result from not designing a
sensible locking scheme from the beginning. Then again, the "fix" for
deadlocks in programs like this always turns out to be holding locks for
longer and merging locks, resulting in terrible slowness and lack of
scalability.

> But what we see in real life is herds of mutexes spread across the code.
> "MT-aware classes" try to lock almost every method... And even more, some
> "industrial-strength" PLs have the built-in mutex per every object! IMHO
> there
> is no excuse for this madness.

Nope, none. Enforcing order has a cost, and clever programmers enforce
order only when the benefits outweigh the costs. Sometimes the benefits are
performance, sometimes they're simplicity of coding. The most effort should
be focused on the proverbial 20% of the code that's performance-critical.
(Most of that 20% should be in common libraries that you write once and used
dozens of times anyway.)

>> Multithreaded programming is clearly about parallel execution. But
>> we'd also like to preserve program correctness in the process, which
>> means we need a way to prevent threads from operating on intermediate,
>> inconsistent versions of data structures that are being modified by
>> other threads.

> The data that really has to be modified from several threads
> simultaneously
> is very rare. Yes, I'm talking about properly designed applications.

Exactly. This is why you should start your design with the data. The
first question you should ask after "what is my program going to do" is
"what data do I need to represent and manipulate it, and what types of
manipulations will I need to do to it".

DS

Hans Boehm

unread,
Feb 23, 2005, 7:23:09 PM2/23/05
to
Sergey P. Derevyago wrote:

> Sure, we need some synchronization primitives (such as mutexes) to
implement
> inter-thread communication. As a rule, thoroughly designed MT
application is a
> set of fully independent threads which communicate via the message
queues. The
> synchronization primitives are used inside these queues and virtually
no locks
> are supposed outside the queues.

If that's possible, great. For the problems I've dealt with, the
problems usually aren't quite independent. And destructors especially
tend to involve shared data structures, since their job is typically to
return something to a shared resource pool. You can sometimes keep the
resource pools per thread, but that incurs other substantial risks.

> But what we see in real life is herds of mutexes spread across the
code.
> "MT-aware classes" try to lock almost every method...

I agree that's usually a bad idea. Synchronization should be left to
the client unless the library introduces shared data that's not
visible to the client.

> And even more, some
> "industrial-strength" PLs have the built-in mutex per every object!
IMHO there
> is no excuse for this madness.

If you're talking about the Java approach, then the cost of associating
a lock with each object is actually pretty small. It may be a few bits
per object, or a table off to the side. Overusing those locks is of
course not cheap.

Hans

Joe Seigh

unread,
Feb 24, 2005, 4:20:04 PM2/24/05
to
On 23 Feb 2005 16:44:19 -0500, Sergey P. Derevyago <non-ex...@iobox.com> wrote:

> Hans Boehm wrote:
>> > Some people think that MT is about locking. While the original idea
>> > is MT is about parallel simultaneous execution.
>> >
>> Could you expand on this?
>>
> Please let me cite a David Butenhof's posting: "multithreading is defined by
> an application design that ALLOWS FOR concurrent or simultaneous execution".
> Obviously, (excessive) locking does NOT allow for simultaneous execution.
>
> A lot of people are used to write old good ST code and "add some locks to
> make it thread-safe" _afterwards_. The realworld result is always frustrating:
> the application is terribly slow and doesn't scale.
> Sure, we need some synchronization primitives (such as mutexes) to implement
> inter-thread communication. As a rule, thoroughly designed MT application is a
> set of fully independent threads which communicate via the message queues. The
> synchronization primitives are used inside these queues and virtually no locks
> are supposed outside the queues.

Multithreading usage patterns tend to fall into producer/consumer or reader/writer
patterns. Message passing works well with the former but doesn't work well
with the latter. Mutexes don't scale well with the latter by definition.
rwlocks don't scale so well either. Lock-free stuff seems to do quite well.
You can crank up the contention level to 11 which will kill mutex and rwlock
based solutions and the lock-free stuff doesn't even seem to breath heavy.


--
Joe Seigh

Sergey P. Derevyago

unread,
Feb 24, 2005, 4:30:26 PM2/24/05
to
Hans Boehm wrote:
> > As a rule, thoroughly designed MT set of fully independent threads which

> > communicate via the message queues. The synchronization primitives are
> > used inside these queues and virtually no locks are supposed outside the
> > queues.
> >
> If that's possible, great. For the problems I've dealt with, the
> problems usually aren't quite independent. And destructors especially
> tend to involve shared data structures, since their job is typically to
> return something to a shared resource pool. You can sometimes keep the
> resource pools per thread, but that incurs other substantial risks.
>
Well, what "shared data structures" you're talking about? The point is that
in well-designed application the data structures being modified from several
threads simultaneously are pretty rare. That is typical destructor doesn't
modify thread-shared data so it doesn't lock anything.

IMHO if reasonably good "MT-friendly" GC can ever be created for C/C++
languages it doesn't have to be fully transparent and ready to deal with every
possible application design. MT programming is really hard and no one should
assume that such a complex (asynchronous) subsystem can just be thrown into
the application.
This GC must clearly define the contract (w.r.t. the application) and the
application has to be designed for this particular GC from the very beginning.


--
With all respect, Sergey. http://ders.angen.net/
mailto : ders at skeptik.net

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

Hans Boehm

unread,
Feb 24, 2005, 8:10:45 PM2/24/05
to
Joe Seigh wrote:
> Multithreading usage patterns tend to fall into producer/consumer or
reader/writer
> patterns. Message passing works well with the former but doesn't
work well
> with the latter. Mutexes don't scale well with the latter by
definition.
> rwlocks don't scale so well either. Lock-free stuff seems to do
quite well.
> You can crank up the contention level to 11 which will kill mutex and
rwlock
> based solutions and the lock-free stuff doesn't even seem to breath
heavy.
>
In my experience, lock-free solutions are great, especially when
someone else has already dealt with the tricky correctness issues. In
particular, they can safely be used from signal/interrupt handlers,
etc.

But if you look simply at performance, in my experience at least, they
can win or lose, depending on implementation details, hardware
characteristics, etc. For example, I reported a small experiment in
http://portal.acm.org/citation.cfm?doid=1011767.1011774 (or
http://www.hpl.hp.com/techreports/2004/HPL-2004-105.html). Beside the
actual content of the paper, the last section compares a simple
lock-free stack vs. a solution based on simple spin-sleep locks. The
winners are different for a 2xPII/266 and a 4xPPro machine. And that's
pretty similar hardware. (There were also OS differences, but I'm not
sure they matter for this particular comparison. If so, I don't
understand the effect.)

As many people have pointed out, the real problem with contention is
typically that a critical cache line (either holding the lock, or the
lock-free data structure) is transferred back and forth between
processor caches for every access. That's inherently not cheap.
(Inappropriate lock implementations may of course make it much worse,
as you can also see in the above test.)

Hans

Hans Boehm

unread,
Feb 24, 2005, 8:14:19 PM2/24/05
to
Sergey P. Derevyago wrote:
> Hans Boehm wrote:
> > If that's possible, great. For the problems I've dealt with, the
> > problems usually aren't quite independent. And destructors
especially
> > tend to involve shared data structures, since their job is
typically to
> > return something to a shared resource pool. You can sometimes keep
the
> > resource pools per thread, but that incurs other substantial risks.
> >
> Well, what "shared data structures" you're talking about?
What do typical destructors do?

They might deallocate memory, which (inside the allocator
implementation) involves updating a list of available memory. That's
normally shared across threads, so that memory deallocated by one can
be used by another.

They might close file descriptors. That updates shared data structures
in the IO library and the kernel to flush buffers, etc.

A synchronous destructor might close a window, which would update a
shared display of some kind.

In the more interesting cases, it's likely to return some user-defined
resource to a user-maintained pool. If you want to allow reuse of a
resource from one thread in another, that involves a shared
user-defined data structure containing available instances of that
resource.

I find it hard to come up with a destructor that is useful and does not
access any shared data. (Of course the shared data might be in the
kernel.)

Hans

It is loading more messages.
0 new messages