This is my first post in this forum so, if my question seems trivial or idiotic, don't blame me too much haha. This being said, I wanted to gather feedbacks and opinions about ARC vs GC memory footprint, from people using Swift in a backend context. To be more clear ;
I didn't found any meaningful enough sources on Google, neither found clear reports that indicate the memory footprint of a Swift backend application, in terms of ARC memory usage vs the same usecase with a more classical JVM GC approach. I do know the architectural differences between these two technologies, but I'm more interested about concrete performance and memory footprint comparison.
Basically, I wanted to ask you, the community, about your opinions on this topic. How does ARC perform against classical (JVM) GC? In the context of a backend app, like a microservice let's say, hosted in a cloud env (so not as a mobile app). Is this something that has already been done? And if so, did you heard of any positive outcome on the memory footprint of ARC? Is it better, equals or worst than JVM? How does it scale? etc.
I'm trying to constitute a case for using Swift as the main language for my new project, which is 100% a backend, cloud-native microservice. This microservice might be consumed later by an iOS mobile app, as well as an Android app too.
I suggest to perform some tests yourself comparing "real" Swift that uses ARC, and for example RemObjects implementation of Swift - Silver: The Swift Language for .NET and Java/Android | RemObjects Software - which is using garbage collection. Source code can be exactly the same - Swift vs Swift.
So from my experience having run both for clients and migrated from Java/JVM to Swift the key takeaways are:
No magic JVM tuning playing with flags until it works
Consistent deterministic performance (no GC that kicks it whenever it pleases)
Orders of magnitude better memory performance in Swift compared to Java for the same app. E.g. ~3GB for a Spring application translated to around 100MB for a Swift application
Here are some stats from a server running locally running a very basic echo endpoint that just returned the contents of the request payload in the response. I ran this test for about 16 minutes. This is from an issue where someone thought we had a memory leak in Hummingbird
I couldn't find anything obvious. I did a test where I had a route that responded with the body that was in the request. And using wrk hit that route 52107270 times, transferring about 153GB and memory usage never went over 9MB according to the activity monitor.
This of course is hardly a real world example, but should give you an idea of how well ARC can run under extreme pressure. Once you bring in databases and do some real work those numbers will change and the numbers from @0xTim are probably more revealing.
in general i have found that Swift applications, at least well-written ones, use very little memory compared to other languages. this should be seen as one of the biggest advantages of using Swift on the server.
anecdotally, i find many people have negative perceptions of ARC because their first (and sadly last) experience with Swift concurrency was during Swift 5.5 which had a number of memory leaks and memory corruption bugs in the runtime. however those bugs were fixed many releases ago and i have not encountered any similar issues in many years.
I've never heard of Hummingbird. How is it compared to Vapor? (performance, features, etc.). Several years ago I tried to use Perfect (https://perfect.org/), but now it seems that this project is essentially dead.
IMHO Hummingbird is a new yet performant framework which is significantly more lightweight than Vapor (which also means it’s not so easy to use as Vapor).
What’s interesting about Hummingbird is that it has a core HTTP implementation which is rumored to be the prototype of a WIP unified HTTP server for the Swift on Server ecosystem, which means Vapor 5 is very likely to use the same HTTP core server as HummingBird thanks to the efforts of SSWG.
That’s the key point in overall comparison, I believe. With GC languages it is easy to get good enough performance with the most straightforward code, while in languages like Swift (or Rust, for instance) many discover it requires a bit more skill to have the same level of performance.
Once the load and performance increase, however, the requirements and tools to handle that in these languages remain mostly the same, while in GC world it has an order of magnitude difference in how well you need to understand all the nuances of memory management.
In the end, of course, there is more room for performance improvement in ARC (or similar), with a drastic difference in memory usage and granularity of control you have. But how good you know the language determines a lot here.
Important to note, you only get determinism in applications that have no multi-threading (i.e. no Swift Concurrency or Dispatch). In real-world applications the determinism of deinit calls on Sendable classes and actors is mostly gone. Even though you can exactly pinpoint where release calls are in emitted code, ordering of executed tasks that capture classes/actors and manipulate reference counts is not deterministic.
Swift is definitely better in memory consumption, it could be 10x difference easily just to run the app.
But tbh for small services I wouldn't worry much, especially if you're doing an MVP.
Good point, just wanted to highlight it. Even though topic is old, but it's an interesting area we're landing into with all the new language features. Exciting to see how language evolve further and how community adapts.
As an insignificant caveat, isn't it true that statics and globals in Swift basically reside in memory permanently once initialized?
JVM web servers create distinct classloaders for each app (minimally), so the class static members are GC'd with the classloader when the app is re-deployed. But keeping the server up mattered only in multi-tenant servers where time bouncing took digits off your 5-9's uptime.
(As if you needed more reasons to avoid global data.)
While you are correct, even in threaded applications there are still regions where you do work on a single thread, and can you can still reason about those regions and the lifetime of (non-shared) objects within them deterministically.
So you don’t completely lose all benefits of Swift/ARC.
As I understand it, the "deterministic performance" here is simply meant as the opposite of "sporadic, uncontrollable execution pauses (when the GC kicks in)".
I remember reading about some becnhmark for iPhone vs Android where the conclusion was: in order for an Android phone to maintain the same battery life for running similar tasks, it should have 25-30% bigger battery and double the size of RAM compared to an iPhone with a similar CPU.
Can't vouch for the accuracy of the benchmarks but sounded quite believable. That's the price in both power consumption and RAM of the GC-based architectures.
And as a philosophical aside, I find it strange that the majority of mainstream programming languages today are GC-based. Many of them are the "children of the 1990s" (Java, Python, Ruby, etc) when nobody cared about power consumption, and when the CPU power plus RAM was assumed to be growing at crazy rates every year, i.e. the "it's slow today, will improve tomorrow" mantra. But why did newer languages like Go or Kotlin pick GC again? More of a rhetorical question.
I can understand picking on Go here, but Kotlin's entire reason for existence was to give a modern coherently designed language for JVM to people who would otherwise be stuck writing Java.
Think even today power consumption is not a priority when designing a language, and that's neither good, nor bad, just as it is. People just focus on different stuff. GC works well with higher level abstraction languages, that's why it came from functional Lisp, where it's basically needed. Rust's memory model basically came up to reduce memory related bugs. So AFAIK if you're building something modern and high level nowadays, you're either landing into GC world, or trying to come up with something different, like regions and etc.
Think even today power consumption is not a priority when designing a language, and that's neither good, nor bad, just as it is.
Considering the environmental crisis, I believe we should be caring about power consumption and efficiency even more. That's how we, as developers, can use our expertise to improve the situation!
On one hand you have Turing machine and lambda calculus, on other von Neumann architecture—what's your proposal on reducing power consumption? My point was not that power consumption is not important, but rather giving all the ideas and limitations we have—there are lots of things to tackle, both from software and hardware. Otherwise we could just use C or Assembler.
Btw, I also believe it's fine to go with GC as the idea per se, it's up to implementation and optimisation to improve performance and reduce power consumption.