What about garbage collection?


(Félix Cloutier) #1

Has there been a garbage collection thread so far? I understand that reference counting vs. garbage collection can be a heated debate, but it might be relevant to have it.

It seems to me that the two principal upsides of reference counting are that destruction is (essentially) deterministic and performance is more easily predicted. However, it comes with many downsides:

object references are expensive to update
object references cannot be atomically updated
heap fragmentation
the closure capture syntax uses up an unreasonable amount of mindshare just because of [weak self]

Since Swift doesn't expose memory management operations outside of `autoreleasepool`, it seems to me that you could just drop in a garbage collector instead of reference counting and it would work (for most purposes).

Has a GC been considered at all?

Félix


(Joe Groff) #2

While true in theory, code that relies on destructors to clean up unmanaged resources does not port cleanly to GC as-is in practice. GC of course has its own drawbacks. Heap scanning is expensive, thrashing cache and burning battery. GCs also require higher memory ceiling proportional to the amount of heap actively being used, and GCs suitable for interactive use tend to increase responsiveness at the cost of higher memory use, which has its own second-order energy costs—devices need more RAM, and spend more time swapping or killing, and thus need bigger batteries to refresh all that RAM. ARC interoperates better with unmanaged resources, both non-memory resources like sockets and files and also C-level memory resources. The ARC optimizer and Swift's calling convention also optimize toward reclaiming resources closer to their last use, keeping resource usage low.

-Joe

···

On Feb 8, 2016, at 11:56 AM, Félix Cloutier via swift-evolution <swift-evolution@swift.org> wrote:

Has there been a garbage collection thread so far? I understand that reference counting vs. garbage collection can be a heated debate, but it might be relevant to have it.

It seems to me that the two principal upsides of reference counting are that destruction is (essentially) deterministic and performance is more easily predicted. However, it comes with many downsides:

object references are expensive to update
object references cannot be atomically updated
heap fragmentation
the closure capture syntax uses up an unreasonable amount of mindshare just because of [weak self]

Since Swift doesn't expose memory management operations outside of `autoreleasepool`, it seems to me that you could just drop in a garbage collector instead of reference counting and it would work (for most purposes).


(Jonathan Tang) #3

I would imagine that the biggest consideration is compatibility with
existing class libraries. It's notoriously difficult to integrate
libraries with different memory management systems together, whether it be
GC + refcounting (Erlang binaries, Java NIO), GC + malloc (JNI, V8
embedding), or refcounting + malloc (Python, ARC/C libraries). The
ownership conventions have to become part of every function & data type
that's exposed across the boundary, and oftentimes you need to copy the
whole memory block from one heap to another. Refcounting seems to
integrate with manual memory management a bit more easily than GC does, and
of course many existing class libraries already use ARC.

Also, anyone interested in debating GC vs. refcounting should read this
paper, which argues that they are in fact duals and most production systems
(generational collectors, train algorithm, cycle detection in refcounts)
are hybrids of the two:

http://researcher.watson.ibm.com/researcher/files/us-bacon/Bacon04Unified.pdf

···

On Mon, Feb 8, 2016 at 12:11 PM, Joe Groff via swift-evolution < swift-evolution@swift.org> wrote:

On Feb 8, 2016, at 11:56 AM, Félix Cloutier via swift-evolution < > swift-evolution@swift.org> wrote:

Has there been a garbage collection thread so far? I understand that
reference counting vs. garbage collection can be a heated debate, but it
might be relevant to have it.

It seems to me that the two principal upsides of reference counting are
that destruction is (essentially) deterministic and performance is more
easily predicted. However, it comes with many downsides:

   - object references are expensive to update
   - object references cannot be atomically updated
   - heap fragmentation
   - the closure capture syntax uses up an unreasonable amount of
   mindshare just because of [weak self]

Since Swift doesn't expose memory management operations outside of
`autoreleasepool`, it seems to me that you could just drop in a garbage
collector instead of reference counting and it would work (for most
purposes).

While true in theory, code that relies on destructors to clean up
unmanaged resources does not port cleanly to GC as-is in practice. GC of
course has its own drawbacks. Heap scanning is expensive, thrashing cache
and burning battery. GCs also require higher memory ceiling proportional to
the amount of heap actively being used, and GCs suitable for interactive
use tend to increase responsiveness at the cost of higher memory use, which
has its own second-order energy costs—devices need more RAM, and spend more
time swapping or killing, and thus need bigger batteries to refresh all
that RAM. ARC interoperates better with unmanaged resources, both
non-memory resources like sockets and files and also C-level memory
resources. The ARC optimizer and Swift's calling convention also optimize
toward reclaiming resources closer to their last use, keeping resource
usage low.


