Program Listing for File vector.h

Return to documentation for file (include/shad/data_structures/vector.h)

//===------------------------------------------------------------*- C++ -*-===//
//
//                                     SHAD
//
//      The Scalable High-performance Algorithms and Data Structure Library
//
//===----------------------------------------------------------------------===//
//
// Copyright 2018 Battelle Memorial Institute
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy
// of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
//
//===----------------------------------------------------------------------===//

#ifndef INCLUDE_SHAD_DATA_STRUCTURES_VECTOR_H_
#define INCLUDE_SHAD_DATA_STRUCTURES_VECTOR_H_

#include <algorithm>
#include <atomic>
#include <cstdint>
#include <iterator>
#include <limits>
#include <memory>
#include <stdexcept>
#include <tuple>
#include <utility>
#include <vector>

#include "shad/data_structures/abstract_data_structure.h"
#include "shad/data_structures/buffer.h"
#include "shad/runtime/runtime.h"

namespace shad {

template <typename T, typename Allocator = std::allocator<T>>
class Vector : public AbstractDataStructure<Vector<T, Allocator>> {
  template <typename ValueType>
  class Iterator;

 public:
  using allocator_type = Allocator;
  using value_type = T;
  using difference_type = typename allocator_type::difference_type;
  using size_type = typename allocator_type::size_type;
  using iterator = Iterator<T>;
  using const_iterator = Iterator<const T>;
  using ObjectID =
      typename AbstractDataStructure<Vector<T, Allocator>>::ObjectID;

  static_assert(
      (std::is_same<typename allocator_type::value_type, value_type>::value),
      "Allocator::value_type must be the same as value_type");


  size_type Size() const noexcept;

  size_type MaxSize() const noexcept;

  size_type Capacity() const noexcept;

  bool Empty() const noexcept;

  void Reserve(size_type n);

  void Resize(size_type n);



  value_type At(size_type n) const;

  value_type operator[](size_type n) const;

  value_type Front() const;

  value_type Back() const;

  void AsyncAt(rt::Handle &handle, size_type n, T *result) const;



  void Clear() noexcept;

  void PushBack(const T &value);

  iterator InsertAt(shad::Vector<T, Allocator>::size_type position,
                    const shad::Vector<T, Allocator>::value_type &value);

  template <typename InputIterator>
  iterator InsertAt(size_type position, InputIterator begin, InputIterator end);

  void AsyncInsertAt(rt::Handle &handle,
                     shad::Vector<T, Allocator>::size_type position,
                     const shad::Vector<T, Allocator>::value_type &value);

  template <typename InputIterator>
  void AsyncInsertAt(rt::Handle &handle, size_type position,
                     InputIterator begin, InputIterator end);

  void BufferedInsertAt(const size_type pos, const value_type &value);

  void BufferedAsyncInsertAt(rt::Handle &handle, const size_type pos,
                             const value_type &value);

  void WaitForBufferedInsert() { buffers_.FlushAll(); }



  template <typename ApplyFunT, typename... Args>
  void Apply(const size_type position, ApplyFunT &&function, Args &... args);

  template <typename ApplyFunT, typename... Args>
  void AsyncApply(rt::Handle &handle, const size_type position,
                  ApplyFunT &&function, Args &... args);

  template <typename ApplyFunT, typename... Args>
  void ForEachInRange(const size_type first, const size_type last,
                      ApplyFunT &&function, Args &... args);

  template <typename ApplyFunT, typename... Args>
  void AsyncForEachInRange(rt::Handle &handle, const size_type first,
                           const size_type last, ApplyFunT &&function,
                           Args &... args);


  ObjectID GetGlobalID() const { return oid_; }

  ~Vector() { _clear(); }

  void BufferEntryInsert(const std::tuple<size_type, value_type> entry) {
    auto blockOffsetPair = _blockOffsetFromPosition(std::get<0>(entry));
    size_type localBlock = _globlalBlockToLocalBlock(blockOffsetPair.first);
    dataBlocks_.at(localBlock)[blockOffsetPair.second] = std::get<1>(entry);
  }

 protected:
  Vector(ObjectID oid, size_type n)
      : oid_(oid),
        dataBlocks_(),
        mainLocality_(static_cast<uint64_t>(oid) % rt::numLocalities()),
        sizeCapacityLock_(),
        size_(n),
        capacity_(0),
        allocator_(),
        buffers_(oid) {
    size_t blocksToAllocate = std::max(_sizeToLocalBlocks(n, kBlockSize), 1UL);
    capacity_ =
        std::max(kBlockSize * _blockOffsetFromPosition(n).first, kBlockSize);

    if (n == 0 && static_cast<uint32_t>(rt::thisLocality()) != 0) return;

    for (size_t i = 0; i < blocksToAllocate; ++i)
      dataBlocks_.emplace_back(std::allocator_traits<allocator_type>::allocate(
          allocator_, kBlockSize));
  }

