This contains my bachelors thesis and associated tex files, code snippets and maybe more. Topic: Data Movement in Heterogeneous Memories with Intel Data Streaming Accelerator
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

243 lines
7.8 KiB

  1. #pragma once
  2. #include <iostream>
  3. #include <atomic>
  4. #include <memory>
  5. #include <vector>
  6. #include <dml/dml.hpp>
  7. #include "util/dml-helper.hpp"
  8. namespace dsacache {
  9. class Cache;
  10. // the cache task structure will be used to submit and
  11. // control a cache element, while providing source pointer
  12. // and size in bytes for submission
  13. //
  14. // then the submitting thread may wait on the atomic "result"
  15. // which will be notified by the cache worker upon processing
  16. // after which the atomic-bool-ptr active will also become valid
  17. class CacheData {
  18. public:
  19. using dml_handler = dml::handler<dml::mem_copy_operation, std::allocator<uint8_t>>;
  20. private:
  21. // data source and size of the block
  22. uint8_t* src_;
  23. size_t size_;
  24. // global reference counting object
  25. std::atomic<int32_t>* active_;
  26. // global cache-location pointer
  27. std::atomic<uint8_t*>* cache_;
  28. // object-local incomplete cache location pointer
  29. // which is only available in the first instance
  30. uint8_t* incomplete_cache_;
  31. // dml handler vector pointer which is only
  32. // available in the first instance
  33. std::unique_ptr<std::vector<dml_handler>> handlers_;
  34. // deallocates the global cache-location
  35. // and invalidates it
  36. void Deallocate();
  37. // checks whether there are at least two
  38. // valid references to this object which
  39. // is done as the cache always has one
  40. // internally to any living instance
  41. bool Active() const;
  42. friend Cache;
  43. public:
  44. CacheData(uint8_t* data, const size_t size);
  45. CacheData(const CacheData& other);
  46. ~CacheData();
  47. // waits on completion of caching operations
  48. // for this task and is safe to be called in
  49. // any state of the object
  50. void WaitOnCompletion();
  51. // returns the cache data location for this
  52. // instance which is valid as long as the
  53. // instance is alive
  54. uint8_t* GetDataLocation() const;
  55. };
  56. }
  57. inline void dsacache::CacheData::WaitOnCompletion() {
  58. // the cache data entry can be in two states
  59. // either it is the original one which has not
  60. // been waited for in which case the handlers
  61. // are non-null or it is not
  62. if (handlers_ == nullptr) {
  63. std::cout << "[-] Waiting on cache-var-update for CacheData 0x" << std::hex << (uint64_t)src_ << std::dec << std::endl;
  64. // when no handlers are attached to this cache entry we wait on a
  65. // value change for the cache structure from nullptr to non-null
  66. // which will either go through immediately if the cache is valid
  67. // already or wait until the handler-owning thread notifies us
  68. cache_->wait(nullptr);
  69. std::cout << "[+] Finished waiting on cache-var-update for CacheData 0x" << std::hex << (uint64_t)src_ << std::dec << std::endl;
  70. }
  71. else {
  72. // when the handlers are non-null there are some DSA task handlers
  73. // available on which we must wait here
  74. std::cout << "[-] Waiting on handlers for CacheData 0x" << std::hex << (uint64_t)src_ << std::dec << std::endl;
  75. // abort is set if any operation encountered an error
  76. bool abort = false;
  77. for (auto& handler : *handlers_) {
  78. auto result = handler.get();
  79. if (result.status != dml::status_code::ok) {
  80. std::cerr << "[x] Encountered bad status code for operation: " << dml::StatusCodeToString(result.status) << std::endl;
  81. // if one of the copy tasks failed we abort the whole task
  82. // after all operations are completed on it
  83. abort = true;
  84. }
  85. }
  86. // the handlers are cleared after all have completed
  87. handlers_ = nullptr;
  88. // now we act depending on whether an abort has been
  89. // called for which signals operation incomplete
  90. if (abort) {
  91. // store nullptr in the cache location
  92. cache_->store(nullptr);
  93. // then free the now incomplete cache
  94. // TODO: it would be possible to salvage the
  95. // TODO: operation at this point but this
  96. // TODO: is quite complicated so we just abort
  97. numa_free(incomplete_cache_, size_);
  98. }
  99. else {
  100. std::cout << "[+] Finished waiting on handlers for CacheData 0x" << std::hex << (uint64_t)src_ << std::dec << std::endl;
  101. // incomplete cache is now safe to use and therefore we
  102. // swap it with the global cache state of this entry
  103. // and notify potentially waiting threads
  104. cache_->store(incomplete_cache_);
  105. }
  106. // as a last step all waiting threads must
  107. // be notified (copies of this will wait on value
  108. // change of the cache) and the incomplete cache
  109. // is cleared to nullptr as it is not incomplete
  110. cache_->notify_all();
  111. incomplete_cache_ = nullptr;
  112. }
  113. }
  114. dsacache::CacheData::CacheData(uint8_t* data, const size_t size) {
  115. std::cout << "[-] New CacheData 0x" << std::hex << (uint64_t)data << std::dec << std::endl;
  116. src_ = data;
  117. size_ = size;
  118. active_ = new std::atomic<int32_t>(1);
  119. cache_ = new std::atomic<uint8_t*>();
  120. incomplete_cache_ = nullptr;
  121. handlers_ = std::make_unique<std::vector<dml_handler>>();
  122. }
  123. dsacache::CacheData::CacheData(const dsacache::CacheData& other) {
  124. std::cout << "[-] Copy Created for CacheData 0x" << std::hex << (uint64_t)other.src_ << std::dec << std::endl;
  125. // we copy the ptr to the global atomic reference counter
  126. // and increase the amount of active references
  127. active_ = other.active_;
  128. const int current_active = active_->fetch_add(1);
  129. // source and size will be copied too
  130. // as well as the reference to the global
  131. // atomic cache pointer
  132. src_ = other.src_;
  133. size_ = other.size_;
  134. cache_ = other.cache_;
  135. // incomplete cache and handlers will not
  136. // be copied because only the first instance
  137. // will wait on the completion of handlers
  138. incomplete_cache_ = nullptr;
  139. handlers_ = nullptr;
  140. }
  141. dsacache::CacheData::~CacheData() {
  142. std::cout << "[-] Destructor for CacheData 0x" << std::hex << (uint64_t)src_ << std::dec << std::endl;
  143. // if this is the first instance of this cache structure
  144. // and it has not been waited on and is now being destroyed
  145. // we must wait on completion here to ensure the cache
  146. // remains in a valid state
  147. if (handlers_ != nullptr) {
  148. WaitOnCompletion();
  149. }
  150. // due to fetch_sub returning the preivously held value
  151. // we must subtract one locally to get the current value
  152. const int32_t v = active_->fetch_sub(1) - 1;
  153. // if the returned value is zero or lower
  154. // then we must execute proper deletion
  155. // as this was the last reference
  156. if (v <= 0) {
  157. std::cout << "[!] Full Destructor for CacheData 0x" << std::hex << (uint64_t)src_ << std::dec << std::endl;
  158. Deallocate();
  159. delete active_;
  160. delete cache_;
  161. }
  162. }
  163. void dsacache::CacheData::Deallocate() {
  164. std::cout << "[!] Deallocating for CacheData 0x" << std::hex << (uint64_t)src_ << std::dec << std::endl;
  165. // although deallocate should only be called from
  166. // a safe context to do so, it can not hurt to
  167. // defensively perform the operation atomically
  168. uint8_t* cache_local = cache_->exchange(nullptr);
  169. if (cache_local != nullptr) numa_free(cache_local, size_);
  170. }
  171. uint8_t* dsacache::CacheData::GetDataLocation() const {
  172. return cache_->load();
  173. }
  174. bool dsacache::CacheData::Active() const {
  175. // this entry is active if more than one
  176. // reference exists to it, as the Cache
  177. // will always keep one internally until
  178. // the entry is cleared from cache
  179. return active_->load() > 1;
  180. }