15

After I am studying about allocator for a few days by reading some articles
(cppreference and Are we out of memory) ,
I am confused about how to control a data-structure to allocate memory in a certain way.

I am quite sure I misunderstand something,
so I will divide the rest of question into many parts to make my mistake easier to be refered.

Here is what I (mis)understand :-

Snippet

Suppose that B::generateCs() is a function that generates a list of C from a list of CPrototype.
The B::generateCs() is used in B() constructor:-

class C          {/*some trivial code*/};
class CPrototype {/*some trivial code*/};
class B {
    public: 
    std::vector<C> generateCs() {  
        std::vector<CPrototype> prototypes = getPrototypes();
        std::vector<C> result;                     //#X
        for(std::size_t n=0; n < prototypes.size(); n++) {
            //construct real object  (CPrototype->C)
            result.push_back( makeItBorn(prototypes[n]) ); 
        }
        return result;
    }
    std::vector<C> bField;    //#Y
    B() {
        this->bField = generateCs();    //#Y  ; "generateCs()" is called only here
    }
    //.... other function, e.g. "makeItBorn()" and "getPrototypes()"
};

From the above code, std::vector<C> currently uses a generic default std::allocator.

For simplicity, from now on, let's say there are only 2 allocators (beside the std::allocator) ,
which I may code it myself or modify from somewhere :-

  • HeapAllocator
  • StackAllocator

