You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

map_op.cc 15 kB

5 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. /**
  2. * Copyright 2019 Huawei Technologies Co., Ltd
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #include "dataset/engine/datasetops/map_op.h"
  17. #include <cstring>
  18. #include <iomanip>
  19. #include <iostream>
  20. #include <memory>
  21. #include <vector>
  22. #include "dataset/core/config_manager.h"
  23. #include "dataset/core/constants.h"
  24. #include "dataset/core/global_context.h"
  25. #include "dataset/core/tensor.h"
  26. #include "dataset/engine/data_buffer.h"
  27. #include "dataset/engine/db_connector.h"
  28. #include "dataset/engine/execution_tree.h"
  29. #include "dataset/engine/opt/pass.h"
  30. #include "dataset/kernels/tensor_op.h"
  31. #include "utils/log_adapter.h"
  32. #include "dataset/util/task_manager.h"
  33. namespace mindspore {
  34. namespace dataset {
  35. // Builder constructor. Creates the builder object.
  36. MapOp::Builder::Builder() : build_perf_mode_(true) {
  37. std::shared_ptr<ConfigManager> cfg = GlobalContext::config_manager();
  38. build_num_workers_ = cfg->num_parallel_workers();
  39. build_op_connector_size_ = cfg->op_connector_size();
  40. }
  41. // Check if the required parameters are set by the builder.
  42. Status MapOp::Builder::sanityCheck() const {
  43. if (build_tensor_funcs_.empty()) {
  44. return Status(StatusCode::kUnexpectedError, __LINE__, __FILE__,
  45. "Building a MapOp that has not provided any function/operation to apply");
  46. }
  47. return Status::OK();
  48. }
  49. // The builder "build" method creates the final object.
  50. Status MapOp::Builder::Build(std::shared_ptr<MapOp> *ptr) {
  51. RETURN_IF_NOT_OK(sanityCheck());
  52. *ptr = std::make_shared<MapOp>(std::move(build_in_col_names_), std::move(build_out_col_names_),
  53. std::move(build_tensor_funcs_), build_num_workers_, build_op_connector_size_,
  54. build_perf_mode_);
  55. return Status::OK();
  56. }
  57. // Constructor of MapOp
  58. MapOp::MapOp(const std::vector<std::string> &in_col_names, const std::vector<std::string> &out_col_names,
  59. std::vector<std::shared_ptr<TensorOp>> tensor_funcs, int32_t num_workers, int32_t op_connector_size,
  60. bool perf_mode)
  61. : ParallelOp(num_workers, op_connector_size),
  62. tfuncs_(std::move(tensor_funcs)),
  63. in_columns_(in_col_names),
  64. out_columns_(out_col_names),
  65. perf_mode_(perf_mode) {
  66. // If caller didn't specify the out_col_names, assume they are same as the in_columns.
  67. if (out_columns_.empty() || out_columns_[0].empty()) {
  68. out_columns_ = in_columns_;
  69. }
  70. MS_LOG(DEBUG) << "Performance Mode in map operator is " << perf_mode_ << ".";
  71. }
  72. // The number of threads consuming data from previous op's output Connector.
  73. int32_t MapOp::num_consumers() const {
  74. // When Performance Mode is on, there is only one thread consuming from the previous Connector.
  75. return perf_mode_ == true ? 1 : num_workers_;
  76. }
  77. // A print method typically used for debugging
  78. void MapOp::Print(std::ostream &out, bool show_all) const {
  79. // Always show the id and name as first line regardless if this summary or detailed print
  80. out << "(" << std::setw(2) << operator_id_ << ") <MapOp>:";
  81. if (!show_all) {
  82. // Call the super class for displaying any common 1-liner info
  83. ParallelOp::Print(out, show_all);
  84. // Then show any custom derived-internal 1-liner info for this op
  85. out << "\n";
  86. } else {
  87. // Call the super class for displaying any common detailed info
  88. ParallelOp::Print(out, show_all);
  89. // Then show any custom derived-internal stuff
  90. out << "\nInput column names:";
  91. for (size_t i = 0; i < in_columns_.size(); i++) {
  92. out << " " << in_columns_[i];
  93. }
  94. out << "\n TensorOps:";
  95. for (size_t i = 0; i < tfuncs_.size(); i++) {
  96. out << " " << *(tfuncs_[i].get());
  97. }
  98. out << "\n\n";
  99. }
  100. }
  101. // This class functor will provide the master loop that drives the logic for performing the work
  102. Status MapOp::operator()() {
  103. if (perf_mode_) {
  104. // Create and register the local queues.
  105. local_queues_.Init(num_workers_, oc_queue_size_);
  106. Status rc = local_queues_.Register(tree_->AllTasks());
  107. if (rc.IsError()) {
  108. TaskManager::FindMe()->Post();
  109. return rc;
  110. }
  111. }
  112. // The operator class just starts off threads by calling the tree_ function
  113. Status rc = tree_->LaunchWorkers(num_workers_, std::bind(&MapOp::WorkerEntry, this, std::placeholders::_1));
  114. // Synchronize with TaskManager
  115. TaskManager::FindMe()->Post();
  116. RETURN_IF_NOT_OK(rc);
  117. if (perf_mode_) {
  118. int64_t que_id = 0;
  119. std::unique_ptr<DataBuffer> buff;
  120. bool is_eof = false;
  121. // Draining output connector of the previous op and distribute it to local queues.
  122. // Stop when all worker threads are finished (received EOF).
  123. while (!is_eof) {
  124. RETURN_IF_NOT_OK(child_[0]->GetNextBuffer(&buff, 0));
  125. is_eof = buff->eof();
  126. RETURN_IF_NOT_OK(local_queues_[que_id]->Add(std::move(buff)));
  127. que_id = (que_id + 1) % num_workers_;
  128. }
  129. }
  130. return Status::OK();
  131. }
  132. // Private function for worker/thread to loop continuously. It comprises the main
  133. // logic of MapOp: getting the data from previous Op, validating user specified column names,
  134. // applying a list of TensorOps to each of the data, process the results and then
  135. // pushing them back to MapOp's output Connector to be fetched by the next Op.
  136. Status MapOp::WorkerEntry(int32_t worker_id) {
  137. // Handshake with TaskManager that thread creation is successful.
  138. TaskManager::FindMe()->Post();
  139. std::unique_ptr<DataBuffer> in_buffer;
  140. // Getting a databuffer to work on.
  141. // Perform the first fetch here outside of the loop. This allows us to execute one-time only
  142. // initializations that happen after the first fetch.
  143. RETURN_IF_NOT_OK(FetchNextBuffer(&in_buffer, worker_id));
  144. // Sanity check the databuffer.
  145. // Special case: if there's more threads than buffers, some threads simply get the final control
  146. // messages (eoe/eof), and so they will not perform the check.
  147. if (!in_buffer->eoe() && !in_buffer->eof()) {
  148. int32_t num_rows = in_buffer->NumRows();
  149. int32_t num_cols = in_buffer->NumCols();
  150. if (num_rows == 0 || num_cols == 0) {
  151. RETURN_STATUS_UNEXPECTED("MapOp is getting an empty DataBuffer.");
  152. }
  153. }
  154. // Now that init work is done, drop into the main fetching loop.
  155. // Map op does not use child iterator, and it needs to manually handle eoe and eof's itself
  156. // rather than use the base-class defaults.
  157. while (true) {
  158. // Handle EOE and EOF ourselves. Implicit eoe/eof handling in GetNextInput does not work
  159. // with Performance Mode design.
  160. if (in_buffer->eoe()) {
  161. // Calling base class EoeReceived to forward eoe buffer.
  162. RETURN_IF_NOT_OK(EoeReceived(worker_id));
  163. RETURN_IF_NOT_OK(FetchNextBuffer(&in_buffer, worker_id));
  164. continue;
  165. } else if (in_buffer->eof()) {
  166. // Calling base class EofReceived to forward eof buffer.
  167. RETURN_IF_NOT_OK(EofReceived(worker_id));
  168. break;
  169. }
  170. std::unique_ptr<TensorQTable> new_tensor_table(std::make_unique<TensorQTable>());
  171. // Perform the compute function of TensorOp(s) and store the result in new_tensor_table.
  172. RETURN_IF_NOT_OK(WorkerCompute(in_buffer.get(), new_tensor_table.get()));
  173. // Replace the TensorTable in DataBuffer with the new one.
  174. in_buffer->set_tensor_table(std::move(new_tensor_table));
  175. // Push the buffer onto the connector for next operator to consume.
  176. RETURN_IF_NOT_OK(out_connector_->Add(static_cast<int>(worker_id), std::move(in_buffer)));
  177. // Fetch the next buffer and loop back to the top.
  178. RETURN_IF_NOT_OK(FetchNextBuffer(&in_buffer, worker_id));
  179. }
  180. return Status::OK();
  181. }
  182. Status MapOp::WorkerCompute(DataBuffer *in_buffer, TensorQTable *new_tensor_table) {
  183. // Getting number of rows and cols in this buffer.
  184. int32_t num_rows = in_buffer->NumRows();
  185. int32_t num_cols = in_buffer->NumCols();
  186. for (int32_t r = 0; r < num_rows; r++) {
  187. // to_process : A vector of Tensors only holding cols in input_columns.
  188. // result_row; : A vector of Tensors to hold the result after Compute().
  189. // cur_row : A vector of Tensors holding all the columns from DataBuffer.
  190. TensorRow to_process, result_row, cur_row;
  191. RETURN_IF_NOT_OK(in_buffer->PopRow(&cur_row));
  192. // Populate the Tensor from the current row to be processed by TensorOp
  193. for (const auto &idx : to_process_indices_) {
  194. to_process.push_back(std::move(cur_row[idx]));
  195. }
  196. // Looping over multiple TensorOps supplied in to MapOp.
  197. // The assumption is that the result of one TensorOp matches the required input to the next TensorOp.
  198. for (size_t i = 0; i < tfuncs_.size(); i++) {
  199. // TensorOp can operate on single col or multiple cols. MapOp always call compute for multiple cols.
  200. // TensorOp base class will call the single column Compute() depending on the ops.
  201. // Note: The columns of the result_row is not preallocated, the compute function of each tensor op are
  202. // required to resize/push back the result_row
  203. RETURN_IF_NOT_OK(tfuncs_[i]->Compute(to_process, &result_row));
  204. // Assign result_row to to_process for the next TensorOp processing, except for the last TensorOp in the list.
  205. if (i + 1 < tfuncs_.size()) {
  206. to_process = std::move(result_row);
  207. }
  208. }
  209. if (out_columns_.size() != result_row.size()) {
  210. return Status(StatusCode::kUnexpectedError, __LINE__, __FILE__,
  211. "Result of a tensorOp doesn't match output column names");
  212. }
  213. if (in_columns_.size() == out_columns_.size()) {
  214. for (size_t i = 0; i < result_row.size(); i++) {
  215. cur_row[to_process_indices_[i]] = std::move(result_row[i]);
  216. }
  217. new_tensor_table->push_back(std::move(cur_row));
  218. } else {
  219. // Add the columns we did not touch to the result_row.
  220. for (int32_t i = 0; i < num_cols; i++) {
  221. if (keep_input_columns_[i]) {
  222. result_row.push_back(std::move(cur_row[i]));
  223. }
  224. }
  225. // Add this final result_row to our new TensorTable.
  226. new_tensor_table->push_back(std::move(result_row));
  227. }
  228. }
  229. return Status::OK();
  230. }
  231. Status MapOp::ComputeColMap() {
  232. // If the map has not been set up yet in the base class, then set it up
  233. if (column_name_id_map_.empty()) {
  234. std::unordered_map<std::string, int32_t> current_name_id_map = child_[0]->column_name_id_map();
  235. // Initialize private variables
  236. RETURN_IF_NOT_OK(InitPrivateVariable(&current_name_id_map));
  237. // Create the final column name to index mapping in the base class field
  238. CreateFinalColMap(&current_name_id_map);
  239. MS_LOG(DEBUG) << "Column name map for map op set: " << this->ColumnNameMapAsString();
  240. } else {
  241. MS_LOG(WARNING) << "Column name map is already set!";
  242. }
  243. return Status::OK();
  244. }
  245. // Validating if each of the input_columns exists in the DataBuffer.
  246. Status MapOp::ValidateInColumns(const std::unordered_map<std::string, int32_t> &col_name_id_map) {
  247. for (const auto &inCol : in_columns_) {
  248. bool found = col_name_id_map.find(inCol) != col_name_id_map.end() ? true : false;
  249. if (!found) {
  250. std::string err_msg = "input column name: " + inCol + " doesn't exist in the dataset columns.";
  251. RETURN_STATUS_UNEXPECTED(err_msg);
  252. }
  253. }
  254. return Status::OK();
  255. }
  256. Status MapOp::InitPrivateVariable(std::unordered_map<std::string, int32_t> *col_name_id_map) {
  257. // If input_columns is empty(), The col at index-0 will be picked.
  258. if (in_columns_.empty()) {
  259. for (const auto &pair : *col_name_id_map) {
  260. if (pair.second == 0) {
  261. MS_LOG(INFO) << "Input columns empty for map op, will apply to the first column in the current table.";
  262. in_columns_.push_back(pair.first);
  263. break;
  264. }
  265. }
  266. // If caller didn't specify the out_col_names, assume they are same as the input_columns.
  267. // This was done in the constructor, but if input columns was empty to start we have to redo it here.
  268. if (out_columns_.empty() || out_columns_[0].empty()) {
  269. out_columns_ = in_columns_;
  270. }
  271. }
  272. // Before we continue, issue a sanity check to make sure the input columns from user and the incoming
  273. // columns from child are correct
  274. RETURN_IF_NOT_OK(this->ValidateInColumns(*col_name_id_map));
  275. // initialize keep_input_columns, true means to keep the column.
  276. keep_input_columns_.resize(col_name_id_map->size(), true);
  277. for (const auto &col_name : in_columns_) {
  278. int32_t missed = (*col_name_id_map)[col_name];
  279. keep_input_columns_[missed] = false;
  280. }
  281. // initialize to_process_indices.
  282. for (const auto &col_name : in_columns_) {
  283. to_process_indices_.push_back((*col_name_id_map)[col_name]);
  284. }
  285. return Status::OK();
  286. }
  287. // Create the final column name to index mapping and get indices of the columns this mapop does not use.
  288. void MapOp::CreateFinalColMap(std::unordered_map<std::string, int32_t> *col_name_id_map) {
  289. std::unordered_map<std::string, int32_t> final_col_name_id_map;
  290. size_t num_cols = col_name_id_map->size();
  291. std::vector<int32_t> new_ids(num_cols);
  292. if (in_columns_.size() == out_columns_.size()) {
  293. for (size_t i = 0; i < in_columns_.size(); i++) {
  294. int32_t loc = (*col_name_id_map)[in_columns_[i]];
  295. (void)col_name_id_map->erase(in_columns_[i]);
  296. (*col_name_id_map)[out_columns_[i]] = loc;
  297. }
  298. // Set the base class final column id map result
  299. column_name_id_map_ = *col_name_id_map;
  300. } else {
  301. int32_t fill_idx = 0;
  302. // First columns of the tables are occupied by the output columns from tensorOp.
  303. for (const auto &col_name : out_columns_) {
  304. final_col_name_id_map[col_name] = fill_idx++;
  305. }
  306. // Creating new_ids mapping for the columns we keep.
  307. for (size_t i = 0; i < num_cols; i++) {
  308. if (keep_input_columns_[i]) {
  309. new_ids[i] = fill_idx++;
  310. }
  311. }
  312. // Iterating through the old mapping to update the final mapping for the columns we kept.
  313. std::string name;
  314. for (const auto &pair : *col_name_id_map) {
  315. name = pair.first;
  316. int32_t old_id = pair.second;
  317. if (keep_input_columns_[old_id]) {
  318. final_col_name_id_map[name] = new_ids[old_id];
  319. }
  320. }
  321. // Set the base class final column id map result
  322. column_name_id_map_ = final_col_name_id_map;
  323. }
  324. }
  325. // Visitor accept method for NodePass
  326. Status MapOp::Accept(NodePass *p, bool *modified) {
  327. // Downcast shared pointer then call visitor
  328. return p->RunOnNode(shared_from_base<MapOp>(), modified);
  329. }
  330. } // namespace dataset
  331. } // namespace mindspore