 private:
  friend class AbstractDataStructure<Vector<T, Allocator>>;
  static const size_type kBlockSize;

  std::tuple<rt::Locality, size_t, size_t> _targetFromPosition(
      size_type position, size_type blockSize) const {
    size_t blockNumber = position / blockSize;
    size_t destination = blockNumber % rt::numLocalities();
    size_t offset = position % blockSize;
    return std::make_tuple(rt::Locality(destination), blockNumber, offset);
  }

  size_t _sizeToLocalBlocks(size_type n, size_type blockSize) const {
    size_t fullyUsedBlocks(0);
    rt::Locality pivot(0);

    if (n == 0) return 0;

    std::tie(pivot, fullyUsedBlocks, std::ignore) =
        _targetFromPosition(n - 1, blockSize);

    size_t localBlocks = fullyUsedBlocks / rt::numLocalities();
    if (rt::thisLocality() <= pivot) localBlocks += 1;

    return localBlocks;
  }

  std::pair<size_type, size_type> _blockOffsetFromPosition(size_type n) const {
    size_type blockNumber = n / kBlockSize;
    size_type offset = n % kBlockSize;
    return std::make_pair(blockNumber, offset);
  }

  size_type _globlalBlockToLocalBlock(size_type n) const {
    size_type i = 0;
    while (static_cast<uint32_t>(rt::thisLocality()) < n) {
      n -= rt::numLocalities();
      ++i;
    }
    return i;
  }

  void _reserve(size_type n) {
    size_type currentCapacity = Capacity();
    if (currentCapacity >= n) return;

    rt::Locality insertLocality(0);
    size_t lastBlock(0);
    std::tie(insertLocality, lastBlock, std::ignore) =
        _targetFromPosition(currentCapacity, kBlockSize);
    size_t newLastBlock = n / kBlockSize;

    size_type blocksToAllocate = 1;
    blocksToAllocate += newLastBlock - lastBlock;

    std::vector<size_type> newBlocks(rt::numLocalities(), 0);
    for (size_t i = static_cast<uint32_t>(insertLocality), j = blocksToAllocate;
         j > 0; ++i, --j) {
      newBlocks[i % rt::numLocalities()]++;
    }

    rt::Handle handle;
    for (size_t i = 0; i < rt::numLocalities(); ++i) {
      if (newBlocks[i] == 0) continue;

      rt::asyncExecuteAt(
          handle, rt::Locality(i),
          [](rt::Handle &, const std::pair<ObjectID, size_type> &args) {
            auto This = Vector<T, Allocator>::GetPtr(args.first);

            for (size_t i = 0; i < args.second; ++i) {
              This->dataBlocks_.emplace_back(
                  std::allocator_traits<allocator_type>::allocate(
                      This->allocator_, kBlockSize));
            }
          },
          std::make_pair(oid_, newBlocks[i]));
    }

    rt::waitForCompletion(handle);

    capacity_ += kBlockSize * blocksToAllocate;
  }

  void _clear() {
    for (auto block : dataBlocks_) {
      for (T *toDestroy = block; toDestroy < block + kBlockSize; ++toDestroy) {
        std::allocator_traits<allocator_type>::destroy(allocator_, toDestroy);
      }
      std::allocator_traits<allocator_type>::deallocate(allocator_, block,
                                                        kBlockSize);
    }
    dataBlocks_.clear();
  }

  template <typename ApplyFunT, typename... Args, std::size_t... is>
  static void CallApplyFun(const ObjectID &oid, const size_type position,
                           ApplyFunT function, std::tuple<Args...> &args,
                           std::index_sequence<is...>) {
    auto This = Vector<T, Allocator>::GetPtr(oid);
    auto blockOffsetPair = This->_blockOffsetFromPosition(position);
    size_type localBlock =
        This->_globlalBlockToLocalBlock(blockOffsetPair.first);
    value_type &element =
        This->dataBlocks_.at(localBlock)[blockOffsetPair.second];
    function(position, element, std::get<is>(args)...);
  }

