Канонический способ нарезать / вырезать / разложить один malloc на несколько массивов с различными типами / выравниваниями? - PullRequest
3 голосов
/ 12 января 2012

Справочная информация : у меня есть подпрограмма C99, для которой требуется временное хранение различных типов данных с различными требованиями к выравниванию. В настоящее время я звоню posix_memalign несколько раз, что а) приводит к большим накладным расходам и б) не гарантирует, что мои временные архивы имеют хорошую память. Я не могу упаковать временные файлы в одну структуру, так как требования к размеру известны только во время выполнения.

Вопрос : Я хочу позвонить malloc (или что-то подобное) один раз с достаточно большим размером, чтобы я мог нарезать / разделять / разбивать отдельные указатели с требуемым выравниванием. Есть ли канонический способ выполнить эту задачу в C99?

Возможный (но маловероятный) ответ для конкретного примера : скажем, я хочу выделить достаточно места для char[3], double[m] с 16-байтовым выравниванием, int и float[n] с 16-байтовым выравниванием и что мне нужно их в памяти в таком порядке. Пожалуйста, не обращайте внимания на то, что заказ глуп и пример придуман. Мой фактический вариант использования - это управляющая структура, за которой следуют несколько временных массивов смешанных целочисленных / числовых типов с выравниваниями, позволяющими выполнять операции SSE.

Используя идеи Как распределить выровненную память только с использованием стандартной библиотеки? можно сделать:

// Several unnecessary values (e.g. alignment_c) are holdovers
// from the macros generating this logic.

// ell, m, and n are sizes known only at runtime

const size_t    datasize_c  = 3*sizeof(char);
const size_t    alignment_c = __alignof__(char);
const size_t    pad_c       = alignment_c - 1;
const uintptr_t mask_c      = ~(uintptr_t)(alignment_c - 1);

const size_t    datasize_d  = ell*sizeof(double);
const size_t    alignment_d = __alignof__(double) > 16 ? __alignof__(double) : 16;
const size_t    pad_d       = alignment_d - 1;
const uintptr_t mask_d      = ~(uintptr_t)(alignment_d - 1);

const size_t    datasize_i  = m*sizeof(int);
const size_t    alignment_i = __alignof__(int);
const size_t    pad_i       = alignment_i - 1;
const uintptr_t mask_i      = ~(uintptr_t)(alignment_i - 1);

const size_t    datasize_f  = n*sizeof(float);
const size_t    alignment_f = __alignof__(float) > 16 ? __alignof__(float) : 16;
const size_t    pad_f       = alignment_f - 1;
const uintptr_t mask_f      = ~(uintptr_t)(alignment_f - 1);


const size_t p_parcel = (datasize_c + pad_c)
                      + (datasize_d + pad_d)
                      + (datasize_i + pad_i)
                      + (datasize_f + pad_f) ;

void   * const p = malloc(p_parcel) ;
char   *       c = (void *) (((uintptr_t)(p      ) + pad_c & mask_c));
double *       d = (void *) (((uintptr_t)(c + ell) + pad_d & mask_d));
int    *       i = (void *) (((uintptr_t)(d + m  ) + pad_i & mask_i));
float  *       f = (void *) (((uintptr_t)(i + n  ) + pad_f & mask_f));

// check if p is NULL, use (c, d, i, f), then free p

Я считаю, что эта возможность функционально верна, но мне интересно, есть ли у кого-нибудь лучший, чище, короткий путь?

Я думаю, что подходы, использующие struct hack, неосуществимы, потому что я могу гарантировать выравнивание только одного массива, используя malloc одного взлома struct. Мне все еще нужно три malloc вызова для трех отдельных структурных хаков.

Наконец, я был бы рад предоставить макросы, которые генерируют этот беспорядок, если кто-то хочет их.

1 Ответ

2 голосов
/ 12 января 2012
struct malloc_wrapper {
    char *storage;
    size_t capacity;
    size_t first_free;
};

инициализируйте его:

void init_malloc_wrapper(struct malloc_wrapper *mw, size_t cap) { // or int
    if (!mw) {
        perror("No struct malloc_wrapper passed\n");
        exit(EXIT_FAILURE);
    }
    mw->storage = malloc(cap);
    if (mw->storage == NULL) {
        perror("allocation failed\n");
        exit(EXIT_FAILURE); // or return 0;
    }
    mw->capacity = cap;
    mw->first_free = 0;
    // return 1;
}

void clear_malloc_wrapper(struct malloc_wrapper *mw) {
    if (!mw) {
        return;
    }
    if (mw->storage) {
        free(mw->storage);
    }
    mw->storage = NULL;
    mw->capacity = 0;
    mw->first_free = 0;
}

разложить кусок памяти:

// First version assumed that `malloc` returns memory aligned suitable for all requests.
// That's probably true, but just in case, we have to be a bit more cautious.
void *deal_out(struct malloc_wrapper *mw, size_t align, size_t size, size_t nmemb) {
    if (!align || (align & (align-1))) {
        perror("invalid alignment\n");
        exit(EXIT_FAILURE);
    }
    if (!mw) {
       perror("No malloc_wrapper\n");
        exit(EXIT_FAILURE);
    }
    uintptr_t start = ((uintptr_t(mw->storage) + mw->first_free + align-1) & ~(align-1);
    size_t offset = start - (uintptr_t)mw->storage;
    if ((mw->capacity - offset) < size*nmemb) {
        return NULL;   // not enough space
    }
    mw->first_free = offset + size*nmemb;
    return (void *)start;
}

В зависимости от паранойи может потребоваться еще несколько проверок. При этом вы можете раздавать подходяще выровненные куски по мере необходимости. Но если вы не выделили достаточно места для хранения при инициализации, обработать дальнейшие запросы несколько сложно. И освобождение - это все или ничего. Так что это может не быть улучшением.

Редактировать: Чтобы справиться с проблемой начальной емкости, можно добавить список использованных блоков памяти, либо только указатели, либо память, смещение и оставшуюся емкость. Если поступает новый запрос, превышающий доступную емкость, добавьте текущий блок памяти в список и выделите новый. Если запоминается оставшаяся емкость использованных блоков, для небольших запросов можно просмотреть список старых блоков, чтобы определить, подходит ли он одному из них.

В первой версии deal_out не учитывалась возможность выравнивания, требующая большего, чем выравнивание возвращаемых значений malloc, исправленных сейчас.

...