(Austin Zheng) #4

(Sorry, Felix; I screwed up my original email and didn't send it to the
list.)

Having the option of a GC would be interesting, but one consideration among
many: how would it work with the COW value type collections? It seems those
would rule out a tracing garbage collector in practice, and the only 'real'
option would be RC + a cycle detector?

···

On Mon, Feb 8, 2016 at 11:56 AM, Félix Cloutier <swift-evolution@swift.org> wrote:

Has there been a garbage collection thread so far? I understand that
reference counting vs. garbage collection can be a heated debate, but it
might be relevant to have it.

It seems to me that the two principal upsides of reference counting are
that destruction is (essentially) deterministic and performance is more
easily predicted. However, it comes with many downsides:

   - object references are expensive to update
   - object references cannot be atomically updated
   - heap fragmentation
   - the closure capture syntax uses up an unreasonable amount of
   mindshare just because of [weak self]

Since Swift doesn't expose memory management operations outside of
`autoreleasepool`, it seems to me that you could just drop in a garbage
collector instead of reference counting and it would work (for most
purposes).

Has a GC been considered at all?

Félix

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Dave Abrahams) #5

Yes. Among other problems, you can't do copy-on-write efficiently with
a GC, because you can't detect a unique reference. And without
efficient copy-on-write, most interesting value types (Array) are out
the window.

···

on Mon Feb 08 2016, Félix Cloutier <swift-evolution@swift.org> wrote:

Has there been a garbage collection thread so far? I understand that
reference counting vs. garbage collection can be a heated debate, but
it might be relevant to have it.

It seems to me that the two principal upsides of reference counting
are that destruction is (essentially) deterministic and performance is
more easily predicted. However, it comes with many downsides:

object references are expensive to update
object references cannot be atomically updated
heap fragmentation
the closure capture syntax uses up an unreasonable amount of mindshare
just because of [weak self]

Since Swift doesn't expose memory management operations outside of
`autoreleasepool`, it seems to me that you could just drop in a
garbage collector instead of reference counting and it would work (for
most purposes).

Has a GC been considered at all?

--
-Dave


(Chris Lattner) #6

Has there been a garbage collection thread so far? I understand that reference counting vs. garbage collection can be a heated debate, but it might be relevant to have it.

Technically speaking, reference counting is a form of garbage collection, but I get what you mean. Since there are multiple forms of GC, I'll assume that you mean a generational mark and sweep algorithm like you’d see in a Java implementation.

It seems to me that the two principal upsides of reference counting are that destruction is (essentially) deterministic and performance is more easily predicted.

Yes, deterministic destruction is a major feature. Not having to explain what finalizers are (and why they shouldn’t generally be used) is a pretty huge. Keep in mind that Swift interops with C, so deinit is unavoidable for certain types.

More pointedly, not relying on GC enables Swift to be used in domains that don’t want it - think boot loaders, kernels, real time systems like audio processing, etc.

We have discussed in the passed using hybrid approaches like introducing a cycle collector, which runs less frequently than a GC would. The problem with this is that if you introduce a cycle collector, code will start depending on it. In time you end up with some libraries/packages that works without GC, and others that leak without it (the D community has relevant experience here). As such, we have come to think that adding a cycle collector would be bad for the Swift community in the large.

However, it comes with many downsides:

object references are expensive to update

Most garbage collectors have write barriers, which execute extra code when references are updated. Most garbage collectors also have safe points, which means that extra instructions get inserted into loops.

object references cannot be atomically updated

This is true, but Swift currently has no memory model and no concurrency model, so it isn’t clear that this is actually important (e.g. if you have no shared mutable state).

heap fragmentation

This is at best a tradeoff depending on what problem you’re trying to solve (e.g. better cache locality or smaller max RSS of the process). One thing that I don’t think is debatable is that the heap compaction behavior of a GC (which is what provides the heap fragmentation win) is incredibly hostile for cache (because it cycles the entire memory space of the process) and performance predictability.

Given that GC’s use a lot more memory than ARC systems do, it isn’t clear what you mean by GC’s winning on heap fragmentation.

the closure capture syntax uses up an unreasonable amount of mindshare just because of [weak self]

I think that this specific point is solvable in others ways, but I’ll interpret this bullet as saying that you don’t want to worry about weak/unowned pointers. I completely agree that we strive to provide a simple programming model, and I can see how "not having to think about memory management" seems appealing.

On the other hand, there are major advantages to the Swift model. Unlike MRR, Swift doesn’t require you to micromanage memory: you think about it at the object graph level when you’re building out your types. Compared to MRR, ARC has moved memory management from being imperative to being declarative. Swift also puts an emphasis on value types, so certain problems that you’d see in languages like Java are reduced.

