0

Say for example I have a function that takes some argument and a size_t length to initialize an array on stack inside a function.

Considering the following:

  • Strictly the length can only be on the range of 1 to 30 (using a fixed max buffer length of 30 is not allowed).
  • The array only stays inside the function and is only used to compute a result.
int foo(/*some argument, ..., ... */ size_t length) {
    uint64_t array[length];
    int some_result = 0;
    // some code that uses the array to compute something ...
    return some_result;
}

In normal cases I would use an std::vector, new or *alloc functions for this but... I'm trying to optimize since this said function is being repeatedly called through out the life time of the program, making the heap allocations a large overhead.

Initially using an array on stack with fixed size is the solution that I have come up with, but I cannot do this, for some reasons that I cannot tell since it would be rude.

Anyway I wonder If I can get away with this approach without encountering any problem in the future?

  • 2
    Well, the first problem is it may not compile. It is non-standard, though some compilers do accept it as a language extension. – Avi Berger Sep 14 '22 at 05:00
  • 2
    you can use std::pmr::vector – Artemy Vysotsky Sep 14 '22 at 05:03
  • 1
    Could you use a static array? Or, a static vector? – Daniel Langr Sep 14 '22 at 05:20
  • 1
    I'll add a note to @DanielLangr's suggestion that this would render the function not thread safe. `static std::vector array; array.resize(length);` might pass the undisclosed rude test and only reallocate until peak size is reached. – Avi Berger Sep 14 '22 at 05:40
  • 1
    *using a fixed max buffer length of 30 is not allowed* this is unfortunate because it's probably the best solution unless the function is recursive. – user4581301 Sep 14 '22 at 05:46
  • @ArtemyVysotsky How would that help? It still doesn't allow allocating a variable-length block of stack memory. There is no standard method of doing that. – user17732522 Sep 14 '22 at 05:54
  • What exactly are your requirements here? What specifically is the requirement that does not allow a fixed-size stack buffer but does allow a runtime-sized stack buffer? If there is a requirement such as a maximum stack frame size, then using a runtime-size stack buffer with the same potential size should also be disallowed. – user17732522 Sep 14 '22 at 05:57
  • 1
    @user17732522 My guess is that it is a non-technical requirement to satisfy a supervisor's set ideas about the technology. Hence why it might be _rude_ to comment on. – Avi Berger Sep 14 '22 at 06:05
  • 1
    *making the heap allocations a large overhead* - That's an assumption you haven't proved. Have you benchmarked the delta in performance between using a fixed length buffer of size 30 vs a consistent new/delete pattern for allocation (or equivalent use of vector). – selbie Sep 14 '22 at 07:10

2 Answers2

1

In the rare cases where I've done some image processing with large fixed sized temp buffers or just wanted to avoid the runtime for redundant alloc/free calls, I've made my own heap.

It doesn't make a lot of sense for small allocations, where you could just use the stack, but you indicated your instructor said not to do this. So you could try something like this:

template<typename T>
struct ArrayHeap {
    
    unordered_map<size_t, list<shared_ptr<T[]>>> available;
    unordered_map<uint64_t*, pair<size_t, shared_ptr<T[]>>> inuse;

    T* Allocate(size_t length) {
        auto &l = available[length];
        shared_ptr<T[]> ptr;
        if (l.size() == 0) {
            ptr.reset(new T[length]);
        } else {
            ptr = l.front();
            l.pop_front();
        }
        inuse[ptr.get()] = {length, ptr};
        return ptr.get();
    }

    void Deallocate(T* allocation) {
        auto itor = inuse.find(allocation);
        if (itor == inuse.end()) {
            // assert
        } else {
            auto &p = itor->second;
            size_t length = p.first;
            shared_ptr<T[]> ptr = p.second;
            inuse.erase(allocation);

            // optional - you can choose not to push the pointer back onto the available list
            // if you have some criteria by which you want to reduce memory usage
            available[length].push_back(ptr);
        }
    }
};

In the above code, you can Allocate a buffer of a specific length. The first time invoked for a given length value, it will incur the overhead of allocating "new". But when the buffer is returned to the heap, the second allocation for the buffer of the same length, it will be fast.

Then your function can be implemented like this:

ArrayHeap<uint64_t> global_heap;

int foo(/*some argument, ..., ... */ size_t length) {
    uint64_t* array = global_heap.Allocate(length);

    int some_result = 0;
    // some code that uses the array to compute something ...

    global_heap.Deallocate(array);
    return some_result;
}
selbie
  • 100,020
  • 15
  • 103
  • 173
1

Personally I would use a fixed size array on the stack, but if there are reasons to prohibit that then check if there are any against the alloca() method.

man 3 alloca

Outtruder
  • 227
  • 1
  • 4