  template <typename Tuple, typename... Args>
  static void ApplyFunWrapper(const Tuple &args) {
    constexpr auto Size = std::tuple_size<
        typename std::decay<decltype(std::get<3>(args))>::type>::value;
    Tuple &tuple = const_cast<Tuple &>(args);
    CallApplyFun(std::get<0>(tuple), std::get<1>(tuple), std::get<2>(tuple),
                 std::get<3>(tuple), std::make_index_sequence<Size>{});
  }

  template <typename ApplyFunT, typename... Args, std::size_t... is>
  static void AsyncCallApplyFun(rt::Handle &handle, const ObjectID &oid,
                                const size_type position, ApplyFunT function,
                                std::tuple<Args...> &args,
                                std::index_sequence<is...>) {
    auto This = Vector<T, Allocator>::GetPtr(oid);
    auto blockOffsetPair = This->_blockOffsetFromPosition(position);
    size_type localBlock =
        This->_globlalBlockToLocalBlock(blockOffsetPair.first);
    value_type &element = This->dataBlocks_[localBlock][blockOffsetPair.second];
    function(handle, position, element, std::get<is>(args)...);
  }

  template <typename Tuple, typename... Args>
  static void AsyncApplyFunWrapper(rt::Handle &handle, const Tuple &args) {
    constexpr auto Size = std::tuple_size<
        typename std::decay<decltype(std::get<3>(args))>::type>::value;
    Tuple &tuple = const_cast<Tuple &>(args);
    AsyncCallApplyFun(handle, std::get<0>(tuple), std::get<1>(tuple),
                      std::get<2>(tuple), std::get<3>(tuple),
                      std::make_index_sequence<Size>{});
  }

  template <typename ApplyFunT, typename... Args, std::size_t... is>
  static void CallForEachInRangeFun(const size_t i, const ObjectID &oid,
                                    const size_t position, ApplyFunT function,
                                    std::tuple<Args...> &args,
                                    std::index_sequence<is...>) {
    // Get a local instance on the remote node.
    auto This = Vector<T, Allocator>::GetPtr(oid);
    auto blockOffsetPair = This->_blockOffsetFromPosition(position + i);
    size_type localBlock =
        This->_globlalBlockToLocalBlock(blockOffsetPair.first);
    value_type &element =
        This->dataBlocks_.at(localBlock)[blockOffsetPair.second];
    function(position + i, element, std::get<is>(args)...);
  }

  template <typename Tuple, typename... Args>
  static void ForEachInRangeFunWrapper(const Tuple &args, size_t i) {
    constexpr auto Size = std::tuple_size<
        typename std::decay<decltype(std::get<3>(args))>::type>::value;
    Tuple &tuple = const_cast<Tuple &>(args);
    CallForEachInRangeFun(i, std::get<0>(tuple), std::get<1>(tuple),
                          std::get<2>(tuple), std::get<3>(tuple),
                          std::make_index_sequence<Size>{});
  }

  template <typename ApplyFunT, typename... Args, std::size_t... is>
  static void AsyncCallForEachInRangeFun(rt::Handle &handle, const size_t i,
                                         const ObjectID &oid,
                                         const size_t position,
                                         ApplyFunT function,
                                         std::tuple<Args...> &args,
                                         std::index_sequence<is...>) {
    // Get a local instance on the remote node.
    auto This = Vector<T, Allocator>::GetPtr(oid);
    auto blockOffsetPair = This->_blockOffsetFromPosition(position + i);
    size_type localBlock =
        This->_globlalBlockToLocalBlock(blockOffsetPair.first);
    value_type &element =
        This->dataBlocks_.at(localBlock)[blockOffsetPair.second];
    function(handle, position + i, element, std::get<is>(args)...);
  }

  template <typename Tuple, typename... Args>
  static void AsyncForEachInRangeFunWrapper(rt::Handle &handle,
                                            const Tuple &args, size_t i) {
    constexpr auto Size = std::tuple_size<
        typename std::decay<decltype(std::get<3>(args))>::type>::value;
    Tuple &tuple = const_cast<Tuple &>(args);
    AsyncCallForEachInRangeFun(
        handle, i, std::get<0>(tuple), std::get<1>(tuple), std::get<2>(tuple),
        std::get<3>(tuple), std::make_index_sequence<Size>{});
  }

  using BuffersVector =
      impl::BuffersVector<std::tuple<size_t, T>, Vector<T, Allocator>>;
  friend class impl::BuffersVector<std::tuple<size_t, T>, Vector<T, Allocator>>;