That said, it is clear that it takes time and thought to use weak/unowned pointers correctly, so the question really becomes: does reasoning about your memory at the object graph level and expressing things in a declarative way contribute positively to your code?

My opinion is yes: while I think it is silly to micromanage memory, I do think thinking about it some is useful. I think that expressing that intention directly in the code adds value in terms of maintenance of the code over time and communication to other people who work on it.

Since Swift doesn't expose memory management operations outside of `autoreleasepool`, it seems to me that you could just drop in a garbage collector instead of reference counting and it would work (for most purposes).

Has a GC been considered at all?

GC also has several *huge* disadvantages that are usually glossed over: while it is true that modern GC's can provide high performance, they can only do that when they are granted *much* more memory than the process is actually using. Generally, unless you give the GC 3-4x more memory than is needed, you’ll get thrashing and incredibly poor performance. Additionally, since the sweep pass touches almost all RAM in the process, they tend to be very power inefficient (leading to reduced battery life).

I’m personally not interested in requiring a model that requires us to throw away a ton of perfectly good RAM to get an “simpler" programming model - particularly on that adds so many tradeoffs.

-Chris

···

On Feb 8, 2016, at 11:56 AM, Félix Cloutier via swift-evolution <swift-evolution@swift.org> wrote:


(Frank Ecsedy) #7

In fact, Java is looking to introduce value types and will have to wrestle
with how to do it in a pure GC environment. For Swift a GC is lots of pain
for minimal gain or even a net loss.

···

On Monday, February 8, 2016, Dave Abrahams via swift-evolution < swift-evolution@swift.org> wrote:

on Mon Feb 08 2016, Félix Cloutier <swift-evolution@swift.org > <javascript:;>> wrote:

> Has there been a garbage collection thread so far? I understand that
> reference counting vs. garbage collection can be a heated debate, but
> it might be relevant to have it.
>
> It seems to me that the two principal upsides of reference counting
> are that destruction is (essentially) deterministic and performance is
> more easily predicted. However, it comes with many downsides:
>
> object references are expensive to update
> object references cannot be atomically updated
> heap fragmentation
> the closure capture syntax uses up an unreasonable amount of mindshare
> just because of [weak self]
>
> Since Swift doesn't expose memory management operations outside of
> `autoreleasepool`, it seems to me that you could just drop in a
> garbage collector instead of reference counting and it would work (for
> most purposes).
>
> Has a GC been considered at all?

Yes. Among other problems, you can't do copy-on-write efficiently with
a GC, because you can't detect a unique reference. And without
efficient copy-on-write, most interesting value types (Array) are out
the window.

--
-Dave

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <javascript:;>
https://lists.swift.org/mailman/listinfo/swift-evolution


(David Waite) #8

TL;DR - I agree generational garbage collection is not a great idea

However, it comes with many downsides:

object references are expensive to update

Most garbage collectors have write barriers, which execute extra code when references are updated. Most garbage collectors also have safe points, which means that extra instructions get inserted into loops.

Many even have read barriers for when the heap is compacted.

heap fragmentation

This is at best a tradeoff depending on what problem you’re trying to solve (e.g. better cache locality or smaller max RSS of the process). One thing that I don’t think is debatable is that the heap compaction behavior of a GC (which is what provides the heap fragmentation win) is incredibly hostile for cache (because it cycles the entire memory space of the process) and performance predictability.

Given that GC’s use a lot more memory than ARC systems do, it isn’t clear what you mean by GC’s winning on heap fragmentation

I find only copy collectors focus on cache locality. Usually the mark and sweep is a fallback when the copy collectors fail to prevent growth, and compaction of the mature objects to really promote reducing RSS is a last resort.

the closure capture syntax uses up an unreasonable amount of mindshare just because of [weak self]

I think that this specific point is solvable in others ways, but I’ll interpret this bullet as saying that you don’t want to worry about weak/unowned pointers. I completely agree that we strive to provide a simple programming model, and I can see how "not having to think about memory management" seems appealing.

On the other hand, there are major advantages to the Swift model. Unlike MRR, Swift doesn’t require you to micromanage memory: you think about it at the object graph level when you’re building out your types. Compared to MRR, ARC has moved memory management from being imperative to being declarative. Swift also puts an emphasis on value types, so certain problems that you’d see in languages like Java are reduced.

Small changes in the relationships between objects and object lifetime in an object-oriented design can have huge impacts when doing manual memory management. What was a scope-lived instance can become a long-lived, shared instance as part of new requirements, and if you are doing manual memory management result in large amounts of changes to accommodate the new ownership rules. This is occasionally when people move from alloc/free or scoped memory management to MRR.

