Hacker News new | ask | show | jobs
by cornholio 923 days ago
I think this tight coupling between the language and the platform compromised a very promising language. Swift is one of few if not the only modern language that, at the same time, has excellent performance (due to AOT compilation and optimization, deterministic garbage collection via ARC etc.), has modern security features (algebraic nil as opposed to NULL, bounds checking etc.), and is relatively easy to learn and become productive in, perhaps on par with Python/JavaScript for the core language.

I don't think there's something else in the "general purpose languages with substantial real-world use" camp that touches on those 3 points quite like Swift does.

On the other hand, non-Apple developers have good reason to avoid Apple, due to the extreme anti-competitive behavior. It's the C# story all over again.

3 comments

Its performance is unfortunately very far from "near Rust". I don't know if that's a result of one virtual dispatch too many or upfront overhead of its ARC implementation, but Swift almost always underperforms on microbenchmarks, despite expectations.

If there's a deep dive on this, I'd love to read it. Could one of the possible reasons be targeting few-core systems and providing as deterministic memory usage as possible to fit into RAM on iOS devices without putting the burden on the programmer?

I generally feel that for a language like Java/C#, which Swift is, you really need a JIT and a tracing, moving GC to get optimal performance. Apple has pushed the COM-like model of static code generation and non-moving reference-counted GC about as far as it can go at this point (impressively far--the compiler heroics in Swift are incredible), and it still can't quite make it to Java/C#, which end up having a simpler implementation than that of Swift in the end. The fact is that the ability to dynamically observe the behavior of the program and recompile with optimizations on the fly is just too powerful to give up.

Perhaps aggressive PGO could help to close some of the gap. The problem is that PGO requires effort on the part of developers to write comprehensive test cases and it's not clear how to scale that workflow. Large companies can write representative test cases and scale PGO on their performance-sensitive services, but your average iOS app developer won't be willing to do that.

Initially I was kind of disappointed of how AOT evolved on Android verus Windows Phone, then I came to realise Google was actually right.

Whereas Windows Phone would use Windows Store to AOT compile the application, Android would AOT on device.

Thus initially, it felt like using the tiny phones for that would be a bad decision, and it was, as the JIT was reintroduced 2 versions later (Android 7).

However, it was a mix and match of all modes, interpreter hand written in Assembly for quick startup, a JIT with PGO data gathering, AOT compiler with feedback loop from PGO data, latter on, sharing PGO data across devices via Play Store services.

This mix of JIT/AOT with PGO sharing across everyone, brings the optimal execution flow that a given application will ever get, allows reflection and dynamic loading to still be supported, and AOT compiler toolchain can have all time of the world to compile on the background.

It's most likely just reference counting and the way abstractions work in Swift (dynamic dispatch?). In particular, it still loses to C# even if you use AOT for the latter, especially in multi-threaded scenarios.

HotSpot C2 and .NET Dynamic PGO-optimized compilations first and foremost help to devirtualize heavy abstractions and inline methods that are unprofitable to inline unconditionally under JIT constraints, with C2 probably doing more heavy lifting because JVM defaults to virtual calls and .NET defaults to non-virtual.

With that said, I am not aware of any comprehensive benchmarking suites that would explore in-depth differences between these languages/platforms for writing a sample yet complex application and my feedback stems mostly from microbenchmark-ish workloads e.g. [0][1].

Performance aside, I do want to compliment Swift for being a pleasant language to program in if you have C# and Rust experience.

[0] https://github.com/ixy-languages/ixy-languages (2019)

[1] https://github.com/jinyus/related_post_gen (2023)

On the case of Java, it isn't only HotSpot, there are several other options, and in the case of OpenJ9 and Azul, cloud JIT also plays a role for cloud workloads.

I also like Swift, if anything it helped to bring back the pressure that AOT compilation also matters.

The performance difference is a small constant factor, perhaps 1.05x, perhaps 3x depending on the workload. If you are writing kernels, high performance graphics, signal processing, numeric analysis - sure, that's significant.

For the typical application though, it's fast enough, you get same order of magnitude performance as C++ or Rust with a almost Python like mental load. As the success of other dog-slow languages show, this is a major selling point.

