Stream: t-libs/wg-allocators

Topic: https://github.com/rust-lang/wg-allocators/issues/32


gnzlbg (Nov 08 2019 at 16:12, on Zulip):

@Lokathor @Tim Diekmann can you provide code for that issue ?

gnzlbg (Nov 08 2019 at 16:12, on Zulip):

Or a link to the actual places in your own code where you would use that API?

Lokathor (Nov 08 2019 at 16:13, on Zulip):

I don't recall at this immediate moment, nor do I care enough to look through my old code.

gnzlbg (Nov 08 2019 at 16:14, on Zulip):

Like, vec![0; 128] uses Alloc::alloc_zeroed

gnzlbg (Nov 08 2019 at 16:14, on Zulip):

Vec::from_raw_parts works

Lokathor (Nov 08 2019 at 16:15, on Zulip):

sure, so why can't the public use alloc zeroed with the initial vec too

gnzlbg (Nov 08 2019 at 16:15, on Zulip):

They can, Vec::from_raw_parts allows that

gnzlbg (Nov 08 2019 at 16:15, on Zulip):

What problem does it motivate adding an extra API for solving this particular problem only, when we have like 3 ways to solve it ?

Lokathor (Nov 08 2019 at 16:15, on Zulip):

from_raw_parts is a degenerate fallback for when the type authors haven't had time to give you enough good stuff

gnzlbg (Nov 08 2019 at 16:16, on Zulip):

Vec<NonZeroU8>::with_capacity_zeroed makes no sense

Lokathor (Nov 08 2019 at 16:16, on Zulip):

surr

Lokathor (Nov 08 2019 at 16:16, on Zulip):

i agree

gnzlbg (Nov 08 2019 at 16:17, on Zulip):

if you want the vec![0; N] optimization, there are a billion different ways to allow you to use that

gnzlbg (Nov 08 2019 at 16:17, on Zulip):

but one needs to know which problem precisely you have that warrants it to choose the right one

Lokathor (Nov 08 2019 at 16:17, on Zulip):

well, this is just one method

Lokathor (Nov 08 2019 at 16:17, on Zulip):

and people will live without it

gnzlbg (Nov 08 2019 at 16:18, on Zulip):

@Tim Diekmann had the problem as well, so apparently there are enough people running into this issue

gnzlbg (Nov 08 2019 at 16:18, on Zulip):

which means that solving it might be worth doing

Lokathor (Nov 08 2019 at 16:18, on Zulip):

personally? I avoid using the vec macro

Lokathor (Nov 08 2019 at 16:18, on Zulip):

always i avoid it

Lokathor (Nov 08 2019 at 16:18, on Zulip):

if i see i used it i usually go remove it later

gnzlbg (Nov 08 2019 at 16:19, on Zulip):

let mut x: Vec::new(); x.resize(0, 42); uses Alloc::alloc_zeroed as well

gnzlbg (Nov 08 2019 at 16:19, on Zulip):

IIRC

gnzlbg (Nov 08 2019 at 16:20, on Zulip):

there is a trait that you can implement for types for which a particular value has a more efficient allocation pattern for bulk allocations

gnzlbg (Nov 08 2019 at 16:22, on Zulip):

https://github.com/rust-lang/rust/blob/master/src/liballoc/vec.rs#L1698

gnzlbg (Nov 08 2019 at 16:22, on Zulip):

You can specialize it in your own crate for your own types

gnzlbg (Nov 08 2019 at 16:22, on Zulip):

A dang, it's private, maybe it should be public

Lokathor (Nov 08 2019 at 16:32, on Zulip):

the standard library has long needed a ZeroedSafe trait

Lokathor (Nov 08 2019 at 16:32, on Zulip):

it's so simple that no one can agree on doing it

gnzlbg (Nov 08 2019 at 16:40, on Zulip):

people want to use it to initialize statics, and that requires it be a const fn

gnzlbg (Nov 08 2019 at 16:40, on Zulip):

but const fns are not supported in traits

gnzlbg (Nov 08 2019 at 16:40, on Zulip):

so it isn't that simple

gnzlbg (Nov 08 2019 at 16:41, on Zulip):

(but yeah, could be done with enough magic, e.g. , const fn mem::zeroed_safe<T: ZeroedSafe>() -> T that's an intrinsic or similar and making ZeroedSafe an auto trait, etc.)

gnzlbg (Nov 08 2019 at 16:42, on Zulip):

The problem is that zeroing the memory when allocating with a Vec is pointless if you are not going to exploit it later, e.g., by using Vec::set_len

gnzlbg (Nov 08 2019 at 16:43, on Zulip):

and then there is the difference in whether you want to always exploit it, so every reallocation should zero memory

gnzlbg (Nov 08 2019 at 16:43, on Zulip):

and when you only want to exploit it once at the beginning

gnzlbg (Nov 08 2019 at 16:46, on Zulip):

I personally think that we should have better support for Box<[T]>, better conversions from/to Vec<T> and Box<[T]>, and better support for T: ZeroedSafe, and with that, you get this for free:

struct Foo(i32); // ZeroedSafe due to the auto trait
let vec: Vec<Foo, A> = Box::array(Foo(0), N).into();
gnzlbg (Nov 08 2019 at 16:48, on Zulip):

Box::array can just do the same optimization that Vec internally does, if Foo: ZeroedSafe and if the value passed is all zeros, then just use A::alloc_zeroed.

gnzlbg (Nov 08 2019 at 16:49, on Zulip):

And well vec![Foo(0); N] would just expand to that and it would "just work"

Lokathor (Nov 08 2019 at 16:56, on Zulip):

1) it would have no methods
2) if zeroed isn't const that's an absolute failure of the standard library