MRR then can have issues when you are not tracking that objects may have an interrelationship (a cycle), which weak references can solve.

The important part of declarative management IMHO is that there are rules for how separate subsystems within your architecture reference one another (e.g. weak references to delegates). That eliminates a lot of the macro-level problems. Comparatively, micro-level problems can be understood as part of a subsystem's design.

That said, it is clear that it takes time and thought to use weak/unowned pointers correctly, so the question really becomes: does reasoning about your memory at the object graph level and expressing things in a declarative way contribute positively to your code?

My opinion is yes: while I think it is silly to micromanage memory, I do think thinking about it some is useful. I think that expressing that intention directly in the code adds value in terms of maintenance of the code over time and communication to other people who work on it.

In generational systems there are de-optimization issues when an application (or application server) is composed of components with differing memory needs.

For example, a web application may have a current request, current transaction, user state, and a cache of persisted state. All of these have differing lifetimes. If the GC is optimized toward handling a younger generation consisting of just the current transaction (due to timing or space tuning), current transaction data may very well get promoted to the mature generation, where it is likely to stay until there is a full GC. Even with tuning, heavy use of caching may still push data into the mature generation, including cache data which is meant to expire. You wind up having threads doing concurrent and incremental garbage collection to deal with the fact that while the GC can be tuned for performance of any one component, it cannot be tuned to handle them well in aggregate.

MRR and declarative memory management systems generally are not negatively impacted by other in-process components.

Since Swift doesn't expose memory management operations outside of `autoreleasepool`, it seems to me that you could just drop in a garbage collector instead of reference counting and it would work (for most purposes).

Has a GC been considered at all?

GC also has several *huge* disadvantages that are usually glossed over: while it is true that modern GC's can provide high performance, they can only do that when they are granted *much* more memory than the process is actually using. Generally, unless you give the GC 3-4x more memory than is needed, you’ll get thrashing and incredibly poor performance. Additionally, since the sweep pass touches almost all RAM in the process, they tend to be very power inefficient (leading to reduced battery life).

Externalizing the marks from objects can turn much of the sweep into a read of memory rather than writing, which I believe does help power usage (although I’ve seen no studies of GC impact on battery life). It certainly helps for scripting languages, particularly in server-oriented applications on unix platforms, which are apt to fork() processes to handle requests than use threads, and wish to retain the benefits of CoW memory usage past the first GC cycle.

The memory and CPU impact of using a fully automatic GC are not to be understated though. This is a strong reason that different mobile phone platforms have drastically different memory requirements for their devices, for instance.

-DW

···

On Feb 8, 2016, at 2:00 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:


(Charles Srstka) #9

Strong -1. The deterministic nature of the refcounting system is one of Swift’s best features.

Charles

···

On Feb 8, 2016, at 1:56 PM, Félix Cloutier via swift-evolution <swift-evolution@swift.org> wrote:

Has there been a garbage collection thread so far? I understand that reference counting vs. garbage collection can be a heated debate, but it might be relevant to have it.

It seems to me that the two principal upsides of reference counting are that destruction is (essentially) deterministic and performance is more easily predicted. However, it comes with many downsides:

object references are expensive to update
object references cannot be atomically updated
heap fragmentation
the closure capture syntax uses up an unreasonable amount of mindshare just because of [weak self]

Since Swift doesn't expose memory management operations outside of `autoreleasepool`, it seems to me that you could just drop in a garbage collector instead of reference counting and it would work (for most purposes).

Has a GC been considered at all?

Félix

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Félix Cloutier) #10

I should check because it's been a while, but in Objective-C loops that quickly modified references, I've measured objc_retain make up to 15% of the loop's cost. My understanding is that most of that cost comes from tracking references in a global hash map, which I can't imagine being particularly kind to memory use or cache performance either.

···

Le 8 févr. 2016 à 15:11:52, Joe Groff <jgroff@apple.com> a écrit :

On Feb 8, 2016, at 11:56 AM, Félix Cloutier via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Has there been a garbage collection thread so far? I understand that reference counting vs. garbage collection can be a heated debate, but it might be relevant to have it.

It seems to me that the two principal upsides of reference counting are that destruction is (essentially) deterministic and performance is more easily predicted. However, it comes with many downsides:

object references are expensive to update
object references cannot be atomically updated
heap fragmentation
the closure capture syntax uses up an unreasonable amount of mindshare just because of [weak self]

Since Swift doesn't expose memory management operations outside of `autoreleasepool`, it seems to me that you could just drop in a garbage collector instead of reference counting and it would work (for most purposes).

