5

Is there a linux equivalent of _aligned_realloc?

I want to use realloc so I don't have to memcpy the data every time I resize it. Am I stuck with mmap? I only used mmap once is there a recommended way of implementing memory that will be resized a few times? I'm assuming I can't mix mmap with aligned_alloc and I would have to do memcpy for the first resize? (or always use mmap)

The realloc below doesn't always align. I tested under (64bit) linux using gcc and clang

#include<cstdlib>
#include<cstdio>
#define ALIGNSIZE 64
int main()
{
    for(int i = 0; i<10; i++)
    {
        void *p = aligned_alloc(ALIGNSIZE, 4096);
        void *p2 = realloc(p, 4096+2048); //This doesn't always align
        //void *p3 = aligned_alloc(ALIGNSIZE, 4096/24); //Doesn't need this line to make it unaligned. 

        if (((long)p & (ALIGNSIZE-1)) != 0 || ((long)p2 & (ALIGNSIZE-1)) != 0)
            printf("%d %d %d\n", i, ((long)p & (ALIGNSIZE-1)) != 0, ((long)p2 & (ALIGNSIZE-1)) != 0);
    }
}
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Eric Stotch
  • 141
  • 4
  • 19
  • No, realloc() does not guarantee an arbitrary alignment, only the machine word. – epx Nov 17 '20 at 23:27
  • 1
    Can you explain **in your question** why you need a specific alignment? `malloc`, `realloc` etc. are guaranteed to return an address that is suitable as a pointer to any data type on the current platform. This means on a 64-bit system it is at least aligned to an address suitable for a 64-bit value, i.e. a multiple of 8 bytes. – Bodo Nov 17 '20 at 23:40
  • 6
    @Bodo but not for 32/64/128 byte aligned simd-suitable data. – Artyer Nov 17 '20 at 23:41
  • @epx Not "machine word" but maximum natural alignment. Which is often greater than a word. – eerorika Nov 17 '20 at 23:42
  • @Bodo SIMD and occasionally I want data that starts at the beginning of a cache line (so I know another thread won't touch it by accessing data next to it) – Eric Stotch Nov 17 '20 at 23:59
  • @EricStotch You should [edit] your question to add information instead of writing comments. – Bodo Nov 18 '20 at 08:47
  • @Bodo Uhhh. I was answering your question. I could want 64byte aligned data for any reason and it wouldn't change the current question – Eric Stotch Nov 18 '20 at 18:24
  • 1
    @EricStotch In your case it might not be important, but often someone may come up with a completely different solution for your use case if enough background information is available. That's why it is useful to provide this information in the question. This will also help other people with similar questions to understand your question and to decide if it matches their use case. That's why I recommend to add the information from your comment to the question. – Bodo Nov 18 '20 at 21:58

2 Answers2

7

No, there is neither standard alternative in C++, nor in POSIX standard, nor in the GNU C library.

Here is a proof of concept using only standard functions:

void*
aligned_realloc_optimistic(
    void* ptr, std::size_t new_size, std::size_t alignment)
{
    void* reallocated = std::realloc(ptr, new_size);
    return is_aligned(reallocated, alignment) // see below
        ? reallocated
        : aligned_realloc_pessimistic(reallocated, new_size, new_size, alignment);
        // see below
}

As pointed out in the comments: This has the caveat that in the worst case std::realloc may fail to reuse the allcation and also happens to return an misaligned pointer, then we allocate twice.

We can skip attempting to realloc by just unconditionally allocting, copying and freeing which removes both the worst case of double allocation and the best case of no allocation:

void*
aligned_realloc_pessimistic(
    void* ptr, std::size_t new_size, std::size_t old_size, std::size_t alignment)
{
    void* aligned = std::aligned_alloc(alignment, new_size);
    std::memcpy(aligned, ptr, old_size);
    std::free(ptr);
    return aligned;
}

The obvious problem with this is that we have to know the old size which is was not required with the regular reallocation.

By relying on system specific functions, we can keep the optimal case of avoiding allocation and avoid double allocation and also not require knowing the old size:

void*
aligned_realloc_glibc(
    void* ptr, std::size_t new_size, std::size_t alignment)
{
    auto old_size = malloc_usable_size(ptr); // GNU extension
    return old_size >= new_size && is_aligned(ptr, alignment)
        ? ptr
        : aligned_realloc_pessimistic(ptr, new_size, old_size, alignment);
}

The helper function used above:

bool is_aligned(void* ptr, std::size_t alignment)
{
    std::size_t space = 1;
    return std::align(alignment, space, ptr, space);
}
eerorika
  • 232,697
  • 12
  • 197
  • 326
  • 1
    Sometimes does multiple reallocations... Perhaps it's better to `aligned_alloc`, `memcpy`, and `free` right from the start? – Anonymous1847 Nov 17 '20 at 23:40
  • @Anonymous1847 I suppose it depends on likelyhood of `realloc` being able to avoid allocation. This is the optimistic approach. If only there was a way to test whether `realloc` would allocate beforehand, we could both make use of it and avoid the case of allocating twice. – eerorika Nov 17 '20 at 23:41
  • Correct me if I'm wrong, but isn't this "proof-of-concept" dangerous? A pointer that gets readjusted by `std::align` would not be able to be `std::free`d because it's not the same pointer returned by any allocation function (`std::aligned_alloc`, `std::realloc`, etc) – Human-Compiler Nov 17 '20 at 23:53
  • @Human-Compiler The pointer won't be adjusted by `std::align` since it has not been given space to adjust. – eerorika Nov 17 '20 at 23:54
  • Ahh, I see. Is that guaranteed by the standard that a `0` value for `size` won't just return `nullptr` though? – Human-Compiler Nov 17 '20 at 23:55
  • @Human-Compiler Good question. We might as well use any other number to avoid that in case it is an issue. Changed to 1 – eerorika Nov 17 '20 at 23:56
  • I like this much more than my mmap thought. I hate that this is the way to do it. I guess another set of functions to add to my personal standard libs – Eric Stotch Nov 18 '20 at 00:04
  • @Anonymous1847 Now that I wrote it, I realize that the problem with just aligned_alloc, memcpy, and free is that we cannot know how much to copy. – eerorika Nov 18 '20 at 00:04
  • In general, you should expect, in any nontrivial-allocation-using program, that `realloc` has to allocate new memory and `memcpy` at least once per geometric growth of the size, so this solution really isn't bad. Just make sure you aren't increasing the size linearly, which can give really bad worst-case performance. Always increase by some factor, not necessarily doubling but at least += half or 1/4 of the current size. – R.. GitHub STOP HELPING ICE Nov 18 '20 at 01:01
4

Am I stuck with mmap?

If you want maximum flexibility, there is no better way than using the platform's low-level allocation mechanism.

In this case, you want mremap(), possibly with MREMAP_MAYMOVE. It does exactly what you need (if you need alignments bigger than a page you will need to take care of that manually).

Acorn
  • 24,970
  • 5
  • 40
  • 69