0

After using an std vector to hold my move list for my chess engine, I realised that because chess has an averaging factor of 35 (i.e. something like 35 legal moves from a typical position), the vector was resizing a lot, thereby negatively impacting the performance of the move generator. One way around this (which I only realised today) is by reserving a minimum capacity for the vector. However, the possibility of using alloca() attracted my attention. This is probably a very simple question, but the documentation on alloca() is pretty scarce, with very very few examples of how to use it.

So an answer from Allocation of variable-sized class mentions that stack allocations cannot be resized. Nevertheless, would the following be valid?

    struct MoveList
    {
       MoveList(): capacity(10)
       {
          moves = (Move*) alloca(sizeof(Move) * 10);
       }
       void resize()
       {
          capacity *= 2;
          moves = (Move*) alloca(sizeof(Move) * capacity );
       }
       void push_back();
       Move* moves;
       int size;
       int capacity;
    }

Specifically, if say, a capacity of 10 is insufficient from alloca() the first time around, is it syntactically valid (and correct) to simply call alloca() again to allocate some more memory? Will this method give better performance (compared to std vector with reserve()), or only increase the chance of a stack overflow? My Move struct requires some 28 bytes of memory, and I suspect that the engine will recursively search (using alpha-beta) to maybe 7 or 8 ply maximum so that perhaps some 28 * 35 * 8 ~ 8kb max will be used from the stack. I read somewhere that typically stacks have a limit of 1Mb so this should not be too much right?

Edit: Thanks to the answers below, I realise now that my initial understanding of what alloca() did was wrong. However, I would still like to know if it is possible to use alloca() in the manner below:

    int main()
    {
        int* arr = (int) alloca(sizeof(int));
        arr = alloca(sizeof(int) * 2 ));//is this 'resizing' valid?
    }
Community
  • 1
  • 1
  • 3
    Why aren't you just using an `array` or `vector`? For example `Move* moves = new Move[10];` – Cory Kramer Jun 06 '14 at 12:41
  • [Why is alloca not considered good practice](http://stackoverflow.com/questions/1018853/why-is-alloca-not-considered-good-practice), plus `alloca` does not call constructors on your C++ classes. – crashmstr Jun 06 '14 at 12:44
  • Ah, I would like to have a fast move generator, if possible :) Your comment is in fact part of my question. Will the use of alloca() give better performance than std vector with reserve()? – UserUnspecified Jun 06 '14 at 12:45
  • 1
    "Resizing" in your example where you use `alloca` for `int`s is valid, but it is not resizing. You allocated on the stack twice - first time the area for one `int`, second time the area for two `int`s. So actually you allocated memory for three `int`s. – Wojtek Surowka Jun 06 '14 at 13:02
  • @Wojtek Surowka: But now is `arr[1]` the last element of `arr` or the middle element? I mean to say does `arr` now have two elements or three? – UserUnspecified Jun 06 '14 at 13:16
  • 1
    @UserUnspecified no differences with `malloc` here. After last call it points to [2], so two elements. Previous pointer to [1] is lost, because its address was in `arr` and now replaced - but it still on stack, consuming memory. Only difference from malloc is that upon return it will be dropped, while `malloc`ed value must be manually freed so it would be memory leak. – keltar Jun 06 '14 at 13:22

2 Answers2

3

The function alloca allocates memory on the stack, and the memory is no longer available as soon as the function in which alloca was called returns. It means that as soon as MoveList constructor or resize function returns, the memory is no longer available. Your assumption that somehow you will be able to use this memory during lifetime of MoveList object is wrong.

The best option for you is to use std::vector and reserve.

Wojtek Surowka
  • 20,535
  • 4
  • 44
  • 51
  • Thanks for the very informative answer! However, is the syntax valid? I should actually be able to call alloca() within the function that creates and uses MoveList directly. – UserUnspecified Jun 06 '14 at 12:53
  • 1
    The syntax is valid, yet you open Pandora's box if you try to use alloca in this way. For starters, the `Move` constructor will not be called. Try to get working version using standard techniques first, and only when it is not efficient enough try to use tricks. Starting with tricks is asking for trouble. – Wojtek Surowka Jun 06 '14 at 12:57
  • Premature optimisation is the root of all evil, eh? Right, I'll stick with std::vector and reserve then. – UserUnspecified Jun 06 '14 at 13:01
  • @UserUnspecified constructors problem could be easily resolved, but the problem is - there is no way to free `alloca`'ted memory other than returning. So if you replacing pointer with newly `alloca`ted one, old pointer is lost on stack but still uses memory. Many subsequent resizes will thrash stack quite heavily, giving good chance for overflow. Variable-length arrays don't have this problem because they're scope-based, not function-based; but not every compiler supports VLA. – keltar Jun 06 '14 at 13:05
  • @keltar: Yes, variable length arrays was the first solution I had in mind, but I don't think my current compiler supports c99 :( The point you have raised about stack overflow is a good one and incidentally another query I had – UserUnspecified Jun 06 '14 at 13:12
