How autofree works #17419
Replies: 10 comments 10 replies
|
(I do not know whether I can, but I have small note) |
Strongly believe that this kind of post and upcoming article is important to help neutralize some of the confusion and deceptive criticisms placed out there. Among the things that are done: 1) Critics who like pretending they don't know anything about V's memory management options. 2) Pretending not to understand what the words "option" or "optional" means, or that V's GC can be turned off. 3) Ridiculous smears of over any GC usage, as if it means to somehow be "inferior". That V uses an optional GC by default is tremendously helpful and convenient to new and casual programmers. Not only that, (in addition to other safety features that V has), it provides a degree of memory safety which has repeatedly been recommended by various government organizations. Consequently, people can get started being productive with V much more rapidly, because they don't have to worry about manual memory management. They can more rapidly focus on getting results and solutions. When ready, the GC can be turned off, because they are actually dealing with a situation where doing so would be useful or are really that advanced of a programmer. As V is a compiled programming language, whose optional GC can be turned off, means it can go about memory management in a number of ways that is highly competitive to any that exists. Thus arena allocators. Optional GC and arena allocator usage, can be more than enough to offset excessive fixation on autofree until it becomes more developed and advanced. These other options should definitely get more attention in the documentation. V is definitely more than just being about autofree (which is a great feature and option), it's a good and useful programming language. Autofree is more like a "cherry on top". The addition of autofree, with the other memory management options, strengthens the already existing benefits of using V. |
|
I m sorry if I tell somethig what sound like that I criticise V have GC, GC is amazing tool when person do not need optimalization ..., and I realy llove V s options and their combinations, really amazing job! |
|
Looking at this. I think this should also be put in V's official Blog (https://blog.vlang.io/), when ready. As it would have a better "aesthetic" and be more "linkable", in addition to be a place where new users can update themselves on things being emphasized for them to know. |
|
The article seems to lack an explanation of why autofree leads to worse performance compared to enabling GC? |
|
Does not used anywhere else mean it isn't returned or its address taken? What about if it is assigned to something, is an implicit clone inserted for heap structs? (2) seems to be missing the local string allocation's call to fn foo() {
s1 := 'hello'
s2 := 'world'
s := s1 + ' ' + s2
str_copy := s.clone() // inserted by autofree
do_stuff(str_copy)
s.free() // inserted by autofree?
}
Was there any update on this? Presumably at least when a reference to heap memory is reassigned to something else, the original memory is also freed if unused? |
|
Autofree has been waiting too long, do I have to wait for another year or even longer |
|
Imho, I think that |
|
I've updated the blog post. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Due to many requests, I'll be writing down the principles of how autofree works. It will give a more transparent view into one of V's memory management models and will make it easier to contribute to the autofree engine, which is still work in progress.
Autofree is one of 4 ways to handle memory in V. The default one is a tracing GC, there's arena allocation with
-prealloc, and manual memory management via-gc none.There's a demo video available. Building the Ved editor with autofree and running it on an 8MB text file with 0 leaks:
https://www.youtube.com/watch?v=gmB8ea8uLsM
When I started working on V, I was very anti-GC, expecting them to be slow and use a lot more RAM. I integrated a GC just for a test, and was surprised at how well it worked with V, a very minimal language, that avoids doing unnecessary allocations in the first place by using value types, string buffers, promoting a simple abstraction-free code style. V's standard library doesn't use the GC, so turning it off for you application is not a problem (no need to use a different version of stdlib etc).
What started as a test and a temporary way to allow developers to write leak free programs, became the stable and well working default option. Autofree in fact performed a bit worse than the default GC. The reasons will be explained in this article.
Other things in the compiler were prioritized, that's why autofree still isn't finished yet. But it is important, and it will be finished. Very often using a GC is simply not an option. For examples in drivers and kernels. In fact, our Vinix kernel uses autofree.
Our goal is to make V very flexible and configurable, that's why we provide multiple ways to manage memory.
As the home page explains, autofree frees the variables that the compiler knows it can safely free. For all the remaining cases, the GC is used with an option to use manual memory management (in the future the compiler will point out the variables it can't free if
-gc noneis used).The amount of such variables can vary depending on the code. It should be about 10% on average. In the mentioned Ved demo, 0% of variables are unhandled by autofree.
Let's go through what autofree can handle and what it does.
1. Simple out of scope variables.
If a variable results in an allocation, and this allocation is not used anywhere else, the variable is freed at the end of the block it was created in (i.e. the variable goes out of scope):
In this example I didn't use
s := 'hello ' + 'world', because V's optimizer will simplify it tos := 'hello world', and simple string literals don't result in an allocation. Literals are also detected by the codegen itself and skipped, so you won't see uselessfree()calls on them."Not used anywhere else" has a precise meaning in the compiler. A variable is left alone when:
return Foo{ x: s });or {}block (those temporaries are emitted after the block in C, so freeing them at scope exit would be wrong);forloop variable (for i, v in list).Everything else gets a
free()call inserted at the end of its scope.2. Allocated strings are cloned on assignments.
To ensure that string copies do not point to freed memory, they are always cloned. This is an expensive O(n) operation, and is one of the reasons autofree can be slower than V's default GC or manual management. It can be optimized by doing copy on write and more analysis by the compiler.
This results in:
This is only about strings, not arrays. Note that V forces cloning arrays on assignment, even without autofree enabled:
The "autofree is sometimes slower than GC" question deserves a fuller answer than just "cloning". The other reason is that autofree frees eagerly at scope exit. A function that creates ten short-lived strings in a loop body will pay ten
free()calls per iteration. The Boehm GC, on the other hand, batches reclamation and only runs when it has to. For programs that allocate in tight loops and let go of allocations almost immediately, batched reclamation can win. For programs with longer-lived allocations (like Ved, which keeps a large text buffer around), autofree's eager strategy is a clear win — there's no scan, no pause, no tracing.3. Temporary expressions inside function calls.
If you write
do_stuff(a + b), the result ofa + bis a heap-allocated string with no name. Autofree can't free what it can't see, so the compiler hoists the temporary into a synthetic variable:becomes, roughly:
The same mechanism handles string interpolation.
'x=${expr}'is split into a string builder that produces a temporary; the temporary is registered in the scope with an internal flag and freed right after it stops being needed.For method calls, the receiver gets the same treatment when it's a temporary expression:
('a' + 'b').contains('ab')will free the concatenation result aftercontainsreturns.4. Recursive freeing of structs, arrays, maps, sumtypes and interfaces.
Calling
s.free()on a string is trivial — it's onefree()of one buffer. Everything else is recursive, and the compiler generates a dedicated free function per type. Given:the compiler emits a
User_freethat freesname, walksemailsfreeing each element and then the backing array, frees the map, and finally releases the struct itself if it was heap-allocated. The same idea extends to:array_T_freefrees every element viaT_freeand then the array.switchon the type tag frees only the active variant's payload.state != 2check, so uninitialized options are never freed.If you define a
free()method on your own type, the compiler uses yours instead of generating one. This is the escape hatch for types that own resources the compiler doesn't know about (file handles, sockets, mmap regions, etc).5. What autofree does not handle yet.
It's more honest to list the limitations than to repeat the "90%" number.
&Foo) are only freed under-experimental. Without that flag, pointer locals to user types are silently left to the GC.voidptrallocations have no free codegen.a, b := f()) doesn't carry an expression into the var, so the string-literal check from §1 can't fire and a temporarystring_freeis emitted even when one side was a literal.return xis fine.return Foo{ x: x }is fine.return [x]orreturn f(x)will currently freexbefore the caller gets it.These are not design limits — they are missing cases. Each one corresponds to a
TODO:somewhere invlib/v/gen/c/autofree.vorauto_free_methods.v.6. Early returns and freeing across nested scopes.
The end-of-scope rule isn't enough on its own. Consider:
When the codegen sees
return, it walks the chain of parent scopes and emits frees for every live variable in each one, stopping at the function boundary. Variables that appear in the return expression itself are marked as "returned" and skipped, soreturn scorrectly hands ownership to the caller.breakandcontinuework the same way, except they stop at the enclosing loop instead of the function.7. Opting out:
@[manualfree]and@[reused].Two attributes let you steer the engine.
@[manualfree]on a function disables autofree inside that function's body. The standard library uses this in hot paths and in places where the compiler doesn't yet know how to reason about lifetimes correctly. You can also put it on a module declaration to opt the whole module out.@[reused]is the opposite of an escape hatch — it's a hint to autofree. It marks a method that recycles its receiver's existing allocation rather than producing a new one (think ofclear()on a string builder). Without the hint, autofree might free a literal that the method intends to reuse; with the hint, the literal is left alone.The
builtinmodule is special-cased: autofree never touches it, because the builtin types are what autofree calls into.8. Current state and what's next.
The pieces above are real, in the compiler today, and exercised by the autofree tests under
vlib/v/tests/and the valgrind suite undervlib/v/slow_tests/valgrind/. The known gaps (§5) are the priority list. The bigger longer-term items are:-gc none+ a flag) that reports every variable autofree couldn't handle, so the remaining manual work is visible.Autofree is not the only memory management option in V, and it doesn't have to be. The goal is that you can pick the model that fits the program: GC for normal application code, autofree for kernels and drivers, arenas for short-lived batch jobs, manual for the last 1%. The same language, the same standard library, the same code.
All reactions