While true in theory, code that relies on destructors to clean up unmanaged resources does not port cleanly to GC as-is in practice. GC of course has its own drawbacks. Heap scanning is expensive, thrashing cache and burning battery. GCs also require higher memory ceiling proportional to the amount of heap actively being used, and GCs suitable for interactive use tend to increase responsiveness at the cost of higher memory use, which has its own second-order energy costs—devices need more RAM, and spend more time swapping or killing, and thus need bigger batteries to refresh all that RAM. ARC interoperates better with unmanaged resources, both non-memory resources like sockets and files and also C-level memory resources. The ARC optimizer and Swift's calling convention also optimize toward reclaiming resources closer to their last use, keeping resource usage low.

-Joe


(Félix Cloutier) #11

Chris, I appreciate that your reply covers the questions without simply stating an opinion.

It seems to me that the two principal upsides of reference counting are that destruction is (essentially) deterministic and performance is more easily predicted.

Yes, deterministic destruction is a major feature. Not having to explain what finalizers are (and why they shouldn’t generally be used) is a pretty huge. Keep in mind that Swift interops with C, so deinit is unavoidable for certain types.

More pointedly, not relying on GC enables Swift to be used in domains that don’t want it - think boot loaders, kernels, real time systems like audio processing, etc.

I agree that you don't want a GC in these, but on the other hand, from what I know of the current RC implementation, I find it hard to believe that it would be much more acceptable. Beyond any cycle efficiency concerns, interrupting a retain/release operation could cause a real carnage, and disabling interrupts while it happens won't necessarily be acceptable either. (Which begs the question: how well does Swift react to signals right now?)

If I wanted to use Swift today in these environments, I'd probably stick to UnsafePointers and structs. It wouldn't really matter if classes were managed with a reference count or a mark-and-sweep collector since I wouldn't have any.

We have discussed in the passed using hybrid approaches like introducing a cycle collector, which runs less frequently than a GC would. The problem with this is that if you introduce a cycle collector, code will start depending on it. In time you end up with some libraries/packages that works without GC, and others that leak without it (the D community has relevant experience here). As such, we have come to think that adding a cycle collector would be bad for the Swift community in the large.

Yes, I've heard about many problems with that and I wouldn't be in favor of a mix either.

object references cannot be atomically updated

This is true, but Swift currently has no memory model and no concurrency model, so it isn’t clear that this is actually important (e.g. if you have no shared mutable state).

The fact that references can't be updated atomically will necessarily influence any decision that is taken regarding the concurrency model. The concurrency model pre-proposal <https://github.com/apple/swift/blob/master/docs/proposals/Concurrency.rst> already uses it as an argument to push a model that respects this limitation. I will be shocked if the same argument doesn't come up again when these talks actually start.

the closure capture syntax uses up an unreasonable amount of mindshare just because of [weak self]

I think that this specific point is solvable in others ways, but I’ll interpret this bullet as saying that you don’t want to worry about weak/unowned pointers. I completely agree that we strive to provide a simple programming model, and I can see how "not having to think about memory management" seems appealing.

What I actually meant is that a lot of energy is being spent right now on how to address these issues, which wouldn't be issues at all if Swift relied on a garbage collector.

On the other hand, there are major advantages to the Swift model. Unlike MRR, Swift doesn’t require you to micromanage memory: you think about it at the object graph level when you’re building out your types. Compared to MRR, ARC has moved memory management from being imperative to being declarative. Swift also puts an emphasis on value types, so certain problems that you’d see in languages like Java are reduced.

That said, it is clear that it takes time and thought to use weak/unowned pointers correctly, so the question really becomes: does reasoning about your memory at the object graph level and expressing things in a declarative way contribute positively to your code?

My opinion is yes: while I think it is silly to micromanage memory, I do think thinking about it some is useful. I think that expressing that intention directly in the code adds value in terms of maintenance of the code over time and communication to other people who work on it.

I don't agree with this. People will very often use `weak` or `unowned` only to break a cycle, not because it really doesn't matter if the object suddenly disappears. The whole weak self capture is a good example of that. The unpopularity of weak references in garbage-collected languages is generally a demonstration that most applications don't need to bother with that.

Since Swift doesn't expose memory management operations outside of `autoreleasepool`, it seems to me that you could just drop in a garbage collector instead of reference counting and it would work (for most purposes).

Has a GC been considered at all?

GC also has several *huge* disadvantages that are usually glossed over: while it is true that modern GC's can provide high performance, they can only do that when they are granted *much* more memory than the process is actually using. Generally, unless you give the GC 3-4x more memory than is needed, you’ll get thrashing and incredibly poor performance. Additionally, since the sweep pass touches almost all RAM in the process, they tend to be very power inefficient (leading to reduced battery life).