  ObjectID oid_;
  rt::Locality mainLocality_;
  std::vector<value_type *> dataBlocks_;
  rt::Lock sizeCapacityLock_;
  size_type size_;
  size_type capacity_;
  allocator_type allocator_;
  BuffersVector buffers_;
};

template <typename T, typename Allocator>
const typename Vector<T, Allocator>::size_type
    Vector<T, Allocator>::kBlockSize = constants::max((1024 << 6) / sizeof(T),
                                                      1lu);

template <typename T, typename Allocator>
template <typename ValueType>
class Vector<T, Allocator>::Iterator
    : std::iterator<std::random_access_iterator_tag, ValueType> {
 public:
  Iterator(Vector<T, Allocator>::size_type n,
           Vector<T, Allocator>::ObjectID oid)
      : position_(n), oid_(oid) {}

  Iterator(const Iterator &itr) = default;

  Iterator &operator=(const Iterator &itr) = default;

  explicit operator bool() const {
    if (position_ == Vector<T, Allocator>::MaxSize())
      return false;
    else
      return true;
  }

  bool operator==(const Iterator &rhs) const {
    if (oid_ != rhs.oid_) return false;

    if (position_ != rhs.position_)
      return false;
    else
      return true;
  }

  bool operator!=(const Iterator &rhs) const { return !(*this == rhs); }

  Iterator &operator+=(const ptrdiff_t &movement) {
    position_ += movement;
    return *this;
  }

  Iterator &operator-=(const ptrdiff_t &movement) {
    position_ -= movement;
    return *this;
  }

  Iterator &operator++() {
    ++position_;
    return *this;
  }
  Iterator &operator--() {
    --position_;
    return *this;
  }

  Iterator operator++(int) {
    auto tmp(*this);
    ++position_;
    return tmp;
  }

  Iterator operator--(int) {
    auto tmp(*this);
    --position_;
    tmp;
  }

  Iterator operator+(const ptrdiff_t &movement) {
    Iterator tmp(*this);
    tmp.position_ += movement;
    return tmp;
  }

  Iterator operator-(const ptrdiff_t &movement) {
    Iterator tmp(*this);
    tmp.position_ -= movement;
    return tmp;
  }

  ptrdiff_t operator-(const Iterator &rhs) {
    return std::distance(rhs.position_, this->position_);
  }

  const value_type operator*() const {
    auto ptr = Vector<T, Allocator>::GetPtr(oid_);
    return ptr->At(position_);
  }

  const value_type *operator->() const {
    auto ptr = Vector<T, Allocator>::GetPtr(oid_);
    return &*(*this);
  }

 private:
  Vector<T, Allocator>::size_type position_;
  Vector<T, Allocator>::ObjectID oid_;
};

template <typename T, typename Allocator>
typename Vector<T, Allocator>::size_type Vector<T, Allocator>::Size() const
    noexcept {
  if (rt::thisLocality() == mainLocality_) return size_;

  size_type size = 0;
  rt::executeAtWithRet(mainLocality_,
                       [](const ObjectID &oid, size_type *size) {
                         auto This = Vector<T, Allocator>::GetPtr(oid);
                         *size = This->size_;
                       },
                       oid_, &size);
  return size;
}

template <typename T, typename Allocator>
typename Vector<T, Allocator>::size_type Vector<T, Allocator>::MaxSize() const
    noexcept {
  return std::numeric_limits<Vector<T, Allocator>::size_type>::max() - 1;
}

template <typename T, typename Allocator>
typename Vector<T, Allocator>::size_type Vector<T, Allocator>::Capacity() const
    noexcept {
  if (rt::thisLocality() == mainLocality_) return capacity_;

  size_type capacity = 0;
  rt::executeAtWithRet(mainLocality_,
                       [](const ObjectID &oid, size_type *capacity) {
                         auto ptr = Vector<T, Allocator>::GetPtr(oid);
                         *capacity = ptr->capacity_;
                       },
                       oid_, &capacity);
  return capacity;
}

template <typename T, typename Allocator>
bool Vector<T, Allocator>::Empty() const noexcept {
  return Size() == 0;
}

template <typename T, typename Allocator>
void Vector<T, Allocator>::Reserve(Vector<T, Allocator>::size_type n) {
  rt::executeAt(mainLocality_,
                [](const std::pair<ObjectID, size_type> &args) {
                  auto This = Vector<T, Allocator>::GetPtr(args.first);
                  auto n = args.second;

                  std::lock_guard<rt::Lock> _(This->sizeCapacityLock_);
                  This->_reserve(n);
                },
                std::make_pair(oid_, n));
}

template <typename T, typename Allocator>
void Vector<T, Allocator>::Resize(Vector<T, Allocator>::size_type n) {
  rt::executeAt(mainLocality_,
                [](const std::pair<ObjectID, size_type> &args) {
                  auto This = Vector<T, Allocator>::GetPtr(args.first);
                  auto n = args.second;

                  std::lock_guard<rt::Lock> _(This->sizeCapacityLock_);
                  if (n <= This->size_) return;

                  This->_reserve(n);
                  This->size_ = n;
                },
                std::make_pair(oid_, n));
}

template <typename T, typename Allocator>
typename Vector<T, Allocator>::value_type Vector<T, Allocator>::At(
    Vector<T, Allocator>::size_type n) const {
  rt::Locality target(0);
  size_t blockNumber(0);
  size_t offset(0);
  std::tie(target, blockNumber, offset) = _targetFromPosition(n, kBlockSize);

  if (target == rt::thisLocality()) {
    size_type localBlock = _globlalBlockToLocalBlock(blockNumber);
    return dataBlocks_[localBlock][offset];
  } else {
    T value;
    rt::executeAtWithRet(
        target,
        [](const std::tuple<ObjectID, size_type, size_type> &args, T *result) {
          auto This = Vector<T, Allocator>::GetPtr(std::get<0>(args));
          size_type localBlock =
              This->_globlalBlockToLocalBlock(std::get<1>(args));

          *result = This->dataBlocks_[localBlock][std::get<2>(args)];
        },
        std::make_tuple(oid_, blockNumber, offset), &value);
    return value;
  }
}

template <typename T, typename Allocator>
typename Vector<T, Allocator>::value_type Vector<T, Allocator>::operator[](
    Vector<T, Allocator>::size_type n) const {
  return At(n);
}

template <typename T, typename Allocator>
typename Vector<T, Allocator>::value_type Vector<T, Allocator>::Front() const {
  return At(0);
}

template <typename T, typename Allocator>
typename Vector<T, Allocator>::value_type Vector<T, Allocator>::Back() const {
  size_type last_position = Size() - 1;
  return At(last_position);
}

template <typename T, typename Allocator>
void Vector<T, Allocator>::AsyncAt(
    rt::Handle &handle, Vector<T, Allocator>::size_type n,
    Vector<T, Allocator>::value_type *result) const {
  rt::Locality target(0);
  size_type blockNumber(0);
  size_type offset(0);
  std::tie(target, blockNumber, offset) = _targetFromPosition(n, kBlockSize);

  rt::asyncExecuteAtWithRet(
      handle, target,
      [](rt::Handle &handle,
         const std::tuple<ObjectID, size_type, size_type> &args, T *result) {
        auto This = Vector<T, Allocator>::GetPtr(std::get<0>(args));
        size_type localBlock =
            This->_globlalBlockToLocalBlock(std::get<1>(args));

        *result = This->dataBlocks_[localBlock][std::get<2>(args)];
      },
      std::make_tuple(oid_, blockNumber, offset), result);
}

template <typename T, typename Allocator>
void Vector<T, Allocator>::Clear() noexcept {
  rt::executeAt(mainLocality_,
                [](const ObjectID &args) {
                  auto This = Vector<T, Allocator>::GetPtr(args);
                  std::lock_guard<rt::Lock> _(This->sizeCapacityLock_);

                  This->size_ = 0;
                  This->capacity_ = 0;

                  rt::executeOnAll(
                      [](const ObjectID &args) {
                        auto This = Vector<T, Allocator>::GetPtr(args);
                        This->_clear();
                      },
                      args);
                },
                oid_);
}

template <typename T, typename Allocator>
void Vector<T, Allocator>::PushBack(
    const typename Vector<T, Allocator>::value_type &value) {
  size_type newSize(0);

  rt::executeAtWithRet(mainLocality_,
                       [](const ObjectID &args, size_type *size) {
                         auto This = Vector<T, Allocator>::GetPtr(args);
                         std::lock_guard<rt::Lock> _(This->sizeCapacityLock_);

                         *size = ++This->size_;
                         if (This->size_ > This->capacity_)
                           This->_reserve(This->size_);
                       },
                       oid_, &newSize);

  size_type position = newSize - 1;

  rt::Locality target(0);
  size_t blockNumber(0);
  size_t offset(0);
  std::tie(target, blockNumber, offset) =
      _targetFromPosition(position, kBlockSize);

  if (target == rt::thisLocality()) {
    size_type localBlock = _globlalBlockToLocalBlock(blockNumber);
    dataBlocks_[localBlock][offset] = value;
  } else {
    using MessageTuple = std::tuple<ObjectID, size_type, size_type, value_type>;
    rt::executeAt(target,
                  [](const MessageTuple &args) {
                    auto This = Vector<T, Allocator>::GetPtr(std::get<0>(args));
                    size_type localBlock =
                        This->_globlalBlockToLocalBlock(std::get<1>(args));

                    This->dataBlocks_[localBlock][std::get<2>(args)] =
                        std::get<3>(args);
                  },
                  std::make_tuple(oid_, blockNumber, offset, value));
  }
}

template <typename T, typename Allocator>
typename Vector<T, Allocator>::iterator Vector<T, Allocator>::InsertAt(
    Vector<T, Allocator>::size_type position,
    const Vector<T, Allocator>::value_type &value) {
  rt::Locality target(0);
  size_t blockNumber(0);
  size_t offset(0);
  std::tie(target, blockNumber, offset) =
      _targetFromPosition(position, kBlockSize);

  if (target == rt::thisLocality()) {
    size_type localBlock = _globlalBlockToLocalBlock(blockNumber);
    dataBlocks_[localBlock][offset] = value;
  } else {
    using MessageTuple = std::tuple<ObjectID, size_type, size_type, value_type>;
    rt::executeAt(target,
                  [](const MessageTuple &args) {
                    auto This = Vector<T, Allocator>::GetPtr(std::get<0>(args));
                    size_type localBlock =
                        This->_globlalBlockToLocalBlock(std::get<1>(args));

                    This->dataBlocks_[localBlock][std::get<2>(args)] =
                        std::get<3>(args);
                  },
                  std::make_tuple(oid_, blockNumber, offset, value));
  }

  return Vector<T, Allocator>::iterator(position, GetGlobalID());
}

template <typename T, typename Allocator>
template <typename IteratorType>
typename Vector<T, Allocator>::iterator Vector<T, Allocator>::InsertAt(
    Vector<T, Allocator>::size_type position, IteratorType begin,
    IteratorType end) {
  rt::Handle handle;

  AsyncInsertAt(handle, position, begin, end);

  rt::waitForCompletion(handle);

  return Vector<T, Allocator>::iterator(position, GetGlobalID());
}

template <typename T, typename Allocator>
void Vector<T, Allocator>::AsyncInsertAt(
    rt::Handle &handle, Vector<T, Allocator>::size_type position,
    const Vector<T, Allocator>::value_type &value) {
  size_type currentSize(0);

  rt::Locality target(0);
  size_t blockNumber(0);
  size_t offset(0);
  std::tie(target, blockNumber, offset) =
      _targetFromPosition(position, kBlockSize);

  if (target == rt::thisLocality()) {
    size_type localBlock = _globlalBlockToLocalBlock(blockNumber);
    dataBlocks_[localBlock][offset] = value;
  } else {
    using MessageTuple = std::tuple<ObjectID, size_type, size_type, value_type>;
    rt::asyncExecuteAt(
        handle, target,
        [](rt::Handle &, const MessageTuple &args) {
          auto This = Vector<T, Allocator>::GetPtr(std::get<0>(args));
          size_type localBlock =
              This->_globlalBlockToLocalBlock(std::get<1>(args));

          This->dataBlocks_[localBlock][std::get<2>(args)] = std::get<3>(args);
        },
        std::make_tuple(oid_, blockNumber, offset, value));
  }
}

template <typename T, typename Allocator>
template <typename IteratorType>
void Vector<T, Allocator>::AsyncInsertAt(
    rt::Handle &handle, Vector<T, Allocator>::size_type position,
    IteratorType begin, IteratorType end) {
  size_type newElements = std::distance(begin, end);
  size_type newSize(0);

  rt::executeAtWithRet(
      mainLocality_,
      [](const std::tuple<ObjectID, size_type, size_type> &args,
         size_type *size) {
        auto This = Vector<T, Allocator>::GetPtr(std::get<0>(args));
        std::lock_guard<rt::Lock> _(This->sizeCapacityLock_);
        auto position = std::get<1>(args);
        auto newElements = std::get<2>(args);

        if (position > This->size_ || position + newElements <= This->size_) {
          *size = This->size_;
          return;
        }

        size_type end = position + newElements;
        size_type growth = This->size_ > end ? 0 : end - This->size_;

        This->size_ += growth;
        if (This->size_ > This->capacity_) This->_reserve(This->size_);

        *size = This->size_;
      },
      std::make_tuple(oid_, position, newElements), &newSize);

  // Trying to insert in an invalid position.
  if (position > newSize) {
    throw std::out_of_range("AsyncInsertAt: position out of range");
  }

  size_type startingPoint = newSize - newElements;

  constexpr size_t kNumElements = 4000 / sizeof(value_type);
  static_assert(kNumElements >= 1,
                "We can't do this due to a series of unfortunate events");

  struct InsertMessage {
    ObjectID objID;
    size_type startPosition;
    size_type numElements;
    value_type elements[kNumElements];

    InsertMessage()
        : objID(ObjectID::kNullID), startPosition(0), numElements(0) {}
  };

  auto insertFunction = [](rt::Handle &, const InsertMessage &args) {
    auto This = Vector<T, Allocator>::GetPtr(args.objID);
    auto blockOffsetPair = This->_blockOffsetFromPosition(args.startPosition);
    size_type localBlock =
        This->_globlalBlockToLocalBlock(blockOffsetPair.first);
    std::copy(&args.elements[0], &args.elements[args.numElements],
              &This->dataBlocks_.at(localBlock)[blockOffsetPair.second]);
  };

  InsertMessage args;
  size_t spaceLeftInBlock = kBlockSize - (startingPoint % kBlockSize);
  // first block
  args.objID = oid_;
  args.startPosition = startingPoint;
  args.numElements =
      std::min(newElements, std::min(kNumElements, spaceLeftInBlock));

  std::copy(begin, begin + args.numElements, args.elements);

  rt::Locality target(0);
  size_t blockNumber(0);
  size_t offset(0);
  std::tie(target, blockNumber, offset) =
      _targetFromPosition(position, kBlockSize);

  rt::asyncExecuteAt(handle, target, insertFunction, args);

  begin += args.numElements;
  newElements -= args.numElements;
  startingPoint += args.numElements;

  // inner blocks
  while (begin != end && newElements > 0) {
    std::tie(target, blockNumber, offset) =
        _targetFromPosition(startingPoint, kBlockSize);
    args.startPosition = startingPoint;

    spaceLeftInBlock = kBlockSize - (startingPoint % kBlockSize);
    args.numElements =
        std::min(newElements, std::min(kNumElements, spaceLeftInBlock));

    std::copy(begin, begin + args.numElements, args.elements);
    rt::asyncExecuteAt(handle, target, insertFunction, args);

    begin += args.numElements;
    newElements -= args.numElements;
    startingPoint += args.numElements;
  }
}

template <typename T, typename Allocator>
void Vector<T, Allocator>::BufferedInsertAt(const size_type position,
                                            const value_type &value) {
  rt::Locality target(0);
  size_t blockNumber(0);
  size_t offset(0);
  std::tie(target, blockNumber, offset) =
      _targetFromPosition(position, kBlockSize);

  if (target == rt::thisLocality()) {
    size_type localBlock = _globlalBlockToLocalBlock(blockNumber);
    dataBlocks_[localBlock][offset] = value;
  } else {
    buffers_.Insert(std::make_tuple(position, value), target);
  }
}

template <typename T, typename Allocator>
void Vector<T, Allocator>::BufferedAsyncInsertAt(rt::Handle &handle,
                                                 const size_type position,
                                                 const value_type &value) {
  rt::Locality target(0);
  size_t blockNumber(0);
  size_t offset(0);
  std::tie(target, blockNumber, offset) =
      _targetFromPosition(position, kBlockSize);

  if (target == rt::thisLocality()) {
    size_type localBlock = _globlalBlockToLocalBlock(blockNumber);
    dataBlocks_[localBlock][offset] = value;
  } else {
    buffers_.AsyncInsert(handle, std::make_tuple(position, value), target);
  }
}

template <typename T, typename Allocator>
template <typename ApplyFunT, typename... Args>
void Vector<T, Allocator>::Apply(const Vector<T, Allocator>::size_type position,
                                 ApplyFunT &&function, Args &... args) {
  rt::Locality target(0);
  size_t blockNumber(0);
  size_t offset(0);
  std::tie(target, blockNumber, offset) =
      _targetFromPosition(position, kBlockSize);

  using FunctionTy = void (*)(size_type, T &, Args & ...);
  FunctionTy fn = std::forward<decltype(function)>(function);
  using ArgsTuple =
      std::tuple<ObjectID, size_t, FunctionTy, std::tuple<Args...>>;
  ArgsTuple argsTuple{oid_, position, fn, std::tuple<Args...>(args...)};

  rt::executeAt(target, ApplyFunWrapper<ArgsTuple, Args...>, argsTuple);
}

template <typename T, typename Allocator>
template <typename ApplyFunT, typename... Args>
void Vector<T, Allocator>::AsyncApply(
    rt::Handle &handle, const Vector<T, Allocator>::size_type position,
    ApplyFunT &&function, Args &... args) {
  rt::Locality target(0);
  size_t blockNumber(0);
  size_t offset(0);
  std::tie(target, blockNumber, offset) =
      _targetFromPosition(position, kBlockSize);

  using FunctionTy = void (*)(rt::Handle &, size_type, T &, Args & ...);
  FunctionTy fn = std::forward<decltype(function)>(function);
  using ArgsTuple =
      std::tuple<ObjectID, size_t, FunctionTy, std::tuple<Args...>>;
  ArgsTuple argsTuple{oid_, position, fn, std::tuple<Args...>(args...)};

  rt::asyncExecuteAt(handle, target, AsyncApplyFunWrapper<ArgsTuple, Args...>,
                     argsTuple);
}

template <typename T, typename Allocator>
template <typename ApplyFunT, typename... Args>
void Vector<T, Allocator>::ForEachInRange(const size_type begin,
                                          const size_type end,
                                          ApplyFunT &&function,
                                          Args &... args) {
  using FunctionTy = void (*)(size_type, value_type &, Args & ...);

  FunctionTy fn = std::forward<decltype(function)>(function);
  using ArgsTuple =
      std::tuple<ObjectID, size_t, FunctionTy, std::tuple<Args...>>;

  size_type numElements = end - begin;
  size_type start = begin;

  // first block
  size_type blockSize =
      std::min(numElements, kBlockSize - (start % kBlockSize));

  rt::Locality target(0);
  std::tie(target, std::ignore, std::ignore) =
      _targetFromPosition(start, kBlockSize);

  ArgsTuple argsTuple{oid_, start, fn, std::tuple<Args...>(args...)};

  rt::forEachAt(target, ForEachInRangeFunWrapper<ArgsTuple, Args...>, argsTuple,
                blockSize);

  start += blockSize;
  numElements -= blockSize;

  // inner blocks
  while (start != end && numElements > 0) {
    std::tie(target, std::ignore, std::ignore) =
        _targetFromPosition(start, kBlockSize);
    blockSize = std::min(kBlockSize, numElements);

    std::get<1>(argsTuple) = start;
    rt::forEachAt(target, ForEachInRangeFunWrapper<ArgsTuple, Args...>,
                  argsTuple, blockSize);

    start += blockSize;
    numElements -= blockSize;
  }
}

template <typename T, typename Allocator>
template <typename ApplyFunT, typename... Args>
void Vector<T, Allocator>::AsyncForEachInRange(rt::Handle &handle,
                                               const size_type begin,
                                               const size_type end,
                                               ApplyFunT &&function,
                                               Args &... args) {
  using FunctionTy =
      void (*)(rt::Handle &, size_type, value_type &, Args & ...);

  FunctionTy fn = std::forward<decltype(function)>(function);
  using ArgsTuple =
      std::tuple<ObjectID, size_t, FunctionTy, std::tuple<Args...>>;

  size_type numElements = end - begin;
  size_type start = begin;

  // first block
  size_type blockSize =
      std::min(numElements, kBlockSize - (start % kBlockSize));

  rt::Locality target(0);
  std::tie(target, std::ignore, std::ignore) =
      _targetFromPosition(start, kBlockSize);

  ArgsTuple argsTuple{oid_, start, fn, std::tuple<Args...>(args...)};

  rt::asyncForEachAt(handle, target,
                     AsyncForEachInRangeFunWrapper<ArgsTuple, Args...>,
                     argsTuple, blockSize);

  start += blockSize;
  numElements -= blockSize;

  // inner blocks
  while (start != end && numElements > 0) {
    std::tie(target, std::ignore, std::ignore) =
        _targetFromPosition(start, kBlockSize);
    blockSize = std::min(kBlockSize, numElements);

    std::get<1>(argsTuple) = start;
    rt::asyncForEachAt(handle, target,
                       AsyncForEachInRangeFunWrapper<ArgsTuple, Args...>,
                       argsTuple, blockSize);

    start += blockSize;
    numElements -= blockSize;
  }
}

}  // namespace shad

#endif  // INCLUDE_SHAD_DATA_STRUCTURES_VECTOR_H_