HEBench
generic_wl_benchmark.cpp
Go to the documentation of this file.
1 
2 // Copyright (C) 2021 Intel Corporation
3 // SPDX-License-Identifier: Apache-2.0
4 
6 
7 #include <algorithm>
8 #include <cassert>
9 #include <cstring>
10 #include <functional>
11 #include <memory>
12 #include <numeric>
13 #include <utility>
14 #include <vector>
15 
16 #include "../include/generic_wl_benchmark.h"
17 #include "../include/generic_wl_engine.h"
18 
19 //-----------------------------------
20 // class ExampleBenchmarkDescription
21 //-----------------------------------
22 
23 ExampleBenchmarkDescription::ExampleBenchmarkDescription(hebench::APIBridge::Category category)
24 {
28 
29  // initialize the descriptor for this benchmark
30  std::memset(&m_descriptor, 0, sizeof(hebench::APIBridge::BenchmarkDescriptor));
31  m_descriptor.workload = hebench::APIBridge::Workload::Generic;
32  m_descriptor.data_type = hebench::APIBridge::DataType::Float64;
33  m_descriptor.category = category;
35  {
36  m_descriptor.cat_params.min_test_time_ms = 0;
37  m_descriptor.cat_params.latency.warmup_iterations_count = 1;
38  } // end if
39  m_descriptor.cipher_param_mask = HEBENCH_HE_PARAM_FLAGS_ALL_PLAIN;
40  //
41  m_descriptor.scheme = HEBENCH_HE_SCHEME_PLAIN;
42  m_descriptor.security = HEBENCH_HE_SECURITY_NONE;
43  m_descriptor.other = 0; // no extras needed for our purpose:
44  // Other backends can use this field to differentiate between
45  // benchmarks for which internal parameters, not specified by
46  // other fields of this structure, differ.
47 
48  // specify default arguments for this workload
49  hebench::cpp::WorkloadParams::Generic default_workload_params(2, 3);
50  default_workload_params.length_InputParam(0) = 2;
51  default_workload_params.length_InputParam(1) = 2;
52  default_workload_params.length_ResultComponent(0) = 2;
53  default_workload_params.length_ResultComponent(1) = 2;
54  default_workload_params.length_ResultComponent(2) = 1;
55  this->addDefaultParameters(default_workload_params);
56 }
57 
58 ExampleBenchmarkDescription::~ExampleBenchmarkDescription()
59 {
60  // nothing needed in this example
61 }
62 
64  const hebench::APIBridge::WorkloadParams *p_params)
65 {
66  if (!p_params)
67  throw hebench::cpp::HEBenchError(HEBERROR_MSG_CLASS("Invalid empty workload parameters. This workload requires flexible parameters."),
69 
70  ExampleEngine &ex_engine = dynamic_cast<ExampleEngine &>(engine);
71  return new ExampleBenchmark(ex_engine, m_descriptor, *p_params);
72 }
73 
74 void ExampleBenchmarkDescription::destroyBenchmark(hebench::cpp::BaseBenchmark *p_bench)
75 {
76  // make sure we are destroying a benchmark object we created
77  if (p_bench)
78  {
79  ExampleBenchmark *p_tmp = dynamic_cast<ExampleBenchmark *>(p_bench);
80  delete p_tmp;
81  } // end if
82 }
83 
84 //------------------------
85 // class ExampleBenchmark
86 //------------------------
87 
88 ExampleBenchmark::ExampleBenchmark(ExampleEngine &engine,
90  const hebench::APIBridge::WorkloadParams &bench_params) :
91  hebench::cpp::BaseBenchmark(engine, bench_desc, bench_params)
92 {
93  // validate workload parameters
94 
95  // number of workload parameters
96  if (bench_params.count < ExampleBenchmarkDescription::NumWorkloadParams)
97  throw hebench::cpp::HEBenchError(HEBERROR_MSG_CLASS("Invalid workload parameters. This workload requires " + std::to_string(ExampleBenchmarkDescription::NumWorkloadParams) + " parameters."),
99 
100  // check values of the workload parameters and make sure they are supported by benchmark:
101 
102  hebench::cpp::WorkloadParams::Generic w_params(bench_params);
103 
104  if (w_params.n() != ParametersCount
105  || w_params.m() != ResultComponentsCount)
106  throw hebench::cpp::HEBenchError(HEBERROR_MSG_CLASS("Invalid workload parameters for `n` and `m`."),
108  if (w_params.length_InputParam(0) != 2
109  || w_params.length_InputParam(1) != 2)
110  throw hebench::cpp::HEBenchError(HEBERROR_MSG_CLASS("Invalid workload parameters for `length_InputParam`."),
112  if (w_params.length_ResultComponent(0) != 2
113  || w_params.length_ResultComponent(1) != 2
114  || w_params.length_ResultComponent(2) != 1)
115  throw hebench::cpp::HEBenchError(HEBERROR_MSG_CLASS("Invalid workload parameters for `length_ResultComponent`."),
117 }
118 
119 ExampleBenchmark::~ExampleBenchmark()
120 {
121  // nothing needed in this example
122 }
123 
125 {
126  if (p_parameters->pack_count != ParametersCount)
127  throw hebench::cpp::HEBenchError(HEBERROR_MSG_CLASS("Invalid number of parameters detected in parameter pack. Expected " + std::to_string(ParametersCount) + "."),
129 
130  // allocate our internal version of the encoded data
131 
132  InternalInputData input_data;
133 
134  for (std::size_t param_i = 0; param_i < input_data.size(); ++param_i)
135  {
136  std::vector<std::vector<double>> &input_param_batch = input_data[param_i];
137  const hebench::APIBridge::DataPack &data_pack = findDataPack(*p_parameters, param_i);
138  input_param_batch.resize(data_pack.buffer_count);
139  for (std::size_t sample_i = 0; sample_i < input_param_batch.size(); ++sample_i)
140  {
141  const hebench::APIBridge::NativeDataBuffer &sample_buffer = data_pack.p_buffers[sample_i];
142  if (!sample_buffer.p)
143  throw hebench::cpp::HEBenchError(HEBERROR_MSG_CLASS("Invalid empty sample in input parameter."),
145  const double *p_raw_buffer = reinterpret_cast<const double *>(sample_buffer.p);
146  input_param_batch[sample_i].assign(p_raw_buffer, p_raw_buffer + (sample_buffer.size / sizeof(double)));
147  }
148  } // end if
149 
150  // wrap our internal object into a handle to cross the boundary of the API Bridge
151  return this->getEngine().template createHandle<decltype(input_data)>(sizeof(InternalInputData), 0,
152  std::move(input_data));
153 }
154 
156 {
157  // This method should handle decoding of data encoded using encode(), due to
158  // specification stating that encode() and decode() are inverses; as well as
159  // handle data decrypted from operation() results.
160 
161  // retrieve our internal format object from the handle
162  const InternalResultData &encoded =
163  this->getEngine().template retrieveFromHandle<InternalResultData>(encoded_data);
164 
165  // according to specification, we must decode as much data as possible, where
166  // any excess encoded data that won't fit into the pre-allocated native buffer
167  // shall be ignored
168 
169  std::uint64_t min_component_count = std::min(p_native->pack_count, static_cast<std::uint64_t>(ResultComponentsCount));
170  for (std::size_t component_i = 0; component_i < min_component_count; ++component_i)
171  {
172  hebench::APIBridge::DataPack *p_native_param = &p_native->p_data_packs[component_i];
173 
174  if (p_native_param && p_native_param->buffer_count > 0)
175  {
176  std::uint64_t min_sample_count = std::min(p_native_param->buffer_count, encoded.size());
177  for (std::size_t sample_i = 0; sample_i < min_sample_count; ++sample_i)
178  {
179  // for latency, we have only one sample, so, decode the sample into the first buffer
180  hebench::APIBridge::NativeDataBuffer &native_sample = p_native_param->p_buffers[sample_i];
181  if (native_sample.p && native_sample.size > 0)
182  {
183  std::uint64_t min_size = std::min(native_sample.size / sizeof(double),
184  encoded[sample_i][component_i].size());
185  double *p_raw_buffer = reinterpret_cast<double *>(native_sample.p);
186  std::copy(encoded[sample_i][component_i].begin(), encoded[sample_i][component_i].begin() + min_size,
187  p_raw_buffer);
188  } // end if
189  } // end for
190  } // end if
191  } // end for
192 }
193 
195 {
196  // we only do plain text in this example, so, just return a copy of our internal data
197  return this->getEngine().duplicateHandle(encoded_data);
198 }
199 
201 {
202  // we only do plain text in this example, so, just return a copy of our internal data
203  return this->getEngine().duplicateHandle(encrypted_data);
204 }
206 {
207  if (count != 1)
208  // we do all ops in plain text, so, we should get only one pack of data
209  throw hebench::cpp::HEBenchError(HEBERROR_MSG_CLASS("Invalid number of handles. Expected 1."),
211  if (!p_local_data)
212  throw hebench::cpp::HEBenchError(HEBERROR_MSG_CLASS("Invalid null array of handles: \"p_local_data\""),
214 
215  // since remote and host are the same for this example, we just need to return a copy
216  // of the local data as remote.
217 
218  return this->getEngine().duplicateHandle(p_local_data[0]);
219 }
220 
222  hebench::APIBridge::Handle *p_local_data, std::uint64_t count)
223 {
224  if (count > 0 && !p_local_data)
225  throw hebench::cpp::HEBenchError(HEBERROR_MSG_CLASS("Invalid null array of handles: \"p_local_data\""),
227 
228  if (count > 0)
229  {
230  // pad with zeros any remaining local handles as per specifications
231  std::memset(p_local_data, 0, sizeof(hebench::APIBridge::Handle) * count);
232 
233  // since remote and host are the same for this example, we just need to return a copy
234  // of the remote as local data.
235 
236  p_local_data[0] = this->getEngine().duplicateHandle(remote_data);
237  } // end if
238 }
239 
241  const hebench::APIBridge::ParameterIndexer *p_param_indexers)
242 {
243  // This method should perform as fast as possible since it is the
244  // method benchmarked by Test Harness.
245 
246  const InternalInputData &InputParams = this->getEngine().template retrieveFromHandle<InternalInputData>(h_remote_packed);
247  assert(InputParams.size() == ParametersCount);
248 
249  for (std::size_t i = 0; i < InputParams.size(); ++i)
250  // normally, a robust backend will use the indexers as appropriate,
251  // but for the sake of the example, we just validate them
252  if (p_param_indexers[i].value_index != 0 || p_param_indexers[i].batch_size != InputParams[i].size())
253  throw hebench::cpp::HEBenchError(HEBERROR_MSG_CLASS("Invalid parameter indexer. Expected index 0 and batch size of 1."),
255 
256  // create a new internal object for result
257  InternalResultData retval;
258  retval.reserve(InputParams[0].size() * InputParams[1].size());
259 
260  // perform the actual operation
261  hebench::cpp::WorkloadParams::Generic w_params(this->getWorkloadParameters());
262  for (std::size_t sample_0_i = 0; sample_0_i < InputParams[0].size(); ++sample_0_i)
263  {
264  for (std::size_t sample_1_i = 0; sample_1_i < InputParams[1].size(); ++sample_1_i)
265  {
266  assert(InputParams[0][sample_0_i].size() == w_params.length_InputParam(0));
267  assert(InputParams[1][sample_0_i].size() == w_params.length_InputParam(1));
268  std::array<std::vector<double>, ResultComponentsCount> ResultComponents;
269 
270  // ResultComponent[0] = InputParam[0] + InputParam[1]
271  ResultComponents[0].resize(w_params.length_ResultComponent(0));
272  std::transform(InputParams[0][sample_0_i].begin(), InputParams[0][sample_0_i].end(),
273  InputParams[1][sample_1_i].begin(), ResultComponents[0].begin(),
274  std::plus<double>());
275 
276  // ResultComponent[1] = InputParam[0] - InputParam[1]
277  ResultComponents[1].resize(w_params.length_ResultComponent(1));
278  std::transform(InputParams[0][sample_0_i].begin(), InputParams[0][sample_0_i].end(),
279  InputParams[1][sample_1_i].begin(), ResultComponents[1].begin(),
280  std::minus<double>());
281 
282  // ResultComponent[2] = InputParam[0] . InputParam[1]
283  ResultComponents[2].resize(w_params.length_ResultComponent(2));
284  ResultComponents[2].front() = std::inner_product(InputParams[0][sample_0_i].begin(), InputParams[0][sample_0_i].end(),
285  InputParams[1][sample_1_i].begin(), 0.0);
286 
287  retval.emplace_back(std::move(ResultComponents));
288  } // end for
289  } // end for
290 
291  // send our internal result across the boundary of the API Bridge as a handle
292  return this->getEngine().template createHandle<decltype(retval)>(sizeof(retval), 0,
293  std::move(retval));
294 }
295 
Top level opaque benchmark class.
Definition: benchmark.hpp:143
Base class that encapsulates common behavior of backend engines.
Definition: engine.hpp:70
Wraps around flexible workload parameters required for a generic workload.
#define HEBERROR_MSG_CLASS(message)
@ Float64
64 bits IEEE 754 standard floating point real numbers.
Definition: types.h:306
ErrorCode encrypt(Handle h_benchmark, Handle h_plaintext, Handle *h_ciphertext)
Encrypts a plain text into a cipher text.
Category
Defines all possible categories for each workload.
Definition: types.h:391
ErrorCode operate(Handle h_benchmark, Handle h_remote_packed_params, const ParameterIndexer *p_param_indexers, uint64_t indexers_count, Handle *h_remote_output)
Performs the workload operation of the benchmark.
std::uint64_t count
Number of workload parameters.
Definition: types.h:371
DataPack * p_data_packs
Collection of data packs.
Definition: types.h:625
ErrorCode encode(Handle h_benchmark, const DataPackCollection *p_parameters, Handle *h_plaintext)
Given a pack of parameters in raw, native data format, encodes them into plain text suitable for back...
ErrorCode decrypt(Handle h_benchmark, Handle h_ciphertext, Handle *h_plaintext)
Decrypts a cipher text into corresponding plain text.
ErrorCode store(Handle h_benchmark, Handle h_remote, Handle *h_local_packed_params, std::uint64_t local_count)
Retrieves the specified data from the backend.
std::uint64_t pack_count
Number of data packs in the collection.
Definition: types.h:626
ErrorCode createBenchmark(Handle h_engine, Handle h_bench_desc, const WorkloadParams *p_params, Handle *h_benchmark)
Instantiates a benchmark on the backend.
std::uint64_t size
Size of underlying data.
Definition: types.h:564
void * p
Pointer to underlying data.
Definition: types.h:558
ErrorCode load(Handle h_benchmark, const Handle *h_local_packed_params, std::uint64_t local_count, Handle *h_remote)
Loads the specified data from the local host into the remote backend to use as parameter during a cal...
std::uint64_t buffer_count
Number of data buffers in p_buffers.
Definition: types.h:613
ErrorCode decode(Handle h_benchmark, Handle h_plaintext, DataPackCollection *p_native)
Decodes plaintext data into the appropriate raw, native format.
NativeDataBuffer * p_buffers
Array of data buffers for parameter.
Definition: types.h:612
Defines a benchmark test.
Definition: types.h:527
Defines a data package for an operation.
Definition: types.h:611
Defines a collection of data packs.
Definition: types.h:624
Specifies the parameters for a workload.
Definition: types.h:363
Structure to contain flexible data.
Definition: types.h:552
std::string to_string(const std::string_view &s)
#define HEBENCH_HE_PARAM_FLAGS_ALL_PLAIN
Definition: types.h:75
#define HEBENCH_ECODE_INVALID_ARGS
Indicates invalid arguments to function call.
Definition: types.h:42
#define HEBENCH_ECODE_CRITICAL_ERROR
Specifies a critical error.
Definition: types.h:50