I’m personally not interested in requiring a model that requires us to throw away a ton of perfectly good RAM to get an “simpler" programming model - particularly on that adds so many tradeoffs.

The way I have to reason about cycles isn't my favorite part of Swift, but I'm not necessarily in favor of having a GC either. I was very surprised that it hadn't come up at all since RC causes actual issues that are currently discussed. (It's also nice that you can ask and have informed people reply to you.)

Félix


(Goffredo Marocchi) #12

Devices being able to house a compact amount of RAM is a device vendor concern not a Swift one although the former kind of owns the latter, but that is being a bit cheeky I will admit that ;).

Still, iPhone 6 Plus only having 1 GB of RAM... Grrrr... :P. Seriously, multitasking suffers greatly on that device as well as having multiple opened tabs on Safari...

···

Sent from my iPhone

On 8 Feb 2016, at 20:11, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Feb 8, 2016, at 11:56 AM, Félix Cloutier via swift-evolution <swift-evolution@swift.org> wrote:

Has there been a garbage collection thread so far? I understand that reference counting vs. garbage collection can be a heated debate, but it might be relevant to have it.

It seems to me that the two principal upsides of reference counting are that destruction is (essentially) deterministic and performance is more easily predicted. However, it comes with many downsides:

object references are expensive to update
object references cannot be atomically updated
heap fragmentation
the closure capture syntax uses up an unreasonable amount of mindshare just because of [weak self]

Since Swift doesn't expose memory management operations outside of `autoreleasepool`, it seems to me that you could just drop in a garbage collector instead of reference counting and it would work (for most purposes).

While true in theory, code that relies on destructors to clean up unmanaged resources does not port cleanly to GC as-is in practice. GC of course has its own drawbacks. Heap scanning is expensive, thrashing cache and burning battery. GCs also require higher memory ceiling proportional to the amount of heap actively being used, and GCs suitable for interactive use tend to increase responsiveness at the cost of higher memory use, which has its own second-order energy costs—devices need more RAM, and spend more time swapping or killing, and thus need bigger batteries to refresh all that RAM. ARC interoperates better with unmanaged resources, both non-memory resources like sockets and files and also C-level memory resources. The ARC optimizer and Swift's calling convention also optimize toward reclaiming resources closer to their last use, keeping resource usage low.

-Joe

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Paul Ossenbruggen) #13

the closure capture syntax uses up an unreasonable amount of mindshare just because of [weak self]

I think that this specific point is solvable in others ways, but I’ll interpret this bullet as saying that you don’t want to worry about weak/unowned pointers. I completely agree that we strive to provide a simple programming model, and I can see how "not having to think about memory management" seems appealing.

I actually like RC, and don’t find it particularly problematic except in this one case…the closure case. If we could solve this one problem then I think the rest of it is fine. I don’t mind remembering to put a weak reference in for back pointers etc. The benefits of RC are great, and the introduction of ARC was a great mental burden lifted of developer’s minds. Just trying to explain and remember all the tricky rules around the closure capture would be nice to solve and would love to discuss that rather than garbage collection.

- Paul

···

On Feb 8, 2016, at 1:00 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:


(Goffredo Marocchi) #14

On a similar note though, I think if we are taking the point of view that simplifying the memory model and making it safer is worth lots of complexity trade offs I would go all the way in that direction and look real hard at the outcome of MS's project Midori rather than stopping at a GC, but it is kind of getting very off topic so I will cut this short.

···

Sent from my iPhone

On 8 Feb 2016, at 21:04, Frank Ecsedy via swift-evolution <swift-evolution@swift.org> wrote:

In fact, Java is looking to introduce value types and will have to wrestle with how to do it in a pure GC environment. For Swift a GC is lots of pain for minimal gain or even a net loss.

On Monday, February 8, 2016, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

on Mon Feb 08 2016, Félix Cloutier <swift-evolution@swift.org> wrote:

> Has there been a garbage collection thread so far? I understand that
> reference counting vs. garbage collection can be a heated debate, but
> it might be relevant to have it.
>
> It seems to me that the two principal upsides of reference counting
> are that destruction is (essentially) deterministic and performance is
> more easily predicted. However, it comes with many downsides:
>
> object references are expensive to update
> object references cannot be atomically updated
> heap fragmentation
> the closure capture syntax uses up an unreasonable amount of mindshare
> just because of [weak self]
>
> Since Swift doesn't expose memory management operations outside of
> `autoreleasepool`, it seems to me that you could just drop in a
> garbage collector instead of reference counting and it would work (for
> most purposes).
>
> Has a GC been considered at all?

