As someone who doesn't develop for the Apple ecosystem, I can't quite find one place that articulates well what the Swift development philosophy is and what it brings to the table besides just being modern language with shims for interacting with legacy Apple APIs. Why would I use Swift on Linux?
Most of the posts that I see related to Swift are RFCs evaluating solutions to problems in other languages. I rarely get to see the actual solutions being integrated into the language. Is this just a result of HN readers caring more about language design than learning to leverage the Swift language?
I can't quite find one place that articulates well what the Swift development philosophy is and what it brings to the table besides just being modern language with shims for interacting with legacy Apple APIs.
It's a modern, fast, statically compiled, statically typed, language (that also has shims for interacting with legacy Obj-C code, but that's not a very important aspect) that unlike Go keeps up with modern PL features and expressibility, and unlike Rust has automatic RC-style memory management that you don't have to think about as much. There's no over-arching principle at play -- it's a pragmatic language.
Why would I use Swift on Linux?
Because you like the actual language and its ecosystem (or not). It's not like you should be using any language just because of some "design philosophy" it's supposed to have.
Most of the posts that I see related to Swift are RFCs evaluating solutions to problems in other languages. I rarely get to see the actual solutions being integrated into the language.
Well, there are several free books from Apple and tons of material to see what the language itself offers.
It also has a really slow compiler and that generates code which is about 2x slower than modern C++, which incidentally also has everything you mention (mostly in the form of libraries to prevent bloating the language).
It also has a really slow compiler and that generates code which is about 2x slower than modern C++
Considering it uses the same compiler infrastructure, it's mostly because of using higher level more expensive constructs. It can also generate code as fast as C++ in some cases (see the language benchmarks game).
which incidentally also has everything you mention (mostly in the form of libraries to prevent bloating the language).
Well, hard to bloat C++ further anyway. It does have a better ecosystem and more mature compilers, but on a purely language level, I'd take Swift over C++ (including "modern C++" that still comes with all the historical baggage and a trillion gotchas) any day.
C++ has header file, which is extremely bad for compile time. You can engineer around it (for example, see Zapcc, which is 4x faster than clang) but it's so difficult most don't. Since Swift does not have header file, even if Swift is higher level Swift should be faster to compile. The fact that it isn't, IMO, indicates problems in Swift frontend (which is, NOT shared with clang, hence NOT the same compiler infrastructure).
I just wrote a swift api that I'm running on Ubuntu 16.04. I wasn't hesitant to use swift because first, it substantially outperforms node on many benchmarks [1] [2] and second, the community of people who are excited about swift seem to have made up for its young age. You can find workable tutorials and help online for server side Swift. And there are multiple backend frameworks to choose from. Having type safety on the server is as great as it is on the client. It's also great to be able to write the server in the same language as the client. Makes for a VERY smooth dev process. Why wouldn't you use swift on the server? (my guess is fear of new tech -- which can be healthy many times but, IMHO, at least in the case of server side swift, it may be a bit irrational!)
1. Specifically for the first set of benchmarks linked to, they are really irrelevant because they don't represent typical workloads unless you're writing a MAAS (Mandlebrot As A Service). Node was designed for I/O efficiency, not fastest CPU-bound computations. That's exercising the JS engine (e.g. V8) more than anything node-specific. With node becoming more VM-neutral, it's entirely possible for other engines (e.g Chakracore or SpiderMonkey) to be better at other types of computation.
2. Especially in the second set of linked benchmarks, it seems the author of the article was hardly a node.js developer because they not only used an older node branch at the time they wrote the article, but they left out a lot of common optimizations (some of which were pointed out in the comments section). Even Express (which the author used) is known to not be very well optimized.
With that in mind, benchmarks are not the only thing you should be looking at IMHO. For example (for me personally), using a single language for frontend and backend is a big deal because there is less cognitive overhead when switching between the two (previously I often found myself writing JS syntax in PHP scripts and vice versa and trying to remember the APIs for different languages/platforms is difficult). There are many other benefits as well, just watch some of Mikeal Rogers' talks to get a better idea.
1. Specifically for the first set of benchmarks linked to, they are really irrelevant because they don't represent typical workloads unless you're writing a MAAS (Mandlebrot As A Service). Node was designed for I/O efficiency, not fastest CPU-bound computations.
Since everything that is not async will need to invoke CPU-bound computations in Node (e.g. for loops, string manipulation, JSON parsing, and generally everything that's not just delegating work elsewhere with a callback), this I/O efficiency doesn't buy much except for very lightweight uses.
Most services in the real world soon get closer to Mandlebrot As A Service than "pure I/O".
Node still does OK-ish there because V8 is fast serially too (and of course everybody runs it on cluster mode or similar), but it's not like fast I/O by itself is enough.
With that in mind, benchmarks are not the only thing you should be looking at IMHO. For example (for me personally), using a single language for frontend and backend is a big deal because there is less cognitive overhead when switching between the two
What about the reduced cognitive overhead of not having to deal with JS on the server though?
Plus, aren't usually the backend and frontend teams different ?
Since everything that is not async will need to invoke CPU-bound computations in Node (e.g. for loops, string manipulation, JSON parsing, and generally everything that's not just delegating work elsewhere with a callback), this I/O efficiency doesn't buy much except for very lightweight uses.
These are different levels of CPU-bound-ness. Comparing for-loops (which barely cost anything) to encoding video or computing PI for example is not comparing apples to apples. The body of a for-loop will typically greatly dwarf any "overhead" incurred by the for-loop construct itself. Obviously any non-I/O tasks like these are going to be CPU-bound. What I meant was Node is good at waiting for databases, web servers, file systems, etc. to respond to requests without blocking other work.
Most services in the real world soon get closer to Mandlebrot As A Service than "pure I/O".
I disagree with this. I think most web apps spend most of their time waiting on the network, file systems, etc. to respond.
What about the reduced cognitive overhead of not having to deal with JS on the server though?
I don't understand this question. JS is not a hard language to learn/pick up and there is a large number of developers out there who already know the language from working in the browser. However, my main point there was about using a single language. Yes, technically you can use Ruby, PHP, etc. in a browser (via JS), but nobody does that because it'd be very slow compared to just using JS from the get-go.
Plus, aren't usually the backend and frontend teams different ?
It depends. I would say for most small businesses (and including the many "one man team" developers who do remote contract work for example) this is not the case. I cannot speak for large corporations, but there is definitely a larger possibility of having separate teams there. However just because you have separate backend and frontend teams doesn't mean having separate languages on each is any more beneficial (e.g. code sharing in some cases can be a win when using the same language). There's also the benefit of being able to "reuse" a JS developer on either end, depending on the work that's needed.
No, GCs collect reference cycles. Whereas a (strong) reference cycle in ARC in an operation repeated many times in a long-running server or something adds up.
Worse, sometimes, you don't even know if you're creating a leak. For example, I recently had to call, given two gesture recognisers a and b:
a.requireToFail(b)
A is a long-lived object. B goes away when the current view controller is popped. But not if A keeps a strong reference to it. Does it? Probably no one without access to the source code of UIGestureRecognizer knows!
1. Swift's ARC uses atomic reference counting underneath, which is normally very expensive, and relies on compiler optimization to remove as many reference count operations as possible. This is normally pretty effective, but there are situations where it's not possible.
2. Reference counting allows for arbitrary long pauses as the result of cascading deletions (i.e. where object deletions trigger other object deletions). You can work around that (by deferring deletions), but then you don't have any guarantees about the timeliness of deletions anymore. As far as I know, this is still an open issue for Swift.
3. Without a compaction scheme, you risk memory fragmentation. While this is a rare occurrence in practice, there are workloads where it can happen.
4. Reference counting cannot reclaim cycles without a mechanism for detecting cycles; such a cycle detector (e.g. trial deletion) poses pretty much the same challenges as tracing GC.
Obviously, tracing garbage collectors pose their own challenges; my point is merely that whether performance and memory usage are more consistent has to be judged on a case by case basis.
1. You're right, it can be slow! But it's usually still consistent and that's useful.
2. Hmm, cascading deletions. Is that really a big problem in practice? I'm skeptical because it seems like that would affect C and C++ programs too, but you rarely hear anyone mention it. Maybe Swift tends to use more objects whereas C++ programmers tend to be better at packing stuff together?
3. Fragmentation -- that's true, but again, it affects C and C++ too. I guess for long-running C/C++ programs you're likely to manage memory pools directly. I don't know if that's possible in Swift.
4. Cycles -- weak references work fine for this. I have never had trouble with cyclic garbage in Objective-C. (I mean, I've had leaks, but they're always easy to spot with a leak detector and easy to fix with weak references.)
Overall, it seems to me that reference-counting adds a small but consistent performance penalty, and otherwise should have comparable runtime behavior to malloc/free in C, which is known to work pretty well when used correctly.
Note that Apple got smooth and reliable 60fps performance on the original iPhone, which was extremely resource-constrained by modern standards, using Objective-C, which isn't usually considered a fast language!
On the GC side, it seems like you typically get bursty, unpredictable performance, in both time and memory. Modern GCs work very hard to keep collection pauses as short as possible, but almost inevitably that means keeping garbage around for longer, which means using a lot of memory.
1. I think you may not realize what state of the art tracing GCs can accomplish. IBM's Metronome has pause times down to hundreds of microseconds.
2. It only takes freeing a tree with a few thousand nodes for it to become an issue. It happens in C++, too (heck, there've been cases where chained destructor calls overflowed the stack [1]). The reason why you don't hear more about it is because pause times just aren't that big a deal for most applications. In forum debates, people always discuss triple A video games and OS kernels and such, but in practice, only a minority of programmers actually have to deal with something even approaching hard real time requirements. Generally, most applications optimize more for throughput rather than pause times.
3. Yes, and it can be a problem for C/C++, too. It's rare, but not non-existent. Note that pools can actually make fragmentation worse for long-running processes.
4. Weak references work if you get them right. But for long-running processes, even a single error can accumulate over time.
On the GC side, it seems like you typically get bursty, unpredictable performance, in both time and memory. Modern GCs work very hard to keep collection pauses as short as possible, but almost inevitably that means keeping garbage around for longer, which means using a lot of memory.
This ... is not at all how garbage collectors work, especially where real time is concerned. Not even remotely. I recommend "The Garbage Collection Handbook" (the 2011 edition) for a better overview. And ultra-low pause times are generally more of an opt-in feature, because they're rarely needed.
> almost inevitably that means keeping garbage around for longer, which means using a lot of memory.
This ... is not at all how garbage collectors work, especially where real time is concerned.
Hmm, I'm certainly no GC expert, but is it really not the case that GC tends to be memory-hungry? Not exotic academic systems, but the languages people use day-to-day.
Most of my experience with GCs is in languages like Java and C#. Java in particular can be very fast but always seems to be memory-hungry, using like 4x the memory you'd need in C++. I haven't spent a huge amount of time fine-tuning the GC settings (it seems like Oracle is working to simplify that -- good!) but the defaults seem to assume at least 2x memory usage as elbow room for the GC.
That's on the server. On mobile, I've worked with iOS and Android and iOS undeniably gets the same work done with much less memory. Flagship Android phone have 4GB of memory and need it, whereas Apple hasn't felt the need to bump up memory so quickly even after going 64-bit across the board.
The last I heard about real-time GC, with guaranteed space and time bounds, it sounded like it was theoretically solved, but not used much in practice because it was too slow. That was a number of years ago though. Has that situation changed? Are there prominent languages or systems with real-time GC?
Looking up IBM's Metronome led me to the Jikes RVM (https://en.wikipedia.org/wiki/Jikes_RVM), which sounds so cool that I wonder why it isn't being used everywhere?
The PowerPC (or ppc) and IA-32 (or Intel x86, 32-bit) instruction set architectures are supported by Jikes RVM.
Ah, no ARM and no x64, that'd be it.
What's keeping this kind of GC technology back from the mainstream?
The Jikes RVM is designed for research, not production. It's pretty impressive, but (inter alia) does not implement all of Java and does not support as many platforms.
What's keeping this kind of GC technology back from the mainstream?
The fact that successful commercialization is possible; the GC tech that you see in Metronome and C4 is seriously non-trivial and not easy to reproduce unless you spend money on it; and it's also technology that businesses are willing to pay for.
At the same time, only a minority of open source use cases really require this kind of hard real-time GC, so there's little pressure to create an open source equivalent. Shenandoah is the one open source GC that does try to compete in this space, and it is trading away some performance for getting ultra-low pause times.
I'll add that this is difficult only because of concurrency and arbitrary sharing of data between threads. If you have one heap per thread, then it becomes much, much easier (and is a solved problem if soft real-time is all you need).
This is because naive can-do-it-all allocations in C/C++ can be expensive, not because allocations are inherently expensive. In C/C++, you have:
1. A call of a library function that typically cannot be inline.
2. Analysis of the object size in order to pick the right pool or a more general allocator to allocate from.
3. A traditional malloc() implementation needs to also use a global lock; thread-local allocators are comparatively rare.
4. For large objects, a complex first-fit/best-fit algorithm with potentially high complexity has to be used.
Modern GCs typically use a bump allocator, which is an arena allocator in all but name. In OCaml or on the JVM, an allocation is a pointer increment and comparison.
Even without bump allocators, it's easy for a GC implementation to automatically turn most allocations into pool allocations that can be inlined.
Also: much as people love to talk about video games, video games with such strict performance requirements are not only just a part of the video game industry, they are a tiny part of the software industry.
In OCaml or on the JVM, an allocation is a pointer increment and comparison.
That's true, but if (hopefully rarely) the object turns out to be needed later, it has to be copied to another heap, and that takes time and memory. Pointers need to be redirected and that takes a little work too.
Bump allocators are definitely a huge win, as good as anything you can do in C/C++ and much more convenient for the programmer, but they're not a completely free lunch.
As someone who is starting to dabble in the Apple ecosystem via Swift - I heartedly agree. There are plenty of resources for new developers who are trying to learn programming via Swift, but if you are a seasoned developer who wants to jump straight into best practices and architectures while learning the syntax and features at the same time... you're going to have a bad time.
I also feel like with Swift people are so focused on pumping out their mobile app that best practices go out the window, or even that the knowledge is seen as such a valuable and proprietary skillset that the senior and experienced developers simply keep it to themselves and don't publish (compared to other OSS ecosystems)...
Comments
As someone who doesn't develop for the Apple ecosystem, I can't quite find one place that articulates well what the Swift development philosophy is and what it brings to the table besides just being modern language with shims for interacting with legacy Apple APIs. Why would I use Swift on Linux?
Most of the posts that I see related to Swift are RFCs evaluating solutions to problems in other languages. I rarely get to see the actual solutions being integrated into the language. Is this just a result of HN readers caring more about language design than learning to leverage the Swift language?
It's a modern, fast, statically compiled, statically typed, language (that also has shims for interacting with legacy Obj-C code, but that's not a very important aspect) that unlike Go keeps up with modern PL features and expressibility, and unlike Rust has automatic RC-style memory management that you don't have to think about as much. There's no over-arching principle at play -- it's a pragmatic language.
Because you like the actual language and its ecosystem (or not). It's not like you should be using any language just because of some "design philosophy" it's supposed to have.
Well, there are several free books from Apple and tons of material to see what the language itself offers.
It also has a really slow compiler and that generates code which is about 2x slower than modern C++, which incidentally also has everything you mention (mostly in the form of libraries to prevent bloating the language).
Considering it uses the same compiler infrastructure, it's mostly because of using higher level more expensive constructs. It can also generate code as fast as C++ in some cases (see the language benchmarks game).
Well, hard to bloat C++ further anyway. It does have a better ecosystem and more mature compilers, but on a purely language level, I'd take Swift over C++ (including "modern C++" that still comes with all the historical baggage and a trillion gotchas) any day.
C++ has header file, which is extremely bad for compile time. You can engineer around it (for example, see Zapcc, which is 4x faster than clang) but it's so difficult most don't. Since Swift does not have header file, even if Swift is higher level Swift should be faster to compile. The fact that it isn't, IMO, indicates problems in Swift frontend (which is, NOT shared with clang, hence NOT the same compiler infrastructure).
Unfortunately the problem here happens to be of a theoretical nature and not something a few years of engineering can reasonably fix.
Swift's slowness comes from its typechecker, not LLVM.
I mean runtime slowness, not compile time.
See for yourself:
http://benchmarksgame.alioth.debian.org/u64q/compare.php?lan...
I just wrote a swift api that I'm running on Ubuntu 16.04. I wasn't hesitant to use swift because first, it substantially outperforms node on many benchmarks [1] [2] and second, the community of people who are excited about swift seem to have made up for its young age. You can find workable tutorials and help online for server side Swift. And there are multiple backend frameworks to choose from. Having type safety on the server is as great as it is on the client. It's also great to be able to write the server in the same language as the client. Makes for a VERY smooth dev process. Why wouldn't you use swift on the server? (my guess is fear of new tech -- which can be healthy many times but, IMHO, at least in the case of server side swift, it may be a bit irrational!)
1. https://benchmarksgame.alioth.debian.org/u64q/compare.php?la... 2. https://medium.com/@rymcol/linux-ubuntu-benchmarks-for-serve...
The problem with those benchmarks are two-fold:
1. Specifically for the first set of benchmarks linked to, they are really irrelevant because they don't represent typical workloads unless you're writing a MAAS (Mandlebrot As A Service). Node was designed for I/O efficiency, not fastest CPU-bound computations. That's exercising the JS engine (e.g. V8) more than anything node-specific. With node becoming more VM-neutral, it's entirely possible for other engines (e.g Chakracore or SpiderMonkey) to be better at other types of computation.
2. Especially in the second set of linked benchmarks, it seems the author of the article was hardly a node.js developer because they not only used an older node branch at the time they wrote the article, but they left out a lot of common optimizations (some of which were pointed out in the comments section). Even Express (which the author used) is known to not be very well optimized.
With that in mind, benchmarks are not the only thing you should be looking at IMHO. For example (for me personally), using a single language for frontend and backend is a big deal because there is less cognitive overhead when switching between the two (previously I often found myself writing JS syntax in PHP scripts and vice versa and trying to remember the APIs for different languages/platforms is difficult). There are many other benefits as well, just watch some of Mikeal Rogers' talks to get a better idea.
Since everything that is not async will need to invoke CPU-bound computations in Node (e.g. for loops, string manipulation, JSON parsing, and generally everything that's not just delegating work elsewhere with a callback), this I/O efficiency doesn't buy much except for very lightweight uses.
Most services in the real world soon get closer to Mandlebrot As A Service than "pure I/O".
Node still does OK-ish there because V8 is fast serially too (and of course everybody runs it on cluster mode or similar), but it's not like fast I/O by itself is enough.
What about the reduced cognitive overhead of not having to deal with JS on the server though?
Plus, aren't usually the backend and frontend teams different ?
These are different levels of CPU-bound-ness. Comparing for-loops (which barely cost anything) to encoding video or computing PI for example is not comparing apples to apples. The body of a for-loop will typically greatly dwarf any "overhead" incurred by the for-loop construct itself. Obviously any non-I/O tasks like these are going to be CPU-bound. What I meant was Node is good at waiting for databases, web servers, file systems, etc. to respond to requests without blocking other work.
I disagree with this. I think most web apps spend most of their time waiting on the network, file systems, etc. to respond.
I don't understand this question. JS is not a hard language to learn/pick up and there is a large number of developers out there who already know the language from working in the browser. However, my main point there was about using a single language. Yes, technically you can use Ruby, PHP, etc. in a browser (via JS), but nobody does that because it'd be very slow compared to just using JS from the get-go.
It depends. I would say for most small businesses (and including the many "one man team" developers who do remote contract work for example) this is not the case. I cannot speak for large corporations, but there is definitely a larger possibility of having separate teams there. However just because you have separate backend and frontend teams doesn't mean having separate languages on each is any more beneficial (e.g. code sharing in some cases can be a win when using the same language). There's also the benefit of being able to "reuse" a JS developer on either end, depending on the work that's needed.
How does ARC hold up for long-lived servers? Are the leaks manageable?
What leaks? You only get leaks if you have cycles that you forgot or don't close non-memory resources that you keep referencing.
Which is not that different than with a GC.
No, GCs collect reference cycles. Whereas a (strong) reference cycle in ARC in an operation repeated many times in a long-running server or something adds up.
Worse, sometimes, you don't even know if you're creating a leak. For example, I recently had to call, given two gesture recognisers a and b:
a.requireToFail(b)
A is a long-lived object. B goes away when the current view controller is popped. But not if A keeps a strong reference to it. Does it? Probably no one without access to the source code of UIGestureRecognizer knows!
Exactly. How is the profiling experience?
Why should ARC imply leaks?
It doesn't.
I would expect it to be more reliable than a GC, as its performance and memory usage are more consistent.
That can cut both ways.
1. Swift's ARC uses atomic reference counting underneath, which is normally very expensive, and relies on compiler optimization to remove as many reference count operations as possible. This is normally pretty effective, but there are situations where it's not possible.
2. Reference counting allows for arbitrary long pauses as the result of cascading deletions (i.e. where object deletions trigger other object deletions). You can work around that (by deferring deletions), but then you don't have any guarantees about the timeliness of deletions anymore. As far as I know, this is still an open issue for Swift.
3. Without a compaction scheme, you risk memory fragmentation. While this is a rare occurrence in practice, there are workloads where it can happen.
4. Reference counting cannot reclaim cycles without a mechanism for detecting cycles; such a cycle detector (e.g. trial deletion) poses pretty much the same challenges as tracing GC.
Obviously, tracing garbage collectors pose their own challenges; my point is merely that whether performance and memory usage are more consistent has to be judged on a case by case basis.
1. You're right, it can be slow! But it's usually still consistent and that's useful.
2. Hmm, cascading deletions. Is that really a big problem in practice? I'm skeptical because it seems like that would affect C and C++ programs too, but you rarely hear anyone mention it. Maybe Swift tends to use more objects whereas C++ programmers tend to be better at packing stuff together?
3. Fragmentation -- that's true, but again, it affects C and C++ too. I guess for long-running C/C++ programs you're likely to manage memory pools directly. I don't know if that's possible in Swift.
4. Cycles -- weak references work fine for this. I have never had trouble with cyclic garbage in Objective-C. (I mean, I've had leaks, but they're always easy to spot with a leak detector and easy to fix with weak references.)
Overall, it seems to me that reference-counting adds a small but consistent performance penalty, and otherwise should have comparable runtime behavior to malloc/free in C, which is known to work pretty well when used correctly.
Note that Apple got smooth and reliable 60fps performance on the original iPhone, which was extremely resource-constrained by modern standards, using Objective-C, which isn't usually considered a fast language!
On the GC side, it seems like you typically get bursty, unpredictable performance, in both time and memory. Modern GCs work very hard to keep collection pauses as short as possible, but almost inevitably that means keeping garbage around for longer, which means using a lot of memory.
1. I think you may not realize what state of the art tracing GCs can accomplish. IBM's Metronome has pause times down to hundreds of microseconds.
2. It only takes freeing a tree with a few thousand nodes for it to become an issue. It happens in C++, too (heck, there've been cases where chained destructor calls overflowed the stack [1]). The reason why you don't hear more about it is because pause times just aren't that big a deal for most applications. In forum debates, people always discuss triple A video games and OS kernels and such, but in practice, only a minority of programmers actually have to deal with something even approaching hard real time requirements. Generally, most applications optimize more for throughput rather than pause times.
3. Yes, and it can be a problem for C/C++, too. It's rare, but not non-existent. Note that pools can actually make fragmentation worse for long-running processes.
4. Weak references work if you get them right. But for long-running processes, even a single error can accumulate over time.
This ... is not at all how garbage collectors work, especially where real time is concerned. Not even remotely. I recommend "The Garbage Collection Handbook" (the 2011 edition) for a better overview. And ultra-low pause times are generally more of an opt-in feature, because they're rarely needed.
[1] E.g. Herb Sutter's talk at C++Con 2016: https://www.youtube.com/watch?v=JfmTagWcqoE&t=16m23s
Hmm, I'm certainly no GC expert, but is it really not the case that GC tends to be memory-hungry? Not exotic academic systems, but the languages people use day-to-day.
Most of my experience with GCs is in languages like Java and C#. Java in particular can be very fast but always seems to be memory-hungry, using like 4x the memory you'd need in C++. I haven't spent a huge amount of time fine-tuning the GC settings (it seems like Oracle is working to simplify that -- good!) but the defaults seem to assume at least 2x memory usage as elbow room for the GC.
That's on the server. On mobile, I've worked with iOS and Android and iOS undeniably gets the same work done with much less memory. Flagship Android phone have 4GB of memory and need it, whereas Apple hasn't felt the need to bump up memory so quickly even after going 64-bit across the board.
The last I heard about real-time GC, with guaranteed space and time bounds, it sounded like it was theoretically solved, but not used much in practice because it was too slow. That was a number of years ago though. Has that situation changed? Are there prominent languages or systems with real-time GC?
Looking up IBM's Metronome led me to the Jikes RVM (https://en.wikipedia.org/wiki/Jikes_RVM), which sounds so cool that I wonder why it isn't being used everywhere?
The PowerPC (or ppc) and IA-32 (or Intel x86, 32-bit) instruction set architectures are supported by Jikes RVM.
Ah, no ARM and no x64, that'd be it.
What's keeping this kind of GC technology back from the mainstream?
The Jikes RVM is designed for research, not production. It's pretty impressive, but (inter alia) does not implement all of Java and does not support as many platforms.
The fact that successful commercialization is possible; the GC tech that you see in Metronome and C4 is seriously non-trivial and not easy to reproduce unless you spend money on it; and it's also technology that businesses are willing to pay for.
At the same time, only a minority of open source use cases really require this kind of hard real-time GC, so there's little pressure to create an open source equivalent. Shenandoah is the one open source GC that does try to compete in this space, and it is trading away some performance for getting ultra-low pause times.
I'll add that this is difficult only because of concurrency and arbitrary sharing of data between threads. If you have one heap per thread, then it becomes much, much easier (and is a solved problem if soft real-time is all you need).
One note, in video games allocations are a major source of slowdown; don't allocate in your inner loop! Use object pools and arena allocators.
This is because naive can-do-it-all allocations in C/C++ can be expensive, not because allocations are inherently expensive. In C/C++, you have:
1. A call of a library function that typically cannot be inline.
2. Analysis of the object size in order to pick the right pool or a more general allocator to allocate from.
3. A traditional malloc() implementation needs to also use a global lock; thread-local allocators are comparatively rare.
4. For large objects, a complex first-fit/best-fit algorithm with potentially high complexity has to be used.
Modern GCs typically use a bump allocator, which is an arena allocator in all but name. In OCaml or on the JVM, an allocation is a pointer increment and comparison.
Even without bump allocators, it's easy for a GC implementation to automatically turn most allocations into pool allocations that can be inlined.
Also: much as people love to talk about video games, video games with such strict performance requirements are not only just a part of the video game industry, they are a tiny part of the software industry.
In OCaml or on the JVM, an allocation is a pointer increment and comparison.
That's true, but if (hopefully rarely) the object turns out to be needed later, it has to be copied to another heap, and that takes time and memory. Pointers need to be redirected and that takes a little work too.
Bump allocators are definitely a huge win, as good as anything you can do in C/C++ and much more convenient for the programmer, but they're not a completely free lunch.
Well legacy Apple APIs happen to be the Apis for the highest revenue software market in the world.
As someone who is starting to dabble in the Apple ecosystem via Swift - I heartedly agree. There are plenty of resources for new developers who are trying to learn programming via Swift, but if you are a seasoned developer who wants to jump straight into best practices and architectures while learning the syntax and features at the same time... you're going to have a bad time.
I also feel like with Swift people are so focused on pumping out their mobile app that best practices go out the window, or even that the knowledge is seen as such a valuable and proprietary skillset that the senior and experienced developers simply keep it to themselves and don't publish (compared to other OSS ecosystems)...
The objc.io books on Swift are awesome https://www.objc.io/books/