So I am making a memory safe language named zinc which will transpilie to zig and compilie.
I am will be using like this pattern for variables:
let x:i32 = 10;
Gets transpilied to :
var x = try _zinc_alloc(i32);
x.* = 10;
defer _zinc_dealloc(x);
In it I am allowing only move of var which will delete old one and borrow which will copy pointer. I have added other two more var keywords like local is a keyword which is function scoped and will use arena allocator and a keyword fix which will use fixedbufferallocator.
So my main question is which is fastest allocators in zig which I should use as default for let keyword.
I am making my language such that only one function zinc alloc will be able to use many allocators from inside without even changing a line in code or library code just changing allocator to used in alloc function.
If they are so easily exchangable it should be easy to measure them.
One of the strengths of Zig and manual memory management is that you can choose to use many different allocators and also have multiple instances, it is on the programmer to know when to use what, that has been discussed a lot previously, so I won’t repeat that here. See some of these topics:
It seems you intend to use some rust-style lifetime analysis / constraints, for that it may make sense to adapt the chosen allocator such that you are able to group multiple allocations to have the same lifetime, for example by using an arena allocator where appropriate.
Beyond that I don’t think there is much that can be said generally, except that it depends on the code how it is structured and the resulting variable lifetimes. I think to tweak performance in that area you will need a detailed understanding of the allocators you use anyway.
I would prefer:
var x = try _zinc_alloc(i32);
defer _zinc_dealloc(x);
x.* = 10;
While transforming every variable on its own is probably an easier translation, I would expect it to result in slower code, personally I would try to have more analysis in-between and group multiple variables (with similar lifetimes) to share one memory allocation. The style of using new/free for every single thing seems terrible in practice, especially if you pick an allocator that can’t mitigate the individual-object-thinking-based loss of performance.
And if you do lifetime analysis you already should have some way to do some grouping, further if you use RAII (I am not a huge fan but that is off-topic), you could use nested arenas. The benefit of arenas would be that you can collapse many deallocations into a single arena deallocation.
I designed my language so that from behind it should not pass allocator to functions but it is causing a problem.
Like I have local keyword which is using function scoped arena it can’t use std.file.read because I don’t want users to send allocator as arguments to function for it I have only few options left.
either stick to one allocator with _zincalloc and de alloc functions.
Your problem is you have to deal with multiple lifetimes.
Local lifetimes are simple and are suited for an arena/stack.
Non-local lifetimes are more complex, the simplest solution is explicit alloc and de-alloc. That can be with std library functions or keywords or something else.
Other solutions include (but not limited to), in order of complexity:
reference counting (simplest form of GC)
garbage collection (this is a big category)
ownership/lifetime analysis (doesn’t have to be anywhere near rust level)