Yes. Among other problems, you can't do copy-on-write efficiently with
a GC, because you can't detect a unique reference. And without
efficient copy-on-write, most interesting value types (Array) are out
the window.

--
-Dave

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Douglas Gregor) #15

We very deliberately chose an ARC model for Swift. It is *way* out of scope for Swift 3; I don’t think it’s worth discussing this on swift-evolution at all.

  - Doug

···

On Feb 8, 2016, at 11:56 AM, Félix Cloutier via swift-evolution <swift-evolution@swift.org> wrote:

Has there been a garbage collection thread so far? I understand that reference counting vs. garbage collection can be a heated debate, but it might be relevant to have it.

It seems to me that the two principal upsides of reference counting are that destruction is (essentially) deterministic and performance is more easily predicted. However, it comes with many downsides:

object references are expensive to update
object references cannot be atomically updated
heap fragmentation
the closure capture syntax uses up an unreasonable amount of mindshare just because of [weak self]

Since Swift doesn't expose memory management operations outside of `autoreleasepool`, it seems to me that you could just drop in a garbage collector instead of reference counting and it would work (for most purposes).

Has a GC been considered at all?


(Colin Cornaby) #16

I thought I’d add my opinion to this thread even though it’s been well covered in different posts, just to put more weight on the -1 argument…

I spend most my time dealing with performance sensitive code, and Swift’s predictability is a very strong pro for it against languages like Java. Java’s garbage collector provides too much uncertainty and instability to performance.

There certainly are tradeoffs. ARC won’t catch things like circular retain loops. But as mentioned, tagged pointers on the modern architecture take care of the retain/release overhead.

I’ll put it a different way that sums up why I felt the need to reply: ARC has it’s inconveniences, but moving to garbage collection would likely lead us to abandon any plans to adopt Swift for many of our performance sensitive projects. If someone solves the “pausing” problem in a garbage collected language I’d reconsider. But until then it would take Swift out of consideration for a lot of projects.

···

On Feb 8, 2016, at 11:56 AM, Félix Cloutier via swift-evolution <swift-evolution@swift.org> wrote:

Has there been a garbage collection thread so far? I understand that reference counting vs. garbage collection can be a heated debate, but it might be relevant to have it.

It seems to me that the two principal upsides of reference counting are that destruction is (essentially) deterministic and performance is more easily predicted. However, it comes with many downsides:

object references are expensive to update
object references cannot be atomically updated
heap fragmentation
the closure capture syntax uses up an unreasonable amount of mindshare just because of [weak self]

Since Swift doesn't expose memory management operations outside of `autoreleasepool`, it seems to me that you could just drop in a garbage collector instead of reference counting and it would work (for most purposes).

Has a GC been considered at all?

Félix

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Michel Fortin) #17

Me neither. I certainly prefer ARC. Hence one reason I like Swift more than D.

I'd just like to point out that a "fake" GC that stops short of actually freeing objects to instead print a warning on the console listing objects to be collected would be a great help to detect and fix cycles early in development.

···

Le 8 févr. 2016 à 16:00, Chris Lattner via swift-evolution <swift-evolution@swift.org> a écrit :

I’m personally not interested in requiring a model that requires us to throw away a ton of perfectly good RAM to get an “simpler" programming model - particularly on that adds so many tradeoffs.

--
Michel Fortin
https://michelf.ca


(Greg Parker) #18

A cycle collector also requires that retaining storage and non-retaining storage be distinguishable. This would be possible in a pure-Swift environment with sufficient extra metadata, but is not in Objective-C. Any cycle that included an Objective-C object would not be detectable.

···

On Feb 8, 2016, at 1:00 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

We have discussed in the passed using hybrid approaches like introducing a cycle collector, which runs less frequently than a GC would. The problem with this is that if you introduce a cycle collector, code will start depending on it. In time you end up with some libraries/packages that works without GC, and others that leak without it (the D community has relevant experience here). As such, we have come to think that adding a cycle collector would be bad for the Swift community in the large.

--
Greg Parker gparker@apple.com <mailto:gparker@apple.com> Runtime Wrangler


(Thorsten Seitz) #19

Strong -1 for GC.

I think the advantages of ARC over GC as given by Chris, Joe and Dave with regards to energy efficiency, memory efficiency, caching behavior, predictability, value types with copy on write and declarative memory management on the object graph level are essential and indispensable.

And yes, I see it as an advantage having to think about memory management dependencies on the object graph level and deciding how to break cycles. I think this leads to much cleaner object models.

-Thorsten

···

Am 08.02.2016 um 22:00 schrieb Chris Lattner via swift-evolution <swift-evolution@swift.org>:

On Feb 8, 2016, at 11:56 AM, Félix Cloutier via swift-evolution <swift-evolution@swift.org> wrote:
Has there been a garbage collection thread so far? I understand that reference counting vs. garbage collection can be a heated debate, but it might be relevant to have it.

Technically speaking, reference counting is a form of garbage collection, but I get what you mean. Since there are multiple forms of GC, I'll assume that you mean a generational mark and sweep algorithm like you’d see in a Java implementation.

It seems to me that the two principal upsides of reference counting are that destruction is (essentially) deterministic and performance is more easily predicted.

Yes, deterministic destruction is a major feature. Not having to explain what finalizers are (and why they shouldn’t generally be used) is a pretty huge. Keep in mind that Swift interops with C, so deinit is unavoidable for certain types.

More pointedly, not relying on GC enables Swift to be used in domains that don’t want it - think boot loaders, kernels, real time systems like audio processing, etc.

We have discussed in the passed using hybrid approaches like introducing a cycle collector, which runs less frequently than a GC would. The problem with this is that if you introduce a cycle collector, code will start depending on it. In time you end up with some libraries/packages that works without GC, and others that leak without it (the D community has relevant experience here). As such, we have come to think that adding a cycle collector would be bad for the Swift community in the large.

However, it comes with many downsides:

object references are expensive to update

Most garbage collectors have write barriers, which execute extra code when references are updated. Most garbage collectors also have safe points, which means that extra instructions get inserted into loops.

object references cannot be atomically updated

This is true, but Swift currently has no memory model and no concurrency model, so it isn’t clear that this is actually important (e.g. if you have no shared mutable state).

heap fragmentation

This is at best a tradeoff depending on what problem you’re trying to solve (e.g. better cache locality or smaller max RSS of the process). One thing that I don’t think is debatable is that the heap compaction behavior of a GC (which is what provides the heap fragmentation win) is incredibly hostile for cache (because it cycles the entire memory space of the process) and performance predictability.

Given that GC’s use a lot more memory than ARC systems do, it isn’t clear what you mean by GC’s winning on heap fragmentation.

the closure capture syntax uses up an unreasonable amount of mindshare just because of [weak self]

I think that this specific point is solvable in others ways, but I’ll interpret this bullet as saying that you don’t want to worry about weak/unowned pointers. I completely agree that we strive to provide a simple programming model, and I can see how "not having to think about memory management" seems appealing.

On the other hand, there are major advantages to the Swift model. Unlike MRR, Swift doesn’t require you to micromanage memory: you think about it at the object graph level when you’re building out your types. Compared to MRR, ARC has moved memory management from being imperative to being declarative. Swift also puts an emphasis on value types, so certain problems that you’d see in languages like Java are reduced.

That said, it is clear that it takes time and thought to use weak/unowned pointers correctly, so the question really becomes: does reasoning about your memory at the object graph level and expressing things in a declarative way contribute positively to your code?

My opinion is yes: while I think it is silly to micromanage memory, I do think thinking about it some is useful. I think that expressing that intention directly in the code adds value in terms of maintenance of the code over time and communication to other people who work on it.

Since Swift doesn't expose memory management operations outside of `autoreleasepool`, it seems to me that you could just drop in a garbage collector instead of reference counting and it would work (for most purposes).

Has a GC been considered at all?

GC also has several *huge* disadvantages that are usually glossed over: while it is true that modern GC's can provide high performance, they can only do that when they are granted *much* more memory than the process is actually using. Generally, unless you give the GC 3-4x more memory than is needed, you’ll get thrashing and incredibly poor performance. Additionally, since the sweep pass touches almost all RAM in the process, they tend to be very power inefficient (leading to reduced battery life).

I’m personally not interested in requiring a model that requires us to throw away a ton of perfectly good RAM to get an “simpler" programming model - particularly on that adds so many tradeoffs.

-Chris

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Joe Groff) #20

That's less of a problem in modern Objective-C and Swift. On ARM64 and 64-bit Mac targeting >=10.11, the refcount for ObjC objects is encoded in the 'isa' word. Swift objects always store the refcount inline in the object as well. (In the worst case, consulting a hash map is definitely not ideal, but is going to be less thrashy than scanning the entire heap.)

-Joe

···

On Feb 8, 2016, at 12:39 PM, Félix Cloutier <felixcca@yahoo.ca> wrote:

I should check because it's been a while, but in Objective-C loops that quickly modified references, I've measured objc_retain make up to 15% of the loop's cost. My understanding is that most of that cost comes from tracking references in a global hash map, which I can't imagine being particularly kind to memory use or cache performance either.