Most likely you would create your own task specific container wrappers storing appropriate allocators inside them to avoid the silly errors of allocating with one allocator while freeing with another. This friction and extra work could actually lead to a better application design in the end.
Yes. If you use std.ArrayList
for lists in an interpreter, then the same sort of logic would apply.
That makes sense, thanks!
I think from a data-oriented standpoint you should avoid individual objects anyway, and instead group them based on the set of keys, so that every unique set of keys is like an archetype, that forms a table, where the keys are the columns and the rows are the former individual instances.
All values shared across an entire column, also conveniently can be shared/stored once (in the archetype), if you are fine with moving the instance to a different archetype once that value needs to be changed.
I donāt think having a lot of managed instances creating an overhead is a very strong argument, I think a better argument is that you shouldnāt have that many container instances and once you have fewer of them; and your code is more data-oriented and deals more with groups of instances, you already naturally have code that groups things and then that code already carries the knowledge about what allocator is used for these 3 containers that are used together to store the data for a bunch of instances. So why store the same allocator 3 times in different containers that are always used together with the same allocator.
Also being able to initialize the unmanaged variants by just using the .empty
decl literal is pretty nice. So I agree that it is better that you see where and when allocations are actually happening.
This is very similar the way by which std.ArrayList
was implemented. Just copy that code instead if you want keep using it.
I will disagree, here itās not negligible. Iāve had first hand experience with that recently. So I was the first in my group of friends to learn Zig, and I follow it very closely. My friend on the other hand has less than a year of practice with the language. Recently he was working on optimizing heās Scrabble solver. In which he computes all the possible solutions for a given board configuration based on the letters and wildcards you might have. I was helping him, looking through his code, benching with poop, and I noticed that he had aliased std.ArrayList(u8)
to String
. changing it to the unmanaged variant, significantly improved performance. The peak RSS went down by like 128mb of Ram. the cache references, cache misses went down by 20% and branch prediction was much better.
So while it probably doesnāt matter at small scale. Itās actually a very big deal at a larger scale. Also the inconvenience is fairly small, and as always you are free to create a wrapper around it. I started like everyone I guess by using the managed versions, and the more I use the language the more Iām leaning towards using undamaged variants.
This very much depends on your design. If you have lots of small arraylists and hashtables you will be spending lots of memory and cpu cycles managing them. Modern trend in Zig programming advocated by Andrew himself is the data-driven programming where instead of slice of containers you go with container of slices. Please google for his videos for the details. Data-driven design makes your RSS smaller and memory access more cache friendly. Also, having fewer but larger array-lists or hash-maps makes overhead negligible,
For the record, I am not against unmanaged containers and use them all the time but not because they save me an allocator object (this saving is nice but not crucial).
I remember a discussion on this topic from a while back. The problem with stashing an allocator
somewhere is that youāre assuming that (a) itāll remain valid beyond the function call and (b) that itās the same interface for deallocation. Either of these has to be true.
Imagine we have a generic function that allows you to limit amount of allocation:
fn callWithLimit(func: anytype, args: anytype, limit: usize) @typeInfo(@TypeOf(func)).@"fn".return_type.? {
// ...
}
How would we go about implementing that? We would loop through args
, look for std.mem.Allocator
, and replace it with an different one:
var new_args: @TypeOf(args) = undefined;
inline for (args, 0..) |arg, index| {
new_args[index] = switch (@TypeOf(arg)) {
std.mem.Allocator => init: {
var awl = AllocatorWithLimit.init(arg, limit);
break :init awl.allocator();
},
else => arg,
};
}
return @call(.auto, func, new_args);
The allocator received by func
would have a ptr
that points to awl
, which sits in the call stack. As soon as callWithLimit()
returns, itās gone.
I do agree, 100%, I donāt think his design was really good, but itās still like 1 < ms response time, so although it would be cool to refactor the implementation he made, I just briefly helped him, and although I would have also implemented a DOD design, Iām pretty sure most people wonāt, and in most cases it will be fine, but having only unmanaged variant, just puts you imo in exactly the mindset you are mentioning, it adds just the right amount of friction, because passing allocators everywhere just isnāt a whole lot of fun, so it will probably make you realize that your design is actually a local maximum that can be better represented and more convenient if it uses some DOD patterns. So I still think there is some values in removing all managed variant.
This really isnāt relevant because the change is to remove managed ArrayLists from the std library ⦠nothing prevented your use case from using unmanaged ArrayLists before or after the change. OTOH, for those who want managed ArrayLists for whatever reason after the change, they can/must roll their own wrapper.
(Iām not arguing for or against the change ⦠especially as it is a done deal and thus moot. As the release notes state, the change was based on a preponderance of feedback in favor of it.)
Were these submissions removed from Hackernews? I can open them from your link, but I canāt find them on the site itself?
They are also not listed on the alternative interface I use. Weird.
They never made it to the Hacker News home page and remained suppressed, yet discoverable through Algolia search. It is very strange, indeed. Normally, Zig news catch HN attention.
Tbf, HNās front page is hardly about interesting software projects anymore. A couple of years ago Lobsterās and HNās front page were nearly a copy of each other, and Lobster still has that quality, but not HN.
Decl literals is actually kind of a game-changer for the language
I really like how that and .zon
files fit with the āstruct as key-value pairsā pattern that is decently common in Zig.
If Iām understanding both right, we have a situation where calling an API and defining a configuration file for software using that API can reasonably share syntax without the complexity of either exploding.
Ie, if the API is defined as accepting an āoptionsā struct, and that struct has (possibly nested structs with) āenum-likeā decl literals like .default
, anything up to and including the top level can be replaced with those āenum-likesā in a configuration .zon
file as well.
Now developer-users who are familiar with configuring your software can crack open the API and find everything immediately familiar. For systems software like Curl or OpenSSH which has both users and developers working with it, I think that is a super neat feature.