cameron314 / concurrentqueue

A fast multi-producer, multi-consumer lock-free concurrent queue for C++11
Other
10k stars 1.69k forks source link

EXPLICIT_INITIAL_INDEX_SIZE not working #357

Closed adoodabi7 closed 1 year ago

adoodabi7 commented 1 year ago

Hi I want to put a limit on memory consumption of each explicit producer. I don't want to use MAX_SUBQUEUE_SIZE because it causes both enqueue and try_enqueue to fail and i just want try_enqueue to fail. I noticed there is something called EXPLICIT_INITIAL_INDEX_SIZE but after changing its value it did nothing. Then i looked into ExplicitProducer constructor and i noticed it gets overwritten to a large value. Is this not a bug? Why would you set it to a large number? How can i make use of EXPLICIT_INITIAL_INDEX_SIZE?

cameron314 commented 1 year ago

I think I see what you're referring to. The minimum explicit index size must be large enough to fit all the blocks pre-allocated by the ConcurrentQueue constructor.

However, I'm not sure I understand what you're looking for. If enqueue succeeds when try_enqueue would not, that means the sub-queue can grow. So what is it exactly that you want to limit?

adoodabi7 commented 1 year ago

The minimum explicit index size must be large enough to fit all the blocks pre-allocated by the ConcurrentQueue constructor. Why is that? So if one of the explicit producers is very active and gets all the block from the pool, what happens to the rest of the producers? They just wait on a free block forever? It's not like the explicit producers are going to give any block back to the original pool. What i'm looking for is something similar to IMPLICIT_INITIAL_INDEX_SIZE but for explicit producers. I want to limit the number of blocks that an explicit producers can transfer from the original shared pool to its own pool. For example if a producer already got 256 blocks from the original pool, it can't take anymore. If it still wants to push something, it should use enqueue so it will allocate its own blocks and if it uses try_enqueue it will fail because it can't allocate.

cameron314 commented 1 year ago

The minimum is in place to prevent misconfiguration where the EXPLICIT_INITIAL_INDEX_SIZE is not configured, but the number of blocks pre-allocated is. The worst case is a single explicit producer, so it needs to be able to use all the blocks.

I think you could get the behaviour you're after by configuring the EXPLICIT_INITIAL_INDEX_SIZE, but not pre-allocating any blocks in the main constructor.

cameron314 commented 1 year ago

Actually no, because as soon as enqueue is called while you're at max capacity, the index will grow doubling in capacity.

adoodabi7 commented 1 year ago

I think the best way for me is to use MAX_SUBQUEUE_SIZE. It's clean and explicitly says what it does. Also i thought the EXPLICIT_INITIAL_INDEX_SIZE is like a maximum but after reading the comments i understand what it does and it's not at all what i thought, sorry for that.

My main problem was that after switching from implicit to explicit producers, all of a sudden my program was consuming five times more memory. I though the reason is EXPLICIT_INITIAL_INDEX_SIZE but now i'm not sure what the actual reason is. It is fixed by setting MAX_SUBQUEUE_SIZE to a sane value.

adoodabi7 commented 1 year ago

Hello again Can you set a default for EXPLICIT_INITIAL_INDEX_SIZE, like what you did with MAX_SUBQUEUE_SIZE (default is max); and if users configure EXPLICIT_INITIAL_INDEX_SIZE to a different value, don't overwrite it in constructor? Because i don't think there is a way to achieve what i want (putting a limit only on try_enqueue and not on enqueue) in current code and the only way i know that works is abusing the EXPLICIT_INITIAL_INDEX_SIZE.

cameron314 commented 1 year ago

When calling enqueue the index would grow anyway. I agree, I don't think there's a way to accomplish exactly what you want here. Could you get away with using two queues instead, one bounded and one not?

adoodabi7 commented 1 year ago

I think using two queues would make my code complicated. I have two more questions

  1. If the EXPLICIT_INITIAL_INDEX_SIZE gets overwritten every time, what is the point of it then? It is guaranteed that this value is never smaller than queue size (in the constructor). And it doesn't make sense if it's bigger than the queue size, for example your queue size is 1000 and you expect each explicit producer to have 2000 items?
  2. What happens if one of the explicit producers is super active and gets all the blocks from the pool? I think there should be a limit here. For example if my queue size is 1000 and i have maximum 4 explicit producers, there will be a pool of 4000 blocks and each explicit producer should be able to get at max 1000 blocks from the pool.
cameron314 commented 1 year ago
  1. It's admittedly not super useful, but it allows the index size to be set when not specifying a pre-allocated number of blocks in the constructor. Setting a larger size than what's used in the constructor could be useful if you expect to add more blocks later.
  2. The sizes passed to the constructor ensure minimums are met. One sub-queue could indeed hoard all 4000 blocks, but the queue as a whole was only guaranteed 1000 blocks.
adoodabi7 commented 1 year ago

Is there a way to prevent the active producer from getting all the blocks?

cameron314 commented 1 year ago

Not with the current design.

adoodabi7 commented 1 year ago

I'm going to use the MAX_SUBQUEUE_SIZE for now. Can you add a feature to put a limit on try_enqueue only? I want enqueue to be able to go up as much as it wants but at the same time try_enqueue should stop at some point.

cameron314 commented 1 year ago

That sounds doable, though only applicable to a very narrow use case. Won't have time to implement this in the short term future though, sorry.