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.

245 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. // cache data holds all required information on
  11. // one cache entry and will both be stored
  12. // internally by the cache and handed out
  13. // as copies to the user
  14. // this class uses its object lifetime and
  15. // a global reference counter to allow
  16. // thread-safe copies and resource management
  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 - !!! this may also
  54. // yield a nullptr !!!
  55. uint8_t* GetDataLocation() const;
  56. };
  57. }
  58. inline void dsacache::CacheData::WaitOnCompletion() {
  59. // the cache data entry can be in two states
  60. // either it is the original one which has not
  61. // been waited for in which case the handlers
  62. // are non-null or it is not
  63. if (handlers_ == nullptr) {
  64. std::cout << "[-] Waiting on cache-var-update for CacheData 0x" << std::hex << (uint64_t)src_ << std::dec << std::endl;
  65. // when no handlers are attached to this cache entry we wait on a
  66. // value change for the cache structure from nullptr to non-null
  67. // which will either go through immediately if the cache is valid
  68. // already or wait until the handler-owning thread notifies us
  69. cache_->wait(nullptr);
  70. std::cout << "[+] Finished waiting on cache-var-update for CacheData 0x" << std::hex << (uint64_t)src_ << std::dec << std::endl;
  71. }
  72. else {
  73. // when the handlers are non-null there are some DSA task handlers
  74. // available on which we must wait here
  75. std::cout << "[-] Waiting on handlers for CacheData 0x" << std::hex << (uint64_t)src_ << std::dec << std::endl;
  76. // abort is set if any operation encountered an error
  77. bool abort = false;
  78. for (auto& handler : *handlers_) {
  79. auto result = handler.get();
  80. if (result.status != dml::status_code::ok) {
  81. std::cerr << "[x] Encountered bad status code for operation: " << dml::StatusCodeToString(result.status) << std::endl;
  82. // if one of the copy tasks failed we abort the whole task
  83. // after all operations are completed on it
  84. abort = true;
  85. }
  86. }
  87. // the handlers are cleared after all have completed
  88. handlers_ = nullptr;
  89. // now we act depending on whether an abort has been
  90. // called for which signals operation incomplete
  91. if (abort) {
  92. // store nullptr in the cache location
  93. cache_->store(nullptr);
  94. // then free the now incomplete cache
  95. // TODO: it would be possible to salvage the
  96. // TODO: operation at this point but this
  97. // TODO: is quite complicated so we just abort
  98. numa_free(incomplete_cache_, size_);
  99. }
  100. else {
  101. std::cout << "[+] Finished waiting on handlers for CacheData 0x" << std::hex << (uint64_t)src_ << std::dec << std::endl;
  102. // incomplete cache is now safe to use and therefore we
  103. // swap it with the global cache state of this entry
  104. // and notify potentially waiting threads
  105. cache_->store(incomplete_cache_);
  106. }
  107. // as a last step all waiting threads must
  108. // be notified (copies of this will wait on value
  109. // change of the cache) and the incomplete cache
  110. // is cleared to nullptr as it is not incomplete
  111. cache_->notify_all();
  112. incomplete_cache_ = nullptr;
  113. }
  114. }
  115. dsacache::CacheData::CacheData(uint8_t* data, const size_t size) {
  116. std::cout << "[-] New CacheData 0x" << std::hex << (uint64_t)data << std::dec << std::endl;
  117. src_ = data;
  118. size_ = size;
  119. active_ = new std::atomic<int32_t>(1);
  120. cache_ = new std::atomic<uint8_t*>();
  121. incomplete_cache_ = nullptr;
  122. handlers_ = std::make_unique<std::vector<dml_handler>>();
  123. }
  124. dsacache::CacheData::CacheData(const dsacache::CacheData& other) {
  125. std::cout << "[-] Copy Created for CacheData 0x" << std::hex << (uint64_t)other.src_ << std::dec << std::endl;
  126. // we copy the ptr to the global atomic reference counter
  127. // and increase the amount of active references
  128. active_ = other.active_;
  129. const int current_active = active_->fetch_add(1);
  130. // source and size will be copied too
  131. // as well as the reference to the global
  132. // atomic cache pointer
  133. src_ = other.src_;
  134. size_ = other.size_;
  135. cache_ = other.cache_;
  136. // incomplete cache and handlers will not
  137. // be copied because only the first instance
  138. // will wait on the completion of handlers
  139. incomplete_cache_ = nullptr;
  140. handlers_ = nullptr;
  141. }
  142. dsacache::CacheData::~CacheData() {
  143. std::cout << "[-] Destructor for CacheData 0x" << std::hex << (uint64_t)src_ << std::dec << std::endl;
  144. // if this is the first instance of this cache structure
  145. // and it has not been waited on and is now being destroyed
  146. // we must wait on completion here to ensure the cache
  147. // remains in a valid state
  148. if (handlers_ != nullptr) {
  149. WaitOnCompletion();
  150. }
  151. // due to fetch_sub returning the preivously held value
  152. // we must subtract one locally to get the current value
  153. const int32_t v = active_->fetch_sub(1) - 1;
  154. // if the returned value is zero or lower
  155. // then we must execute proper deletion
  156. // as this was the last reference
  157. if (v <= 0) {
  158. std::cout << "[!] Full Destructor for CacheData 0x" << std::hex << (uint64_t)src_ << std::dec << std::endl;
  159. Deallocate();
  160. delete active_;
  161. delete cache_;
  162. }
  163. }
  164. void dsacache::CacheData::Deallocate() {
  165. std::cout << "[!] Deallocating for CacheData 0x" << std::hex << (uint64_t)src_ << std::dec << std::endl;
  166. // although deallocate should only be called from
  167. // a safe context to do so, it can not hurt to
  168. // defensively perform the operation atomically
  169. uint8_t* cache_local = cache_->exchange(nullptr);
  170. if (cache_local != nullptr) numa_free(cache_local, size_);
  171. }
  172. uint8_t* dsacache::CacheData::GetDataLocation() const {
  173. return cache_->load();
  174. }
  175. bool dsacache::CacheData::Active() const {
  176. // this entry is active if more than one
  177. // reference exists to it, as the Cache
  178. // will always keep one internally until
  179. // the entry is cleared from cache
  180. return active_->load() > 1;
  181. }