Part 1 (#X)

This snippet can be improved using a specific type allocator.
It can be improved in 2 locations. (#X and #Y)

std::vector<C> at line #X seems to be a stack variable,
so I should use stack allocator :-

std::vector<C,StackAllocator> result;   //#X

This tends to yield a performance gain. (#X is finished.)

Part 2 (#Y)

Next, the harder part is in B() constructor. (#Y)
It would be nice if the variable bField has an appropriate allocation protocol.

Just coding the caller to use allocator explicitly can't achieve it, because the caller of constructor can only do as best as :-

std::allocator<B> bAllo;   
B* b = bAllo.allocate(1);   

which does not have any impact on allocation protocol of bField.

Thus, it is duty of constructor itself to pick a correct allocation protocol.

Part 3

I can't know whether an instance of B will be constructed as a heap variable or a stack variable.
It is matter because this information is importance for picking a correct allocator/protocol.

If I know which one it is (heap or stack), I can change declaration of bField to be:-

std::vector<C,StackAllocator> bField;     //.... or ....
std::vector<C,HeapAllocator> bField;     

Unfortunately, with the limited information (I don't know which it will be heap/stack, it can be both),
this path (using std::vector) leads to the dead end.

Part 4

Therefore, the better way is passing allocator into constructor:-

MyVector<C> bField; //create my own "MyVector" that act almost like "std::vector"
B(Allocator* allo) {
    this->bField.setAllocationProtocol(allo);  //<-- run-time flexibility 
    this->bField = generateCs();   
}

It is tedious because callers have to pass an allocator as an additional parameter,
but there are no other ways.

Moreover, it is the only practical way to gain the below data-coherence advantage when there are many callers, each one use its own memory chunk:-

class System1 {
    Allocator* heapForSystem1;
    void test(){
        B b=B(heapForSystem1);
    }
};
class System2 {
    Allocator* heapForSystem2;
    void test(){
        B b=B(heapForSystem2);
    }
};

Question

  • Where did I start to go wrong, how?
  • How can I improve the snippet to use appropriate allocator (#X and #Y)?
  • When should I pass allocator as a parameter?

It is hard to find a practical example about using allocator.

Edit (reply Walter)

... using another than std:allocator<> is only rarely recommendable.

For me, it is the core of Walter's answer.
It would be a valuable knowledge if it is reliable.

1. Are there any book/link/reference/evidence that support it?
The list doesn't support the claim. (It actually supports the opposite a little.)
Is it from personal experience?

2. The answer somehow contradict with many sources. Please defense.
There are many sources that recommend not to use std:allocator<>.

More specifically, are they just a "hype" that rarely worth using in real world?

Another small question :-
Can the claim be expanded to "Most quality games rarely use custom allocator"?

3. If I am in such rare situation, I have to pay the cost, right?

There are only 2 good ways:-

  • passing allocator as template argument, or
  • as a function's (including constructor) parameter
  • (another bad approach is to create some global flag about what protocol to use)

Is it correct?

TylerH
  • 20,799
  • 66
  • 75
  • 101
javaLover
  • 6,347
  • 2
  • 22
  • 67
  • Some readability, please) – Inline Jan 29 '17 at 13:27
  • 1
    Ok, I will try even more harder to make it easier to read. – javaLover Jan 29 '17 at 13:28
  • after `public/private/protected:` make a line break. Add more spaces. Change `a=b` to `a = b`, and `for(int i=0;i – Inline Jan 29 '17 at 13:30
  • Yes sir, I will fix it asap. – javaLover Jan 29 '17 at 13:30
  • change `class ClassName{` to `class ClassName {` or add a line break. `for(int n=0;n – Inline Jan 29 '17 at 13:34
  • 1
    `std::vector` `size()` method returns `std::vector::size_type` (aka std::size_t) `size()` description: http://www.cplusplus.com/reference/vector/vector/size/ . When should we use std::size_t: https://stackoverflow.com/questions/1951519/when-should-i-use-stdsize-t – Inline Jan 29 '17 at 13:36
  • @Inline I fixed it, don't know if it is now good enough. You can propose an edit too. Thank a lot about `size_t`. ... About `size()`, I think it will be optimized out, so I will not change it, OK? – javaLover Jan 29 '17 at 13:38
  • 1
    @javaLoverit it is not guaranteed, but possible https://stackoverflow.com/questions/3901630/performance-issue-for-vectorsize-in-a-loop – Inline Jan 29 '17 at 13:43
  • 1
    @Inline Awww, understand, but I have another personal reason - I don't want to sacrifice this little readability (reduce 1 line) for this little performance. – javaLover Jan 29 '17 at 13:46
  • @Inline As you proposed edit, B() is a constructor, no return type is required. – javaLover Jan 29 '17 at 13:52
  • *"seems to be a stack variable, so I should use stack allocator"* . Vector doesn't use its allocator to allocate memory for the class instance itself, but for the chunk of memory where it stores actual data. Where the variable of type `std::vector` is itself located is irrelevant to the question of which allocation strategy should be used for stored data. It's similar to `void f() { int* p = new int[42]; }` - the pointer `p` is on the stack, but the data it points to is on the heap. – Igor Tandetnik Jan 29 '17 at 14:53
  • Also, if you change line `#X` to `std::vector result;`, then you'd also need to change the return type of `generateCs()` and the type of `bField`, otherwise your code will no longer compile. – Igor Tandetnik Jan 29 '17 at 14:58
  • What do you mean by "correct allocation protocol"? What criteria, exactly, do you look at when you decide whether an "allocation protocol" (whatever that might be) is "correct" or "incorrect"? – Igor Tandetnik Jan 29 '17 at 15:01
  • @Igor Tandetnik "*`#X` seems to be a stackvariable*" because I think `result` will be copied to the return value, and the actual allocated data (which is in heap) will be deleted at the end of the function scope. (If there is no optimization.) – javaLover Jan 29 '17 at 15:03
  • 1
    `result` is moved to the return value of `generateCs`, which in turn is moved to `bField`. This is guaranteed by the C++11 and subsequent versions of the standard. Once `result` is populated, no further copying or allocation/deallocation is taking place. – Igor Tandetnik Jan 29 '17 at 15:06
  • @Igor Tandetnik "correct allocation protocol" <-- From my incomplete knowledge, I think I will follow this [link](http://www.swedishcoding.com/2008/08/31/are-we-out-of-memory/) (already cited in question). There are Dynamic / Persistent / One-Frame. I am still not sure ... I also want something like heaps, different heap would be used in a certain subsystem in my game. I know it would be slower but should be faster than a shared single heap for the whole program. – javaLover Jan 29 '17 at 15:06
  • Well, `B` can't know how the application is going to use it, so I don't see how it itself can decide whether to use an allocator optimized for "dynamic", "persistent" or "one-frame" scenarios (not that I quite grasp the point of the exercise, but let's assume for the sake of argument that there's merit to this distinction). If you want it to be flexible, make it a template taking `Allocator` type as a template parameter, and perhaps taking an allocator instance in constructor. Then pass it along to various `vector`s the class uses. – Igor Tandetnik Jan 29 '17 at 15:11
  • @Igor Tandetnik I have very limited experience. Do you think, most cases in real practice, `B` usually `new/delete` without signal-guide about allocation-protocol from caller? ... If so, most program nowadays would suffer some (partially avoidable) common fragmentation. (?) – javaLover Jan 29 '17 at 15:15
  • Custom allocators are very rarely used in practice, if that's what you are asking. – Igor Tandetnik Jan 29 '17 at 15:16
  • @Igor Tandetnik Do you also mean a single program usually use only 1 allocation protocol? That would be a precious knowledge for me. – javaLover Jan 29 '17 at 15:17
  • 1
    Typically, yes. Assuming that by "allocation protocol" you just mean `new` and `delete`, possibly wrapped in `std::allocator`. – Igor Tandetnik Jan 29 '17 at 15:19
  • 1
    You could read the allocator requirement in the ISO C++ standard [link] (http://open-std.org/JTC1/SC22/WG21/docs/papers/2016/n4618.pdf) (§17.5.3.5), and also the paragraph about memory resources (§20.12), because it seems that most of the functionality of allocator (but not those related to memory allocations) are about to be deprecated. It looks like allocators are going to be wrapper around memory resources. – Oliv Jan 29 '17 at 15:38
  • @Oliv It is too technical for a C++ newbie like me. I don't understand what it says at all. But thank! I may read it again when I am more professional. – javaLover Jan 30 '17 at 04:11
  • 1
    Regarding the popularity of custom allocators, it depends on company/project/team: I worked in two companies that used allocators . One large financial company that used them extensively, to the point of effectively banning the default, one small embedded company that only used them for pools. But I have not seen them anywhere else (not any kind of a statistic though, just personal experience) – Cubbi Jan 30 '17 at 04:18
  • @Cubbi In the financial company, they have to pass allocator as template/function parameter in various code lines, right? If there are > 1 allocators, that would be quite a mess, wouldn't it? If it is not a mess, I persuade you to post a (fake) sample as solution. XD – javaLover Jan 30 '17 at 04:49
  • Why would it be a mess? I'll try to write an answer I guess. – Cubbi Jan 30 '17 at 14:57
  • @Cubbi mess : e.g. example in Walter's answer **1.** add allocator as a field of template of class `B` **2.** A lot of `B` implementation will be in header, because `B` is a template type **3.** `B` is not the same as `B` , when passing `B` around, callers have to recognize it. (e.g. `static B Caller::createB(){}` and `std::vector>` are ugly ; I can not do `B b=B();` easily ) ... **4.** other bad side effects from enhancing `B` to `B<>` that even aliasing by `using` can't cure all the symptoms. – javaLover Jan 30 '17 at 16:16
  • 1
    None of that strikes me as "bad". It's just one more policy. But if your code is not using templates already, polymorphic allocators can be worked in without a paradigm shift. – Cubbi Jan 30 '17 at 16:30
  • @Cubbi Thank! That corrects my opinion. :) – javaLover Jan 31 '17 at 02:07

2 Answers2

8

In C++, the allocator used for the standard containers is tied to the container type (but see below). Thus, if you want to control the allocation behaviour of your class (including its container members), the allocator must be part of the type, i.e. you must pass it as a template parameter:

template<template <typename T> Allocator>
class B
{
public:
  using allocator = Allocator<C>
  using fieldcontainer = std::vector<C,allocator>;
  B(allocator alloc=allocator{})
  : bFields(create_fields(alloc)) {}
private:
  const fieldcontainer bFields;
  static fieldcontainer create_fields(allocator);
};

Note, however, that there is experimental polymorphic allocator support, which allows you change the allocator behaviour independently of the type. This is certainly preferable to designing your own MyVector<> template.

Note that using another than std::allocator<> is only recommendable if there is a good reason. Possible cases are as follows.

  1. A stack allocator may be preferred for small objects that are frequently allocated and de-allocated, but even the heap allocator may not be less efficient.

  2. An allocator that provides memory aligned to, say, 64bytes (suitable for aligned loading into AVX registers).

  3. A cache-aligned allocator is useful to avoid false sharing in multi-threaded situations.

  4. An allocator could avoid default initialising trivially constructible objects to enhance performance in multi-threaded settings.


note added in response to additional questions.

The article Are we out of memory dates from 2008 and doesn't apply to contemporary C++ practice (using the C++11 standard or later), when memory management using std containers and smart pointers (std::unique_ptr and std::shared_ptr) avoids memory leaks, which are the main source of increasing memory demand in poorly written code.

When writing code for certain specific applications, there may well be good reasons to use a custom allocator -- and the C++ standard library supports this, so this is a legitimate and appropriate approach. The good reasons include those listed already above, in particular when high performance is required in a multi-threaded environment or to be achieved via SIMD instructions.

If memory is very limited (as it may be on some game consoles), a custom allocator cannot really magically increase the amount of memory. So in this case the usage of the allocator, not the allocator itself, is most critical. A custom allocator may help reducing memory fragmentation, though.

Walter
  • 44,150
  • 20
  • 113
  • 196
2

It sounds like you are misunderstanding what a stack allocator is. A stack allocator is just an allocator that uses a stack, the data structure. A stack allocator can manage memory that is either allocated on the stack or the heap. It is dangerous to use if you don't know what you are doing as a stack allocator deallocates all the memory past the specified pointer when deallocate is called. You can use a stack allocator for when the most recently initialized element in a data structure is always the next one destroyed (or if you end up destroying them all at once in the end).

You can look at some of the std collections to see how they allow programmers to supply a specified allocator such as std::vector. They use an optional template argument so the user can choose the allocator class. It also allows you to pass the allocator in as an instance if you want to. If you don't, it instantiates one with the default constructor. If you don't choose an allocator class, then it uses the default allocater which just uses the heap. You could do the same.

template<typename C, typename Allocator = std::allocator<C> >
class B {

   vector<C, Allocator> bField;

   void generateCs() {  
     std::vector<CPrototype> prototypes = getPrototypes();
     for(std::size_t n=0; n < prototypes.size(); n++) {
         //construct real object  (CPrototype->C)
         bField.push_back( makeItBorn(prototypes[n]) ); 
     }
   }

   B(const Allocator& allo = Allocator()) : bField(allo) {
       generateCs();
   }
}

This allows the user to have control over allocation when they want to, but they also ignore it if they don't care

daz
  • 714
  • 6
  • 9
  • Roughly speaking, you think that Part 4 is correct about passing allocator as parameter, right? It is tedious, but there is nothing wrong about it. (?) To emphasize, B is a absolutely-normal class, but its constructor has "allocator" as parameter - this code doesn't smell bad. .... With this logic, every moderate-size class that has something like custom-array should have allocator as parameter. (??) – javaLover Jan 29 '17 at 14:43
  • Sure, you can do that, but you should really use a const reference instead of a pointer. I would also initialize bField in the initialization list with the allocator instead of using setAllocationProtocol (I haven't used this before). If you are worried about tediousness, then just make it optional so that it is only used if the programmer cares to use it. – daz Jan 29 '17 at 14:54
  • It is too hard to believe. Can you provide some reference/link/book about this? Is it a good practice? – javaLover Jan 29 '17 at 15:11
  • @javaLover Initialization in Initialization list is good practice, in other case you assign to pre initialized value. http://stackoverflow.com/questions/9903248/initializing-fields-in-constructor-initializer-list-vs-constructor-body – Inline Jan 30 '17 at 07:16
  • @Inline I meant it is hard to believe that "passing allocator into the constructor" is a good practice. It would work, but just not sound right, so I request some reference. I didn't mean that initializer `: bField(allo)` looks weird. – javaLover Jan 30 '17 at 07:30
  • @javaLover passing the allocator into a constructor is ok, the collections in the standard library have constructors that accept allocators. You usually only use these kinds of constructors for classes that create lots of objects such as data structures. For classes that aren't data structures, you usually shouldn't pass allocators into them. The code that is responsible for choosing the allocator is the code using the collection. The programmer at this point should have an idea of how that collection is used to make that choice. – daz Jan 30 '17 at 12:39
  • Sorry for not being clear. (I may be too panic because I am very new about this.) I know your code will work. I am afraid, whether it is a good practice (real practice in business world). If the answer is yes, why are you sure? It seems messy. (Yes, I know I can omit it like a default parameter, but it has some limitation, especially when there are already default parameters in places) Do you ever pass allocator in a real selling program or have ever seen a high-quality code that do this? (I have very limited experience.) Thanks for bearing with me. – javaLover Jan 30 '17 at 12:46
  • @javaLover Realistically, you would only make constructors with allocators for data structures. Most of the time, you are going to be using data structures that are already implemented for you. As a result, you are rarely making constructors with allocators. Also, for the vast majority of the time, the default allocator performs just fine. You only would use a custom allocator either when you are trying to optimize code that must run faster, or you need your code to run in real time, meaning that you can't afford to potentially wait a long time when asking th os for memory. – daz Jan 30 '17 at 13:10
  • @javaLover Most code doesn't use custom allocators since the default one works just fine most of the time. If you are unsure about using an allocator, then you are usually ok not using them. – daz Jan 30 '17 at 13:31