Lokathor (Nov 08 2019 at 16:57, on Zulip):

the biggest problem is that while the vec![0; 50] version will happen to do what I want, it's completely opaque that something special is happening compared to my performance compared to vec![2; 50]

Lokathor (Nov 08 2019 at 17:01, on Zulip):

rather, i am well aware that zeroed isn't currently const, but if they refuse to make it const that's a total failure state for the library to end up in.

gnzlbg (Nov 08 2019 at 17:40, on Zulip):

@Lokathor mem::zeroed isn't const fn today

gnzlbg (Nov 08 2019 at 17:41, on Zulip):

ah yep

gnzlbg (Nov 08 2019 at 17:42, on Zulip):

it's completely opaque that something special is happening compared to my performance compared to vec![2; 50]

Why should people have to write different code to get the optimization ?

gnzlbg (Nov 08 2019 at 17:43, on Zulip):

If instead of a macro it would be a method Vec::new_with(val:T, size: usize,) the behavior would be identical

gnzlbg (Nov 08 2019 at 17:43, on Zulip):

Vec::new_with(0, 50) would be "fast" and Vec::new_with(42, 50) would be "slow"

gnzlbg (Nov 08 2019 at 17:44, on Zulip):

I'm not sure what alternative you are thinking of instead

gnzlbg (Nov 08 2019 at 17:45, on Zulip):

Either way, given an API that does the fast thing, adding a second API to do the same thing would not add as much value

gnzlbg (Nov 08 2019 at 17:48, on Zulip):

Also, the macro API solves many more problems than a method based API

gnzlbg (Nov 08 2019 at 17:51, on Zulip):

So currently we provide vec![Foo; N] that does what you want for some types, and also Vec::from_raw_parts that lets you exploit any feature that whatever allocator you are using supports. Supporting vec![Foo; N] for more types feels simpler to me from an extension point-of-view.

Lokathor (Nov 08 2019 at 17:53, on Zulip):

using the macro or a method should do the optimization if it can obviously

Lokathor (Nov 08 2019 at 17:53, on Zulip):

but having a method that _forces_ the optimization and the expense of being less general is good

Lokathor (Nov 08 2019 at 17:53, on Zulip):

then you know that if you change the design to the point where you can't use the old method, you might be giving up some performance

Lokathor (Nov 08 2019 at 17:54, on Zulip):

it should _not_ be the case that new_with(5) and new_with(0) are both slow, that's stupid

Lokathor (Nov 08 2019 at 17:55, on Zulip):

but if you care to go get that optimization you should know when you just made yourself ineligible for it later (possibly on accident)

gnzlbg (Nov 08 2019 at 18:13, on Zulip):

make a concrete proposal, but adding a new API has a cost, and it's not obvious to me that the cost is worth it

gnzlbg (Nov 08 2019 at 18:14, on Zulip):

something like fn zeroed_vec<T: Zeroable>(size: usize) -> Vec<T> { vec![T::zeroed(); size] } does the trick

gnzlbg (Nov 08 2019 at 18:16, on Zulip):

(it generates a compilation time error if T happens to not be Zeroable)

gnzlbg (Nov 08 2019 at 18:17, on Zulip):

also, none of this guarantees the optimization

gnzlbg (Nov 08 2019 at 18:17, on Zulip):

if your allocator dispatches the Alloc::alloc_zeroed method to Alloc::alloc + manual zeroing, you won't get it

gnzlbg (Nov 08 2019 at 18:18, on Zulip):

If you really want to be sure to get the optimization, using Vec::from_raw_parts while calling the allocator directly is probably the only choice

Lokathor (Nov 08 2019 at 18:24, on Zulip):

"an API" makes it sound huge, but really it's one method

Tim Diekmann (Nov 08 2019 at 18:48, on Zulip):

Another choice would be an extension trait. Simple and not needed in std/alloc itself

gnzlbg (Nov 13 2019 at 10:28, on Zulip):

"an API" makes it sound huge, but really it's one method

Its' just one of many methods one might add. For example, Vec::new() is a const fn, so you might want to use Vec::new() to get a Vec, and then use Vec::resize_zeroed in the first allocation.

gnzlbg (Nov 13 2019 at 10:29, on Zulip):

And so on, e.g., if you are using the Vec to implement a Deque, and want to pass the [tail, head] "uninitialized" slice to, e.g., the Bytes crate, then you want [tail, head] to always be zeroed, so you probably want to call Vec::push_zeroed within the Deque to implement Deque::push_back, etc.

gnzlbg (Nov 13 2019 at 10:31, on Zulip):

Another choice would be an extension trait. Simple and not needed in std/alloc itself

That's an option, but for some users adding a crate just to obtain an extension trait for this is probably overkill.

Last update: Nov 15 2019 at 10:35UTC