1

You don't seem to understand what the non-standard alloca() expression actually does. It allocates memory in the stack frame of the calling function. In your case, it means that the lifetime of allocated space (in this case assigned to the moves member) is that of the constructor:

   MoveList(): capacity(10)
   {
      moves = (Move*) alloca(sizeof(Move) * 10);
      ... moves is valid from this point

      // "moves" stops being valid at this point
   }

Since the rest of your constructor is empty, this is not what you intended. (Also, alloca() has a side effect of preventing the calling function to be inlined - another unintended side effect.) In other words, to answer the question from the title, this use of alloca() is not valid.

Even if it were somehow made valid, since alloca() has no counterpart to resize or free the allocated memory (nor can it have one, due to how it works), it is highly inappropriate for any situation where the area needs to be resized — which is exactly how you are trying to use it.

The resizing capacity of an std::vector normally already factors in exponential growth, so adding your own is unnecessary. If you are unsure, measure performance and see what works for you. Maybe for your case it would be sufficient to call std::vector<T>::reserve() to make sure that the vector starts off with an optimistic size, removing the need for reallocations. Or use an std::deque, which never reallocates elements (at the costs of slightly slower access).

user4815162342
  • 141,790
  • 18
  • 296
  • 355
  • Alloca prevents the calling function to be inlined? Is this because the compiler refuses to inline the function or because it is too dangerous for the programmer to suggest an inline to the compiler? – UserUnspecified Jun 06 '14 at 13:02
  • @UserUnspecified The compiler refuses to inline it, at least [GCC does](http://www.spinics.net/lists/gcchelp/msg03213.html). – user4815162342 Jun 06 '14 at 14:44
  • @UserUnspecified If you think about it, it wouldn't make much sense to inline a function using `alloca`, which is about efficient **stack** allocation and timely deallocation by stack unwinding. Imagine calling a function `f()` that uses `alloca` in a loop. Inlining the call to `f()` would quickly cause a stack overflow because `alloca` would keep growing the caller's stack. Keeping `f()` out-of-line makes sure that the space consumed by `alloca` calls in `f()` is released on each exit from `f()`. – user4815162342 Jun 06 '14 at 14:45
  • @user4815162342 yes but inline still isn't forbidden. Compiler just usually don't want to, because alloca call is quite heavy factor. But with `__attribute__ ((always_inline))` inline happens, at least in my tests. So, just one of many things compiler processes as "better not inline". – keltar Jun 06 '14 at 16:29
  • To make my point clear - compiler **can** inline function and still reset its stack (according to scope) after 'leaving' function, just like it does with VLA. Don't know if any compiler actually doing this, though. – keltar Jun 06 '14 at 16:32
  • @keltar Interesting. Have you tested that a combination of `__attribute__ ((always_inline))` and `alloca()` actually does the right thing with respect to, say, the inlined function being called in a loop? In other words, is the compiler actually implementing VLA-like semantics for `alloca()`, or is mixing the `alloca` and forced inlining a case of "then don't do that"? – user4815162342 Jun 06 '14 at 16:56
  • @keltar A simple test with g++ 4.8.2 shows that the compiler miscompiles an `always_inline` that uses `alloca()` to allocate from the caller's stack without cleaning up on "returns". (VLAs are, of course, handled correctly.) – user4815162342 Jun 06 '14 at 17:10
  • @user4815162342 I haven't told it will, only that it could - although, depending on implementation method, it may reduce effectiveness of inlining itself. No compiler I use implements scoped access semantics for alloca (and I really miss that feature, since I dislike VLAs implicity and their effect on sizeof). But even so, both clearing stack and thrashing it may be considered correct behaviour - especially when programmer explicitly insisted on inlining. I also find it interesting that BSD doc says alloca is freed upon leaving scope *if* at least one VLA is used within same scope (true!). – keltar Jun 06 '14 at 18:04