Why Eiffel hasn't met its potential
Author |
Message |
Mike Youn #31 / 53
|
Why Eiffel hasn't met its potential
Quote:
> :Just as with handguns, I find that very few legs are actually > :in danger after the first few times. > : > :All this goes back to the snipped discussion regarding costs of C++ copy > :constructing, versus Eiffel's pass by reference semantics. I don't know that > :we have a clear winner. > Perhaps the person left with 2 legs? :-)
======= :) It grows back. Quote: > The whole thrust of Eiffel is for the language environment to do as much of the > work for you as it possibly can to allow you to concentrate creative effort on > abstracting the problem domain. The developer is saying "Well, I probably can't > do a significantly better job than the compiler/run-time-system (because I know > it has been carefully designed to do the job efficiently), so I willingly > relinquish responsibility for such tasks as optimising calls and memory > management. I have the assurance, at least, that these tasks will be performed > safely which is something I cannot guarrantee myself".
=========== Without getting too heated on the subject, I can't agree with you entirely on this. I find quite a bit of appeal in the guarantees that GC can give you. OTOH, determinism in resource management is a powerful tool when we talk about pragmatics: managing those artifacts of implementation that seemingly will never fade into obscurity. Memory is but one resource issue; GC is not symmetric when it comes to this. If it were, I would, like you, gladly hang my hat up and say the war is won. Even then, I still reserve the right bear arms when the situation warrants. Eiffel doesn't offer this option; C++ does. In regards to qualifying "absolutes" about safety, I have to admit I recently caught one of my "leak-proof" demo programs dripping all over itself. The problem was identified, and in the spirit of iterative refinement, version 3 of ObjectRef<T> is now closer than ever to perfect, efficient protection. :) With the STL and a reference-counting genericized smart-pointer at hand, GC is not necessary at all. In terms of efficiency, I would guess access is at least on par with Eiffel's object ref's. Beyond this, it carries no overhead in terms of sweeping and marking. In addition, it can guard any resource, not just memory, with no or very little additional effort. The advantage is, I can choose to protect resources this way when I deem it appropriate. The disadvantage is, I'm still not sure that I guarded everything that needs to be guarded. Java and Eiffel both have an advantage in this regard: you cannot choose to not protect yourself. The problem with Java and Eiffel is, ... well, you get the idea. (This was the leak I mentioned; my smart pointer implementation was still rather inefficient at the time. I chose to not use it where I should have seen the need to. Now, with reference counting added, I have very little reason not to.) In this corner of the world, victory over resources is at hand. Quote: > If we reckon we can do a much better job ourselves and are willing to accept > our mistakes and are prepared to suffer the consequences, then we wouldn't > bother using Eiffel.
=========== Perhaps. I'm still exploring the ways that Eiffel's object model can prove an advantage over C++ in building the business logic. Its covariance and anchored types appear to be just the things C++ lacks. I haven't forgotten the initial point of this discussion (efficiency, and relative performance). More than ever, I'm looking forward to a quantitative study of *what* Eiffel is doing that can be more efficient than an appropriately coded C++ version. I suspect that one of the earlier posters in this thread hit on the real reason: inefficient copying when references were more appropriate. Mike.
|
Sun, 04 Oct 1998 03:00:00 GMT |
|
|
Mike Youn #32 / 53
|
Why Eiffel hasn't met its potential
Quote:
> But how does a language that uses automatic GC prevent me from doing the same > thing? > x.shut_down; -- release resources, etc. > x := Void; -- let GC pick up the memory when it feels like it. > What's the problem?
=========== One might say the same of memory: delete x; // release memory, no GC needed x = 0; // force safe state One of the problems is with exceptions. Your rescue needs to release any resource allocated, as does your normal execution path. Local objects (somewhat like expanded types in Eiffel) are reliably and predictably destroyed when they leave scope, regardless of presence or absence of exceptions. This quality affords an opportunity to encapsulate allocation/release in the object's behavior. One other reason is simple convenience. The same mechanism is available to reliably release the resource; you might say the object *is* the resource. Its behavior is simple, coherent, and consistent -- those same qualities we look for when deciding what to abstract in the problem domain. The only difference is that this sits entirely in the solution domain. Mike.
|
Sun, 04 Oct 1998 03:00:00 GMT |
|
|
Joachim Durchho #33 / 53
|
Why Eiffel hasn't met its potential
Quote: > Quite often, the "thing" selected has some crisp meaning: opening a file, > changing the cursor shape, beginning a database transaction, or allocating > some memory perhaps. Creation grabs the resource; destruction releases it. > The resource is available so long as the object is accessible. Conversely, > the object is inaccessible after the resource has been released. All code > paths cause allocation/release to happen in pairs.
Heck, why does everybody assume an external resource cannot be freed before the object is garbage collected? As soon as the program knows the file (or whatever) isn't needed anymore, it can use a disconnect() or close() or whatever() call, and the the resource will be freed, at a statically known point in the program flow. To make the programs safe, a finalization method on the controlling memory object should still check wether the resource is allocated, and free it if appropriate. Such an approach would combine the best of both worlds: Explicit deallocation when desired, and garbage collection when you don't care (or forget to deallocate - garbage collection will prevent your system from locking up due to exhausted resources in that case). Quote: > Significantly, at least > one language guarantees order of destruction is opposite that of > construction
This is a much more important issue. There are lots of cases where external resources have to be freed in a certain order (usually opposite order of allocation). (Though I think such prescriptions are bad design on the side of the resource - when I release a resource, I clearly don't want to bother with any sub-resources that I may have allocated. But it's no use groaning about the mean resource designers - the language have to cope with the environment, not the other way round.) Anyway, C++ wins on this - the Eiffel language standard does not have a finalization mechanism (yet, I hope). -Joachim -- Im speaking for myself here.
|
Sun, 04 Oct 1998 03:00:00 GMT |
|
|
Joachim Durchho #34 / 53
|
Why Eiffel hasn't met its potential
Quote: > With the STL and a reference-counting genericized smart-pointer at > hand, GC is not necessary at all. In terms of efficiency, I would > guess access is at least on par with Eiffel's object ref's. Beyond > this, it carries no overhead in terms of sweeping and marking. In > addition, it can guard any resource, not just memory, with no or > very little additional effort.
I don't know what you mean by STL, so I may miss the point. Still, reference counting is not what I'd consider a good solution. First of all, reference counting cannot totally replace GC because it cannot handle reference cycles. Of course, this point isn't valid if the cycles can be broken, e.g. if certain pointers are known to be backpointers that shouldn't increase the reference count. In practice, I have never met a case where I couldn't easily break the cycles this way, but it is easy to imagine cases where this is impossible. But reference counting is also inefficient. Every time a reference variable gets assigned, you have two accesses to the reference count of the referred-to: one to read the old reference count, one to write the increased count. And we're talking memory references here that cannot be easily optimized by a compiler! The same holds when a reference goes away: The routine has to read the old count and write the new decreased one. For a highly variable graph, GC will outrun any reference count solution - provided that the GC algorithm is a decent one. I really think there should be a better way than GC to get rid of unreachable objects - in 99% of all cases it is possible to statically determine when an object will become. (Of course the 99% figure is one of the 67.34% of statistics that are made up <g>.) But I don't think the programmer should be bothered with memory deallocation - this type of work should be done by the optimization phase of a compiler. -Joachim -- Im speaking for myself here.
|
Tue, 06 Oct 1998 03:00:00 GMT |
|
|
Mike Youn #35 / 53
|
Why Eiffel hasn't met its potential
Quote:
> > With the STL and a reference-counting genericized smart-pointer > at > > hand, GC is not necessary at all. In terms of efficiency, I would > > guess access is at least on par with Eiffel's object ref's. Beyond > > this, it carries no overhead in terms of sweeping and marking. In > > addition, it can guard any resource, not just memory, with no or > > very little additional effort. > I don't know what you mean by STL, so I may miss the point.
========== STL is the C++ Standard Template Library, part of the pending standard, properly known as the Standard Library; the reference implementation was written by A. Stepanov at HP Labs. Among other things, it defines a number of collections, various types of iterators, and algorithms that work with the iterators (copy, foreach, etc). It's actually a quite nice example of extension via instantiation in C++; the idioms embodied there should become required study for advanced students. The significance of the STL in this discussion is that it easily fills many of the complex data structure and access needs in most applications. It is usable, offers wide coverage, and is standard (or soon to be), thus forming a portable solution to the needs it addresses. In short, it means we can stop re-inventing the wheel for common data structures and operations. Quote: > Still, reference counting is not what I'd consider a good > solution. > First of all, reference counting cannot totally replace GC > because it cannot handle reference cycles. Of course, this point > isn't valid if the cycles can be broken, e.g. if certain pointers > are known to be backpointers that shouldn't increase the > reference count. In practice, I have never met a case where I > couldn't easily break the cycles this way, but it is easy to > imagine cases where this is impossible.
========== Cycles and backpointers refer back to the re-invented data structures. Backpointers point back to other nodes, which might contain references to your data objects. The container contains a counted reference to your data object. No cycles or backpointers are involved at this level. Quote: > But reference counting is also inefficient. Every time a > reference variable gets assigned, you have two accesses to the > reference count of the referred-to: one to read the old reference > count, one to write the increased count. And we're talking memory > references here that cannot be easily optimized by a compiler! > The same holds when a reference goes away: The routine has to > read the old count and write the new decreased one.
=========== Quite true. Assignment is somewhat rare compared to use, though. (Statistically speaking, that is. :) Also, it's difficult for me to imagine a GC scheme where reference counting is not used. If you know of specifics on one, I'd be very interested to hear it. Quote: > For a highly variable graph, GC will outrun any reference count > solution - provided that the GC algorithm is a decent one.
========= Again, I'm assuming that GC similarly requires a reference count. Whether it marks and sweeps indepedently, or simply deallocates immediately when count reaches zero, is unspecified. I haven't looked much at GC science since looking at Smalltalk's, 8 years ago. My implementation for generic reference counting is about 120 lines of very simple C++ code. Any resource type can be "garbage collected" by providing deallocation and management functions, written once for each resource type. For normal heap memory objects, this amounted to 10 lines of code. Protection for file handles and mutex's were also implemented in about 10 lines of code each. Very few resource types will require something much more complex. Quote: > I really think there should be a better way than GC to get rid of > unreachable objects - in 99% of all cases it is possible to > statically determine when an object will become. (Of course the > 99% figure is one of the 67.34% of statistics that are made up > <g>.) But I don't think the programmer should be bothered with > memory deallocation - this type of work should be done by the > optimization phase of a compiler.
========== I agree in principle, and extend that to include all resource types, not just memory on the heap. I think I took matters in the opposite direction: instead of asking the compiler to treat all resources as it does memory, I simply treated memory as I would any other resource. Those same mechanisms that worked to guard everything else can also guard memory. In fact, I count on the statically known properties of C++ memory management to accomplish this. The "close to metal" property of C++ allows me to do this quite easily and naturally. (Closure on the article that started this thread. This is not the same as saying I don't find anything to like in Eiffel.) Mike.
|
Wed, 07 Oct 1998 03:00:00 GMT |
|
|
Steve Tyn #36 / 53
|
The myth of C++ efficiency (reaffirmed! :-)
| Anyway, C++ wins on this - the Eiffel language standard does not | have a finalization mechanism (yet, I hope). This is incorrect: the ELKS (Eiffel Library Kernel Standard) specifies a class MEMORY with a feature `dispose' which, if redefined, is called when the garbage collector decides to "free" an object. This sort of finalization has been part of the TowerEiffel and ISE implementations from the very beginning. In June 1995, TowerEiffel 1.5.0 introduced an alternate type of finalization mechanism (see our MEMORY class, feature `set_global_dispose') which is called when _any_ Eiffel object is collected. At least one customer is using this sort of finalization for managing a persistent store (database handle <-> Eiffel object map). =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= Never express yourself more clearly than you think. -- Neils Bohr
Tower Technology WWW: http://www.twr.com/
|
Wed, 07 Oct 1998 03:00:00 GMT |
|
|
TAURA Kenji #37 / 53
|
The myth of C++ efficiency (reaffirmed! :-)
> > With the STL and a reference-counting genericized smart-pointer at > hand, GC is not necessary at all. In terms of efficiency, I would > guess access is at least on par with Eiffel's object ref's. Very surprising opinion. With tracing type of GCs, accessing an object is just a pointer dereference and no special operation is needed when passing object refs. Smart pointer schemes require an indirection on accessing objects and expensive arithmetics on duplicating references. > Beyond > this, it carries no overhead in terms of sweeping and marking. On the other hand, tracing collectors carry no overhead in terms of reference count manipulation. Well, there are certainly pros and cons on both sides. With garbage collectors, your program can be much safer with smaller efforts. On the other hand, with manual storage allocation, you do not have to worry about unintended resource retention caused by conservatism of a particular GC implementation. We must not try to overly generalize; discussions like "GC is not necessary at all" or "Manual storage management is too dangerous" are hardly fruitful. I would like to see more case studies. There is an interesting case study here: Benjamin Zorn The Measured Cost of Conservative Garbage Collection Software---Practice and Experience, vol 23(7) pp733--756 (July 1993) > > I haven't forgotten the initial point of this discussion > (efficiency, and relative performance). More than ever, I'm looking > forward to a quantitative study of *what* Eiffel is doing that can > be more efficient than an appropriately coded C++ version. GC is certainly one of the possibilities. Tracing collectors are usually faster than reference counting (even if the reference count is appropriately coded in C++). I am looking forward to a quantitative study on your statement about efficiency of smart pointers. I strongly recommend you to take a look at the Boehm's conservative collector (ftp://parcftp.xerox.com/pub/gc) and have a comparison with your smart pointer schemes. You don't have to rewrite your things in Eiffel. Just link a good collector with your C++ programs. -- Kenjiro Taura Yonezawa Laboratory, Department of Information Science Tokyo University
|
Fri, 09 Oct 1998 03:00:00 GMT |
|
|
Jean-Marc Jezequ #38 / 53
|
The myth of C++ efficiency (reaffirmed! :-)
Quote: >> Actually, Eiffel generics are simpler to compile than C++ templates, and allows for both >> shared and efficient code to be produced in most cases. >> Clearly, if the actual generic is a reference type, there is no problem, and all generic code >> can be shared. Now if the actual generic is an expanded type, things get a little bit more >> complex, but not too much. If the expanded object has the same size as a reference >> (mostly usual on 32 bits architectures), then again the same generic code can be reused. >This is not quite correct. Example: >class A [G] >feature > g: G > proc (a: G) is > do > g := a -- * > end >end -- class A >In the marked line proc has to call copy in case G is expanded and assign references >otherwise.
yes, but it does not contradict what I said before. The instruction marked --* can be compiled to the very same C code: g = a; whether or not actual for G is of a reference or of an expanded (value) type. Then it's up to the C compiler to produce the same code or not depending on the target computer. Basically, if sizeof(actual for G) <= sizeof(register), there's no problem. In practice this allows a fair amount of code to be shared. --- Jean-Marc Jezequel | Tel : +81 (3) 3812-2111 ext. 4116 IRISA/CNRS, currently visiting: | Fax : +81 (3) 5689-4365
The University of Tokyo | http://www.irisa.fr/pampa/PROF/jmj.html Hongo Bunkyo-Ku, Tokyo 113, JAPAN
|
Fri, 09 Oct 1998 03:00:00 GMT |
|
|
Zenger Christo #39 / 53
|
The myth of C++ efficiency (reaffirmed! :-)
Quote:
> >> Actually, Eiffel generics are simpler to compile than C++ templates, and allows for both > >> shared and efficient code to be produced in most cases. > >> Clearly, if the actual generic is a reference type, there is no problem, and all generic code > >> can be shared. Now if the actual generic is an expanded type, things get a little bit more > >> complex, but not too much. If the expanded object has the same size as a reference > >> (mostly usual on 32 bits architectures), then again the same generic code can be reused. > >This is not quite correct. Example: > >class A [G] > >feature > > g: G > > proc (a: G) is > > do > > g := a -- * > > end > >end -- class A > yes, but it does not contradict what I said before. > The instruction marked --* can be compiled to the very same C code: > g = a; > whether or not actual for G is of a reference or of an expanded (value) type. > Then it's up to the C compiler to produce the same code or not depending on the target > computer. Basically, if sizeof(actual for G) <= sizeof(register), there's no problem.
I was wrong. I thought, expanded assignment calls copy, but it ``calls'' standard_copy, for which the C assignment is OK. Still, I'm not quite sure, whether you're right in general. Let's change the example a bit: class A [G] feature g: ANY -- here I changed to ANY proc (a: G) is do g := a -- * end end -- class A Now we have standard_clone for G expanded, which should allocate heap-storage, but a simple reference assignment for G referential.
|
Fri, 09 Oct 1998 03:00:00 GMT |
|
|
Don Harris #40 / 53
|
The myth of C++ efficiency (reaffirmed! :-)
Quote: Jon S Anthony writes:
: Quote: :> Mike Young writes:
:> :> [...] :> :> :... Ada has what I think is :> :the most rational, model: it tracks call nesting to determine if passing a :> :reference might be unsafe. In almost all cases, the nesting count adequately :> :protects the programmer while allowing reasonable use. : :> Creating a dangling pointer or an object referenced by nothing is a :> piece of cake in Ada. Here is an example of the former courtesy of :> Bob Duff from a thread in c.l.a.: :>... :> X: Some_Pointer := new Whatever; :> Y: Some_Pointer := X; :> ... :> Free(Y); :> ... -- Now, X is a dangling pointer, and you better not say X.all. : :Ah, Don. This is not the point. The two things being compared are :apples and oranges. Mike is correctly refering to the fact that Ada :ensures that passing a reference is safe. I'm sure it's true. : Bob's example is about how :you _can_ create a dangling pointer in Ada. Which is broken whether :you pass the thing somewhere or not. And passing a pointer (by value) :is not the same as passing by reference. People often confuse these - :as you appear to have. So, Mike's point stands and your point is :analogous to, say, a bug in your GC where it effectively does the :erroneous Free(Y) _for you_. Of course, that latter case is a lot :more "horrifying"... It was a little off-topic but the two issues are opposite sides of the same coin: mismanaged reference semantics. a) dangling references - reference without objects, and b) memory leaks - objects without references. It's more difficult to have either in Eiffel (assuming the GC works). :/Jon :-- :Jon Anthony :Organon Motives, Inc. :1 Williston Road, Suite 4 :Belmont, MA 02178 : :617.484.3383
: Don.
|
Sat, 10 Oct 1998 03:00:00 GMT |
|
|
Don Harris #41 / 53
|
The myth of C++ efficiency (reaffirmed! :-)
Quote: Mike Young writes:
[...] :> The whole thrust of Eiffel is for the language environment to do as much of the :> work for you as it possibly can to allow you to concentrate creative effort on :> abstracting the problem domain. The developer is saying "Well, I probably can't :> do a significantly better job than the compiler/run-time-system (because I know :> it has been carefully designed to do the job efficiently), so I willingly :> relinquish responsibility for such tasks as optimising calls and memory :> management. I have the assurance, at least, that these tasks will be performed :> safely which is something I cannot guarrantee myself". : :=========== :Without getting too heated on the subject, I can't agree with you :entirely on this. I find quite a bit of appeal in the guarantees :that GC can give you. OTOH, determinism in resource management is a :powerful tool when we talk about pragmatics: managing those :artifacts of implementation that seemingly will never fade into :obscurity. In a sequential language, we might reasonably worry about determinism but in in a concurrent one, it loses it's significance. You don't know exactly what will execute when. Instead, (in Ada, at least), you prioritise threads so the less important ones are allowed to run after the more important threads have had a go and are blocked, waiting for an event to occur before they resume. Note that the event required to trigger resumption of a higher priority thread may be the completion of work performed by the lower priority one. This is the most efficient way of managing things, IMO, and how GC should work. The GC is just such a low priority thread and executes at the most opportune time to cause minimum interference to the application proper. With such an approach, collection proceeds in a non-deterministic but optimal way. Compared with such a scheme, manual deallocation would be less efficient, IMO. Note that although Eiffel is not yet concurrent explicitly, it is concurrent behind the scenes and we might expect that implementations would perform GC at appropriate windows of opportunity such as waiting on user input etc. : :Mike. Don.
|
Sun, 11 Oct 1998 03:00:00 GMT |
|
|
Joachim Durchho #42 / 53
|
The myth of C++ efficiency (reaffirmed! :-)
Quote: > | Anyway, C++ wins on this - the Eiffel language standard does not > | have a finalization mechanism (yet, I hope). > This is incorrect: the ELKS (Eiffel Library Kernel Standard) specifies > a class MEMORY with a feature `dispose' which, if redefined, is called > when the garbage collector decides to "free" an object.
Oops - I stand corrected. BTW where can I find the ELKS, preferably on the Net? (Seems like every good Eiffelist should have OOSC, ETL, *and* ELKS <g>...) -Joachim -- Im speaking for myself here.
|
Sun, 11 Oct 1998 03:00:00 GMT |
|
|
|