home *** CD-ROM | disk | FTP | other *** search
/ NetNews Usenet Archive 1992 #18 / NN_1992_18.iso / spool / comp / lang / cplus / 12367 < prev    next >
Encoding:
Internet Message Format  |  1992-08-14  |  14.1 KB

  1. Path: sparky!uunet!synaptx!synaptics.com!daveg
  2. From: daveg@synaptics.com (Dave Gillespie)
  3. Newsgroups: comp.lang.c++
  4. Subject: Re: Garbage Collection for C++
  5. Message-ID: <DAVEG.92Aug14194411@synaptx.synaptics.com>
  6. Date: 15 Aug 92 02:44:11 GMT
  7. References: <1992Aug6.014619.2111@ucc.su.OZ.AU>
  8.     <DAVEG.92Aug13025629@synaptx.synaptics.com>
  9.     <1992Aug14.021547.15215@news.mentorg.com>
  10. Sender: daveg@synaptx.Synaptics.Com
  11. Organization: Synaptics, Inc., San Jose, CA
  12. Lines: 277
  13. In-reply-to: bcannard@hppcb36.mentorg.com's message of 14 Aug 92 02:15:47 GMT
  14.  
  15. In article <1992Aug14.021547.15215@news.mentorg.com> bcannard@hppcb36.mentorg.com (Bob Cannard @ PCB x5565) writes:
  16. > In article <DAVEG.92Aug13025629@synaptx.synaptics.com>, daveg@synaptics.com (Dave Gillespie) writes:
  17. > |> Couldn't we get away with having a garbage collector that didn't
  18. > |> need pointers to be declared `GC'?  The only thing that would need
  19. > |> special `GC' treatment would be "new" [...]
  20. > I can't see that this is viable given the basic constraints on C++: among
  21. > other things, those who don't want GC shouldn't have to suffer the hits.
  22. > Marking classes or pointers (as appropriate, depending on the GC
  23. > implementation) means the compiler knows about any extra treatment it has
  24. > to give to those pointers/objects, and can leave it out for "ordinary"
  25. > code.
  26.  
  27. The "GCnew" approach means you're marking objects (rather than classes
  28. or pointers) as GC-able.  It's still true that non-GC-allocated objects
  29. would work just like they always did.
  30.  
  31. > In particular, in my own application - which will eventually be quite a big
  32. > program - I only want GC for certain classes, where the problem of determining
  33. > when an object must be deleted has no sensible solution other than GC. Yet
  34. > performance (both run time and memory use) carries a high premium, so I can't
  35. > afford to have GC everywhere.
  36.  
  37. So you use "GCnew" to allocate objects of those classes, and "new"
  38. elsewhere.
  39.  
  40. The "extra treatment" that GC classes would need basically involves
  41. adding some kind of tag information.  If you declare an entire class
  42. to be GC, it makes sense to put such a tag in every instance of the
  43. class.  If you instead work by allocating regular classes in a
  44. special GC-able way, then it makes more sense to put the tag only
  45. in instances of the class that are in the GC heap.
  46.  
  47. I still think declaring pointers to be GC would be too much of a
  48. hassle for the programmer, so we're left having to assume all
  49. pointers may point to GC-allocated objects.  Without adding lots of
  50. tags in places that make C compatiblity awkward, we're left with a
  51. completely "conservative", i.e., brute-force GC algorithm.  We scan
  52. every word on the stack, every word in static storage, and every
  53. word on the non-GC heap for things that look like GC pointers.
  54. (Since this is slow, a good optimization might be to separate the
  55. static area and possibly the non-GC heap into things-which-contain-
  56. pointers and things-which-don't, like massive arrays of doubles
  57. which would be a waste of time to scan.)
  58.  
  59. The decision of which static area or which non-GC heap a thing
  60. should go in can be made at compile-time.
  61.  
  62. In the GC heap, we'd only scan the objects that have been marked
  63. (just like classic mark-and-sweep GC's do).  If the allocator has
  64. recorded the size of an object (which most C allocators do), then
  65. it's easy to tell the range of words that need to be scanned.  An
  66. optimization here would be to add a tag next to the object which
  67. identifies which words inside it are pointers.
  68.  
  69. If you have "GCnew"d an array of objects, the whole array gets a
  70. single tag.  The array must be marked and swept as a whole anyway,
  71. because of pointer arithmetic.
  72.  
  73. To the user this scheme would feel exactly like C++ does now, with
  74. no loopholes as far as I can tell.  The only change is that you
  75. are allowed to say "GCnew" instead of "new", on any kind of data,
  76. in which case that data gets deleted automatically as soon as there
  77. are no pointers of any kind to it left.  Very clean, very simple
  78. (at least where it counts---to the C++ user).
  79.  
  80. > |> Some Lisp systems, such as Kyoto Common Lisp, work this way.  Its
  81. > |> garbage collector scans the whole stack as "raw memory," looking
  82. > |> for 32-bit words that could be pointers to GC-able objects.  It
  83. > |> allocates GC-able objects in such a way that it is relatively
  84. > |> easy to tell if a given memory address points to a GC-able object
  85. > |> or not.
  86.  
  87. > Exactly what I mean. If the GC has to scan the whole stack (and all the
  88. > statics and globals while we're at it), it's doing too much work.
  89.  
  90. This is exactly what KCL does now.  Stacks tend not to be all that
  91. large in practice, especially if you have C++ vector classes rather
  92. than actual local arrays.
  93.  
  94. > The reference-count solution is more efficient than that, because the need
  95. > for GC is restricted to a small but important part of the program. The
  96. > idea of languages like C++ is to give the compiler the information it
  97. > needs to get the best performance out of the resulting code.
  98.  
  99. I wasn't thinking about reference counting solutions at all.  I agree
  100. that for reference counting you'd need a special kind of class
  101. declaration.
  102.  
  103. I think people have compared the performance and found that GC, even
  104. simple, stupid GC, comes out ahead of reference counting.  The overhead
  105. of all those increments and decrements is enormous (relatively speaking).
  106. Linked list traversals that used to involve one memory read per step now
  107. involve several reads and writes, for example.
  108.  
  109. Pointers to reference-counted objects would be messy because of
  110. pointer arithmetic.  An array as a whole ought to have a reference
  111. count (not the individual things inside it), but a pointer can
  112. point anywhere in the middle of it.
  113.  
  114. Also, reference counting has the flaw that it can't deal with circular
  115. structures.  If A points to B, and B points to A, they both have a
  116. reference count of 1 and so will get collected, even if nothing
  117. else in the program points to either of them.  (Remember, even if
  118. your programs don't use circular structures, many programs do, and
  119. a GC solution that's part of the language will be used by those
  120. people too.)
  121.  
  122. > |> This may sound slow, but actually I've found KCL's garbage
  123. > |> collector to be quite fast.
  124.  
  125. > Quite fast compared to what? In previous projects I've been involved with
  126. > (using C rather than C++) "malloc" and "free" were found to be too slow,
  127. > and were replaced by custom ones. I want this GC to clip along as fast as
  128. > possible.
  129.  
  130. You're sort of comparing apples to oranges here.  On the one hand,
  131. there's the speed of a "new" operation; on the other hand, there's
  132. the speed of the GC.  Especially if your language allows explicit
  133. "delete" for the common case that you know an object is garbage, GC
  134. will happen much more rarely than "new".
  135.  
  136. I don't think my "GCnew" has to be any slower than "new".
  137.  
  138. I was comparing KCL's GC to other garbage collectors I've seen, such
  139. as GNU Emacs, Lucid Lisp, and Allegro Lisp.  I haven't done a careful
  140. race of all four, but KCL "felt" nice and fast, and certainly was not
  141. noticeably slower than the others in casual use.
  142.  
  143. > |> The vital question is, can a scheme like this be made to work on
  144. > |> all architectures on which C++ could reasonably be expected to be
  145. > |> used?
  146.  
  147. > If it can be made to work on one common architecture, it can probably be
  148. > tweaked for all.
  149.  
  150. I'm not so sure.  Consider a related example: making tagged pointers.
  151. (Note: this is *not* a part of the GC system I proposed above!)
  152. Most Lisps put some minimal tag information in the bottom two
  153. bits of a 32-bit pointer.  Since word accesses to non-word-aligned
  154. addresses are slow or illegal on most architectures, these Lisps
  155. allocate everything on a multiple-of-four boundary so that the bottom
  156. two bits of a pointers are sitting around doing nothing.  Why not put
  157. tag bits there!  That's just what they do, and it works fine for them,
  158. but that's not a suitable thing to write into a language standard.
  159. The Lisp standard can be implemented in other ways just as well.
  160.  
  161. Languages like C++ and Lisp get ported to all kinds of gonzo architectures
  162. that you and I have never heard of.  Having the low two bits available
  163. to play with may not be a safe assumption for those machines.
  164.  
  165. We have to be sure we don't write anything into a proposed GC system
  166. for C++ that can only be implemented by a feature like low-two-bits
  167. tags, which could make it difficult to port C++ to unusual machines.
  168.  
  169. Another example:  Machine architectures have changed a lot over the
  170. lifetime of C.  Back then, it was fashionable to allow word accesses
  171. to odd addresses (e.g., VAX, 68020, 8086).  Suppose the ANSI C
  172. committee had put something into the language that *depended* on
  173. fast odd-word accesses?  We'd be hosed now, because current fashion
  174. (ahem, scientific design) has the fastest machines treating odd
  175. word accesses as an error.
  176.  
  177. > The vital question is, would it be useable?
  178.  
  179. That's the question that is so vital, it goes without saying.  :-)
  180. Except that experience shows it's probably a good idea to say it
  181. anyway, and on a regular basis.  :-(
  182.  
  183. > |> I think you'd have to live with no compaction
  184. > |> at all:  Whereas it is fairly benign to mark an object because
  185. > |> an integer happened to hold its address, you wouldn't want to adjust
  186. > |> that integer when you moved the object!  You'd need special GC
  187. > |> pointers if you wanted compaction, and those would probably not
  188. > |> be practical.
  189.  
  190. > This is why special GC pointers have been proposed in the first place.
  191. > If GC is in use, the programmer must accept the "no clever tricks"
  192. > constraint, which includes not passing pointers around disguised as
  193. > integers.
  194.  
  195. And, "no passing pointers around to Xlib, because Xlib was written
  196. before GC pointers were invented."
  197.  
  198. > And once that constraint is accepted, there may be no good
  199. > reason for not having compaction.
  200.  
  201. I kind of like the way KCL does it, which is to compact only specific
  202. kinds of things (i.e., array data), and to make pointers to *compactible*
  203. things be special.  That's not such a nuisance; a C++ vector class, for
  204. example, would not be compactible but the data vector it allocates and
  205. keeps inside itself could be.  The accesses to that compactible thing
  206. would all be nicely localized inside the class, so it's okay if the
  207. pointers to it are a bit more linguistically awkward.
  208.  
  209. It would be a shame not to be able to compact strings, though.
  210. There are lots of existing libraries with non-compactible arguments
  211. and local variables that point to strings.  What if you pass a pointer
  212. to a compactible string into "printf", and a GC occurs during the call
  213. which moves the string?  "printf" hasn't declared its argument as a GC
  214. pointer or compactible pointer, so it loses.
  215.  
  216. You might be able to squeak out of it by noting that "printf" is not
  217. going to invoke a GC since only "GCnew" calls can do that, and "printf",
  218. being non-GC-aware, will not call "GCnew".  The only two remaining
  219. tricky points then are existing functions with callbacks to code that
  220. can do a GC (like X toolkits, say), and signal handlers that invoke
  221. a GC (which might have to be forbidden for all sorts of other reasons).
  222.  
  223. > |> [...] suppose you have a GC class which contains a string (i.e.,
  224. > |> a pointer to an old-fashioned non-GC'd array of chars).  Suppose you
  225. > |> no longer have any pointers a certain object of this class, but you
  226. > |> still do have a pointer to its component string.  You lose, if the
  227. > |> destructor for the class deletes the string (which it pretty much has
  228. > |> to do).
  229.  
  230. > What you have described is a programming error. If a collectable object
  231. > contains such an old-style pointer, it is the programmer's responsibility
  232. > to ensure that the object will not be deleted as long as it is needed.
  233.  
  234. That makes sense.  As long as all types, including, say, char arrays,
  235. can be GC-able, then it's perfectly reasonable to expect this.
  236.  
  237. > This in no way prevents us from having old style pointers in GC objects:
  238. > it's no different from the existing situation where a class contains a
  239. > pointer to a string.
  240.  
  241. This is the old when-are-temporaries-deleted issue.  At least there it
  242. can be resolved in some definitive way, by specifying in the language
  243. when temporaries are deleted (say, only on statement boundaries or
  244. whatever).  With GC you don't have this luxury because GC's happen so
  245. unpredictably.  (Especially if signal handlers can invoke GC!)
  246.  
  247. > |>   GC objects can be deleted, in which case any remaining pointers
  248. > |>   to the object become invalid and must not be used to dereference
  249. > |>   an object.
  250.  
  251. > This would accord with existing C++ philosophy, but I don't see how
  252. > you could make a GC that's immune to dangling pointers. Anyone out
  253. > there know otherwise?
  254.  
  255. A "conservative" GC pretty much *has* to be immune to dangling pointers.
  256.  
  257. > |> What if a pointer to a GC-able object is passed to foreign (non-C++)
  258. > |> code, which stashes it somewhere the C++ collector doesn't know to
  259. > |> look?  (All garbage-collecting languages have this problem, and as
  260. > |> far as I know all they can do is shrug and warn programmers not to
  261. > |> let go of the last C++ reference to an object if they know there
  262. > |> might be non-C++ references still lurking around.)
  263.  
  264. > Hence the proposal for tagging GC-able objects, and having something
  265. > comparable to "const" which declares "I will not do any clever tricks
  266. > with this pointer, including converting it to an integer, performing
  267. > pointer arithmetic on it, storing it, etc". This only needs a change
  268. > to the C++ declarations of library functions, not to the functions
  269. > themselves. IMO this makes it feasible to mix GC and libraries.
  270.  
  271. I don't see how either tagging or declaring things helps here.  I
  272. didn't mean "what if some novice incorrectly stashes away a pointer
  273. to a GC object," I meant "what if someone *needs* to pass a pointer
  274. to a GC object to a function which will stash it away where the GC
  275. can't find it."
  276.  
  277. This is an issue even for the brute-force conservative GC I outlined
  278. above:  Suppose someone maps a segment of shared memory at some
  279. address range which the GC wasn't expecting to exist, and puts the
  280. only pointer to an object there?
  281.  
  282. At least if you aren't compacting anything, this won't hurt as long
  283. as that person also keeps at least one pointer to the object in
  284. regular GC-scanned memory.
  285.  
  286.                                 -- Dave
  287. --
  288. Dave Gillespie
  289.   daveg@synaptics.com, uunet!synaptx!daveg
  290.   or: daveg@csvax.cs.caltech.edu
  291.