I can confirm that one big reason why Apple went with reference counting in Swift is because they like it when garbage is freed right away. This lets them get away with smaller heaps than they'd need to get comparable performance with tracing garbage collection. This does slow down execution somewhat; the overhead of updating all those reference counts isn't terrible, but it is significant. It's just a price they consider to be well worth paying.
> It's the C# story all over again.

At least C# has a real cross platform story for a while now.

Partially, you need to rely on the community for GUI stuff (Avalonia and Uno), and old Microsoft still pushes VS/Windows as the best experience, anyone else that wants a VS like experience has to buy Rider.

Yes, there is VS Code, which besides being Electron based, Microsoft is quite open that will never achieve feature parity with VS.

With regards to UI there is Microsoft’s MAUI, which I personally prefer over Avalonia. I love the single project approach of MAUI. I think Avalonia also relies on MAUI controls to some extent (I seem to recall a <UseMaui /> project setting in Avalonia projects.
MAUI doesn't count if "supports GNU/Linux" is part of being considered FOSS proper, and on macOS they took the shortcut of using Mac Catalyst instead of macOS UI APIs.
RC is slower than a modern garbage collector, ARC (if the A means it requires an atomic increment/decrement) is significantly so.

I’m not saying that it is a bad choice, it is probably a good one in case of battery-powered machines with small RAMs, but I think tracing GCs get a bad look for no good reason.

Not sure why this gets downvoted when a quick search for RC overhead in Swift reveals that it is quite high[1]

[1] Figure 3 in https://iacoma.cs.uiuc.edu/iacoma-papers/pact18.pdf

---

Additionally, people comparing ARC to Objective-C's conservative GC in the replies don't seem to understand that (1) refcounting is a form of GC, often times inefficient compared to a mark-and-sweep GC, and (2) conservative GCs are quite limited and Apple's implementation was pretty bad compared to other implementations.

Objective-C objects are basically all a struct objc_class* under the hood, and conservative GCs in general cannot distinguish whether a given word is a pointer. Even worse, for a conservative GC to determine whether a word points into a heap-allocated block, it has to perform a lengthy, expensive scan of the entire heap. It also doesn't help that Apple decided to kickstart the GC if your messages began with "re" (the prefix for "retain" and "release" messages, which were used all the time before ARC came around). So at one point in time, you were able to marginally boost performance of a garbage collected Objective-C application by avoiding messages beginning with "re"!

The A in Arc stands for "automatic."

But you are right about memory usage and battery ... this is why iOS devices require less memory than Android devices for comparable performance (or better performance in some cases).

The confusion might arise from the fact that for a rust arc, the a does mean atomic.
Well, automatic doesn’t add much to the picture, it’s more of a marketing name. But you are right, I used rust’s terminology here.
Indeed, in microbenchmarks the only time you see swift faster than java, c#, or go, is when the bounds checking is turned off. It is not a very performant language. I do like the syntax and semantics though.
What's the point opining on which is faster when you don't even know what ARC stands for, and thus presumably not the first thing about it?

Apple's frameworks used GC before, and they switched to ARC.

What Apple calls "ARC", the rest of the word simply calls "RC". Unlike in Objective-C, the vast majority of RC implementations do not need the developer to modify refcounts by hand. It was already "automatic", so to speak.

Moreover, ARC itself indeed modifies refcounts atomically. If it didn't, you would not be able to reliably determine the liveness of an object shared between threads. Now ask yourself whether atomically updating dozens of integers is faster than flipping a bit across a table of pointers.

Basically Apple turned Objective-C's GC failure to deal with C semantics, picked COM's approach to smart pointers, and made a market message out of it into ARC, for people that never dealt with this kind of stuff before.

Like in many things where "Apple did it first".

ARC can stand for multiple things and is more of a marketing name here, than anything. The relevant garbage collector algorithm is called reference counting - and depending on whether it is single-threaded or have to do it over multiple threads, it can have quite a big overhead. Also, ObjC was also ref counted AFAIK before.
Yes, everyone that points that out usually has no idea that Objective-C GC failed due to C's semantics making it quite prone to crashes, and that automating Cocoa's retain/release calls was a much more easier and safer approach than making C code work in a sensible way beyond what a conservative tracing GC will ever be able to offer, while dealing with C pointers all over the place.