Временная сложность выделения неинициализированной памяти в стеке в функции constexpr?

Обычно, если программа на C++ выделяет большой массив в стеке, большая часть которого в конечном итоге не инициализируется, я бы сказал, что получение этого массива по сути бесплатное (вы просто перемещаете указатель стека или еще много чего) и платите только за инициализация, которую вы на самом деле делаете.

Например, предположим, что я хэширую n элементов в n блоков, а n здесь является константой времени компиляции. Потенциально каждое ведро может получить все n предметов. Таким образом, мы выделяем память для n блоков размером n в стеке. Поскольку в контейнеры на самом деле помещается только n элементов, я бы сказал, что время выполнения похоже на O(n) - конечно, мы выделили O(n^2) памяти в стеке, но только n из этих ячеек фактически были инициализированы.

Однако, когда код выполняется во время компиляции как побочный продукт constexpr, он на самом деле не работает со стековыми фреймами и таким способом, которым он фактически будет работать на металле. Это как-то интерпретируется компилятором.

Когда я создаю неинициализированный массив в стеке, интерпретатор садится и выделяет столько памяти точно и "фиксирует" некоторые конкретные неопределенные значения для неинициализированных ячеек? Если это так, это будет означать, что производительность во время компиляции, вероятно, больше похожа на O(n^2), чем на O(n). Или это "эффективно" обрабатывает неинициализированные распределения как-то?


Конечно, практический ответ, он может варьироваться, и вы должны измерить, но я спрашиваю конкретно, есть ли какое-либо руководство или объяснение высокого уровня.


Код, на который я смотрю, находится в библиотеке здесь: https://github.com/serge-sans-paille/frozen

Более конкретно в этом заголовке: https://github.com/serge-sans-paille/frozen/blob/master/include/frozen/bits/pmh.h

Вот рассматриваемая функция, она пытается сделать идеальное хеширование во время компиляции, используя C++14 constexpr. Линия, о которой я особенно беспокоюсь:

// Step 1: Place all of the keys into buckets
cvector<bucket<M>, M> buckets;

Здесь Cvector является std::array, а также bucket также cvector:

template <size_t N>
using bucket = cvector<std::size_t, N>;

basic_types.h:)

template <class T, std::size_t N>
class cvector {
  T data [N] = {}; // zero-initialization for scalar type T, default-initialized otherwise
  std::size_t dsize = 0;

public:
  ...
};

Вот соответствующая функция (в pmh.h):

template <std::size_t M, class Item, std::size_t N, class Hash, class Key>
pmh_tables<M, Hash> constexpr make_pmh_tables(const std::array<Item, N> &
                                                               items,
                                                           Hash const &hash,
                                                           Key const &key) {
  // Step 1: Place all of the keys into buckets
  cvector<bucket<M>, M> buckets;
  cvector<uint64_t, M> values;
  cvector<int64_t, M> G;

  auto *it = &std::get<0>(items);

  for (std::size_t i = 0; i < N; ++i)
    buckets[hash(key(it[i])) % M].push_back(1 + i);

  // Step 2: Sort the buckets and process the ones with the most items first.
  bits::quicksort(buckets.begin(), buckets.begin() + M - 1, bucket_size_compare{});

  std::size_t b = 0;
  for (; b < M; ++b) {
    auto &bucket = buckets[b];
    auto const bsize = bucket.size();
    if (bsize <= 1)
      break;

    // Repeatedly try different values of d until we find a hash function
    // that places all items in the bucket into free slots
    std::size_t d = 1;
    cvector<std::size_t, M> slots;

    while (slots.size() < bsize) {
      auto slot = hash(key(it[bucket[slots.size()] - 1]), d) % M;

      if (values[slot] != 0 || !all_different_from(slots, slot)) {
        slots.clear();
        d++;
        continue;
      }

      slots.push_back(slot);
    }

    G[hash(key(it[bucket[0] - 1])) % M] = d;
    for (std::size_t i = 0; i < bsize; ++i)
      values[slots[i]] = bucket[i];
  }

  // Only buckets with 1 item remain. Process them more quickly by directly
  // placing them into a free slot. Use a negative value of d to indicate
  // this.
  cvector<int64_t, M> freelist;

  for (std::size_t i = 0; i < M; ++i)
    if (values[i] == 0)
      freelist.push_back(i);

  for (std::size_t i = b; i < M; ++i) {
    auto &bucket = buckets[i];
    if (bucket.size() == 0)
      break;
    auto slot = freelist.back();
    freelist.pop_back();
    // We subtract one to ensure it's negative even if the zeroeth slot was
    // used.
    G[hash(key(it[bucket[0] - 1])) % M] = -slot - 1;
    values[slot] = bucket[0];
  }
  for (std::size_t i = 0; i < M; ++i)
    if (values[i])
      values[i]--;

  return {G.to_array(), values.to_array(), hash};
}

0 ответов

Другие вопросы по тегам