Impala
Impalaistheopensource,nativeanalyticdatabaseforApacheHadoop.
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros
hash-benchmark.cc
Go to the documentation of this file.
1 // Copyright 2012 Cloudera Inc.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include <iostream>
16 #include <stdlib.h>
17 #include <stdio.h>
18 #include <vector>
19 
20 #include <boost/functional/hash.hpp>
21 
22 #include "codegen/llvm-codegen.h"
24 #include "runtime/mem-tracker.h"
25 #include "runtime/raw-value.h"
26 #include "runtime/string-value.h"
27 #include "util/benchmark.h"
28 #include "util/cpu-info.h"
29 #include "util/hash-util.h"
30 
31 #include "common/names.h"
32 
33 using boost::hash_combine;
34 using boost::hash_range;
35 using namespace impala;
36 using namespace llvm;
37 
38 
39 // Benchmark tests for hashing tuples. There are two sets of inputs
40 // that are benchmarked. The 'Int' set which consists of hashing tuples
41 // with 4 int32_t values. The 'Mixed' set consists of tuples with different
42 // data types, including strings. The resulting hashes are put into 1000
43 // buckets so the expected number of collisions is roughly ~1/3.
44 //
45 // The different hash functions benchmarked:
46 // 1. FNV Hash: Fowler-Noll-Vo hash function
47 // 2. Boost Hash: boost hash function
48 // 3. Crc: hash using sse4 crc hash instruction
49 // 4. Codegen: hash using sse4 with the tuple types baked into the codegen function
50 //
51 // n is the number of buckets, k is the number of items
52 // Expected(collisions) = n - k + E(X)
53 // = n - k + k(1 - 1/k)^n
54 // For k == n (1000 items into 1000 buckets)
55 // = lim n->inf n(1 - 1/n) ^n
56 // = n / e
57 // = 367
58 
59 // Machine Info: Intel(R) Core(TM) i7-2600 CPU @ 3.40GHz
60 // Int Hash: Function Rate (iters/ms) Comparison
61 // ----------------------------------------------------------------------
62 // Fnv 76.46 1X
63 // Boost 203.7 2.665X
64 // Crc 305.3 3.993X
65 // Codegen 703.4 9.199X
66 
67 // Mixed Hash: Function Rate (iters/ms) Comparison
68 // ----------------------------------------------------------------------
69 // Fnv 55.51 1X
70 // Boost 82.86 1.493X
71 // Crc 215.1 3.874X
72 // Codegen 219.8 3.959X
73 
74 typedef uint32_t (*CodegenHashFn)(int rows, char* data, int32_t* results);
75 
76 struct TestData {
77  void* data;
78  int num_cols;
79  int num_rows;
80  vector<int32_t> results;
81  void* jitted_fn;
82 };
83 
84 void TestFnvIntHash(int batch, void* d) {
85  TestData* data = reinterpret_cast<TestData*>(d);
86  int rows = data->num_rows;
87  int cols = data->num_cols;
88  for (int i = 0; i < batch; ++i) {
89  int32_t* values = reinterpret_cast<int32_t*>(data->data);
90  for (int j = 0; j < rows; ++j) {
91  size_t hash = HashUtil::FNV_SEED;
92  for (int k = 0; k < cols; ++k) {
93  hash = HashUtil::FnvHash64to32(&values[k], sizeof(uint32_t), hash);
94  }
95  data->results[j] = hash;
96  values += cols;
97  }
98  }
99 }
100 
101 void TestCrcIntHash(int batch, void* d) {
102  TestData* data = reinterpret_cast<TestData*>(d);
103  int rows = data->num_rows;
104  int cols = data->num_cols;
105  for (int i = 0; i < batch; ++i) {
106  int32_t* values = reinterpret_cast<int32_t*>(data->data);
107  for (int j = 0; j < rows; ++j) {
108  size_t hash = HashUtil::FNV_SEED;
109  for (int k = 0; k < cols; ++k) {
110  hash = HashUtil::CrcHash(&values[k], sizeof(uint32_t), hash);
111  }
112  data->results[j] = hash;
113  values += cols;
114  }
115  }
116 }
117 
118 void TestBoostIntHash(int batch, void* d) {
119  TestData* data = reinterpret_cast<TestData*>(d);
120  int rows = data->num_rows;
121  int cols = data->num_cols;
122  for (int i = 0; i < batch; ++i) {
123  int32_t* values = reinterpret_cast<int32_t*>(data->data);
124  for (int j = 0; j < rows; ++j) {
125  size_t h = HashUtil::FNV_SEED;
126  for (int k = 0; k < cols; ++k) {
127  size_t hash_value = boost::hash<int32_t>().operator()(values[k]);
128  hash_combine(h, hash_value);
129  }
130  data->results[j] = h;
131  values += cols;
132  }
133  }
134 }
135 
136 void TestCodegenIntHash(int batch, void* d) {
137  TestData* data = reinterpret_cast<TestData*>(d);
138  CodegenHashFn fn = reinterpret_cast<CodegenHashFn>(data->jitted_fn);
139  int rows = data->num_rows;
140  for (int i = 0; i < batch; ++i) {
141  char* values = reinterpret_cast<char*>(data->data);
142  fn(rows, values, &data->results[0]);
143  }
144 }
145 
146 void TestFnvMixedHash(int batch, void* d) {
147  TestData* data = reinterpret_cast<TestData*>(d);
148  int rows = data->num_rows;
149  for (int i = 0; i < batch; ++i) {
150  char* values = reinterpret_cast<char*>(data->data);
151  for (int j = 0; j < rows; ++j) {
152  size_t hash = HashUtil::FNV_SEED;
153 
154  hash = HashUtil::FnvHash64to32(values, sizeof(int8_t), hash);
155  values += sizeof(int8_t);
156 
157  hash = HashUtil::FnvHash64to32(values, sizeof(int32_t), hash);
158  values += sizeof(int32_t);
159 
160  hash = HashUtil::FnvHash64to32(values, sizeof(int64_t), hash);
161  values += sizeof(int64_t);
162 
163  StringValue* str = reinterpret_cast<StringValue*>(values);
164  hash = HashUtil::FnvHash64to32(str->ptr, str->len, hash);
165  values += sizeof(StringValue);
166 
167  data->results[j] = hash;
168  }
169  }
170 }
171 
172 void TestCrcMixedHash(int batch, void* d) {
173  TestData* data = reinterpret_cast<TestData*>(d);
174  int rows = data->num_rows;
175  for (int i = 0; i < batch; ++i) {
176  char* values = reinterpret_cast<char*>(data->data);
177  for (int j = 0; j < rows; ++j) {
178  size_t hash = HashUtil::FNV_SEED;
179 
180  hash = HashUtil::CrcHash(values, sizeof(int8_t), hash);
181  values += sizeof(int8_t);
182 
183  hash = HashUtil::CrcHash(values, sizeof(int32_t), hash);
184  values += sizeof(int32_t);
185 
186  hash = HashUtil::CrcHash(values, sizeof(int64_t), hash);
187  values += sizeof(int64_t);
188 
189  StringValue* str = reinterpret_cast<StringValue*>(values);
190  hash = HashUtil::CrcHash(str->ptr, str->len, hash);
191  values += sizeof(StringValue);
192 
193  data->results[j] = hash;
194  }
195  }
196 }
197 
198 void TestCodegenMixedHash(int batch, void* d) {
199  TestData* data = reinterpret_cast<TestData*>(d);
200  CodegenHashFn fn = reinterpret_cast<CodegenHashFn>(data->jitted_fn);
201  int rows = data->num_rows;
202  for (int i = 0; i < batch; ++i) {
203  char* values = reinterpret_cast<char*>(data->data);
204  fn(rows, values, &data->results[0]);
205  }
206 }
207 
208 void TestBoostMixedHash(int batch, void* d) {
209  TestData* data = reinterpret_cast<TestData*>(d);
210  int rows = data->num_rows;
211  for (int i = 0; i < batch; ++i) {
212  char* values = reinterpret_cast<char*>(data->data);
213  for (int j = 0; j < rows; ++j) {
214  size_t h = HashUtil::FNV_SEED;
215 
216  size_t hash_value = boost::hash<int8_t>().operator()(*reinterpret_cast<int8_t*>(values));
217  hash_combine(h, hash_value);
218  values += sizeof(int8_t);
219 
220  hash_value = boost::hash<int32_t>().operator()(*reinterpret_cast<int32_t*>(values));
221  hash_combine(h, hash_value);
222  values += sizeof(int32_t);
223 
224  hash_value = boost::hash<int64_t>().operator()(*reinterpret_cast<int64_t*>(values));
225  hash_combine(h, hash_value);
226  values += sizeof(int64_t);
227 
228  StringValue* str = reinterpret_cast<StringValue*>(values);
229  hash_value = hash_range<char*>(str->ptr, str->ptr + str->len);
230  hash_combine(h, hash_value);
231  values += sizeof(StringValue);
232 
233  data->results[j] = h;
234  }
235  }
236 }
237 
238 int NumCollisions(TestData* data, int num_buckets) {
239  vector<bool> buckets;
240  buckets.resize(num_buckets);
241 
242  int num_collisions = 0;
243  for (int i = 0; i < data->results.size(); ++i) {
244  uint32_t hash = data->results[i];
245  int bucket = hash % num_buckets;
246  if (buckets[bucket]) ++num_collisions;
247  buckets[bucket] = true;
248  }
249  memset(&data->results[0], 0, data->results.size() * sizeof(uint32_t));
250  return num_collisions;
251 }
252 
253 // Codegen for looping through a batch of tuples
254 // define void @HashInt(i32 %rows, i8* %data, i32* %results) {
255 // entry:
256 // %0 = icmp sgt i32 %rows, 0
257 // br i1 %0, label %loop, label %exit
258 //
259 // loop: ; preds = %loop, %entry
260 // %counter = phi i32 [ 0, %entry ], [ %1, %loop ]
261 // %1 = add i32 %counter, 1
262 // %2 = mul i32 %counter, 16
263 // %3 = getelementptr i8* %data, i32 %2
264 // %4 = call i32 @CrcHash16(i8* %3, i32 0, i32 -2128831035)
265 // %5 = getelementptr i32* %results, i32 %counter
266 // store i32 %4, i32* %5
267 // %6 = icmp slt i32 %counter, %rows
268 // br i1 %6, label %loop, label %exit
269 //
270 // exit: ; preds = %loop, %entry
271 // ret void
272 // }
273 Function* CodegenCrcHash(LlvmCodeGen* codegen, bool mixed) {
274  string name = mixed ? "HashMixed" : "HashInt";
275  LlvmCodeGen::FnPrototype prototype(codegen, name, codegen->void_type());
276  prototype.AddArgument(
277  LlvmCodeGen::NamedVariable("rows", codegen->GetType(TYPE_INT)));
278  prototype.AddArgument(
279  LlvmCodeGen::NamedVariable("data", codegen->ptr_type()));
280  prototype.AddArgument(
281  LlvmCodeGen::NamedVariable("results", codegen->GetPtrType(TYPE_INT)));
282 
283  LlvmCodeGen::LlvmBuilder builder(codegen->context());
284  Value* args[3];
285  Function* fn = prototype.GeneratePrototype(&builder, &args[0]);
286 
287  BasicBlock* loop_start = builder.GetInsertBlock();
288  BasicBlock* loop_body = BasicBlock::Create(codegen->context(), "loop", fn);
289  BasicBlock* loop_exit = BasicBlock::Create(codegen->context(), "exit", fn);
290 
291  int fixed_byte_size = mixed ?
292  sizeof(int8_t) + sizeof(int32_t) + sizeof(int64_t) : sizeof(int32_t) * 4;
293 
294  Function* fixed_fn = codegen->GetHashFunction(fixed_byte_size);
295  Function* string_hash_fn = codegen->GetHashFunction();
296 
297  Value* row_size = NULL;
298  if (mixed) {
299  row_size = codegen->GetIntConstant(TYPE_INT,
300  sizeof(int8_t) + sizeof(int32_t) + sizeof(int64_t) + sizeof(StringValue));
301  } else {
302  row_size = codegen->GetIntConstant(TYPE_INT, fixed_byte_size);
303  }
304  Value* dummy_len = codegen->GetIntConstant(TYPE_INT, 0);
305 
306  // Check loop counter
307  Value* counter_check =
308  builder.CreateICmpSGT(args[0], codegen->GetIntConstant(TYPE_INT, 0));
309  builder.CreateCondBr(counter_check, loop_body, loop_exit);
310 
311  // Loop body
312  builder.SetInsertPoint(loop_body);
313  PHINode* counter = builder.CreatePHI(codegen->GetType(TYPE_INT), 2, "counter");
314  counter->addIncoming(codegen->GetIntConstant(TYPE_INT, 0), loop_start);
315 
316  Value* next_counter = builder.CreateAdd(counter, codegen->GetIntConstant(TYPE_INT, 1));
317  counter->addIncoming(next_counter, loop_body);
318 
319  // Hash the current data
320  Value* offset = builder.CreateMul(counter, row_size);
321  Value* data = builder.CreateGEP(args[1], offset);
322 
323  Value* seed = codegen->GetIntConstant(TYPE_INT, HashUtil::FNV_SEED);
324  seed = builder.CreateCall3(fixed_fn, data, dummy_len, seed);
325 
326  // Get the string data
327  if (mixed) {
328  Value* string_data = builder.CreateGEP(
329  data, codegen->GetIntConstant(TYPE_INT, fixed_byte_size));
330  Value* string_val =
331  builder.CreateBitCast(string_data, codegen->GetPtrType(TYPE_STRING));
332  Value* str_ptr = builder.CreateStructGEP(string_val, 0);
333  Value* str_len = builder.CreateStructGEP(string_val, 1);
334  str_ptr = builder.CreateLoad(str_ptr);
335  str_len = builder.CreateLoad(str_len);
336  seed = builder.CreateCall3(string_hash_fn, str_ptr, str_len, seed);
337  }
338 
339  Value* result = builder.CreateGEP(args[2], counter);
340  builder.CreateStore(seed, result);
341 
342  counter_check = builder.CreateICmpSLT(next_counter, args[0]);
343  builder.CreateCondBr(counter_check, loop_body, loop_exit);
344 
345  // Loop exit
346  builder.SetInsertPoint(loop_exit);
347  builder.CreateRetVoid();
348 
349  return codegen->FinalizeFunction(fn);
350 }
351 
352 int main(int argc, char **argv) {
353  CpuInfo::Init();
354  cout << Benchmark::GetMachineInfo() << endl;
356 
357  const int NUM_ROWS = 1024;
358 
361  MemPool mem_pool(&tracker);
362  RuntimeProfile int_profile(&obj_pool, "IntGen");
363  RuntimeProfile mixed_profile(&obj_pool, "MixedGen");
364  DataProvider int_provider(&mem_pool, &int_profile);
365  DataProvider mixed_provider(&mem_pool, &mixed_profile);
366 
367  Status status;
368  scoped_ptr<LlvmCodeGen> codegen;
369  status = LlvmCodeGen::LoadImpalaIR(&obj_pool, "test", &codegen);
370  if (!status.ok()) {
371  cout << "Could not start codegen.";
372  return -1;
373  }
374  codegen->EnableOptimizations(true);
375 
376  Function* hash_ints = CodegenCrcHash(codegen.get(), false);
377  void* jitted_hash_ints;
378  codegen->AddFunctionToJit(hash_ints, &jitted_hash_ints);
379 
380  Function* hash_mixed = CodegenCrcHash(codegen.get(), true);
381  void* jitted_hash_mixed;
382  codegen->AddFunctionToJit(hash_mixed, &jitted_hash_mixed);
383 
384  status = codegen->FinalizeModule();
385  if (!status.ok()) {
386  cout << "Could not compile module: " << status.GetDetail();
387  return -1;
388  }
389 
390  // Test a tuple consisting of just 4 int cols. This is representative of
391  // group by queries. The test is hardcoded to know it will only be int cols
392  vector<DataProvider::ColDesc> int_cols;
393  int_cols.push_back(DataProvider::ColDesc::Create<int32_t>(0, 10000));
394  int_cols.push_back(DataProvider::ColDesc::Create<int32_t>(0, 1000000));
395  int_cols.push_back(DataProvider::ColDesc::Create<int32_t>(0, 1000000));
396  int_cols.push_back(DataProvider::ColDesc::Create<int32_t>(0, 1000000));
397 
398  int_provider.Reset(NUM_ROWS, NUM_ROWS, int_cols);
399 
400  TestData int_data;
401  int_data.data = int_provider.NextBatch(&int_data.num_rows);
402  int_data.num_cols = int_cols.size();
403  int_data.results.resize(int_data.num_rows);
404  int_data.jitted_fn = jitted_hash_ints;
405 
406  // Some mixed col types. The test hash function will know the exact
407  // layout. This is reasonable to do since we can use llvm
408  string min_std_str("aaa");
409  string max_std_str("zzzzzzzzzz");
410 
411  StringValue min_str(const_cast<char*>(min_std_str.c_str()), min_std_str.size());
412  StringValue max_str(const_cast<char*>(max_std_str.c_str()), max_std_str.size());
413 
414  vector<DataProvider::ColDesc> mixed_cols;
415  mixed_cols.push_back(DataProvider::ColDesc::Create<int8_t>(0, 25));
416  mixed_cols.push_back(DataProvider::ColDesc::Create<int32_t>(0, 10000));
417  mixed_cols.push_back(DataProvider::ColDesc::Create<int64_t>(0, 100000000));
418  mixed_cols.push_back(DataProvider::ColDesc::Create<StringValue>(min_str, max_str));
419  mixed_provider.Reset(NUM_ROWS, NUM_ROWS, mixed_cols);
420 
421  TestData mixed_data;
422  mixed_data.data = mixed_provider.NextBatch(&mixed_data.num_rows);
423  mixed_data.num_cols = mixed_cols.size();
424  mixed_data.results.resize(mixed_data.num_rows);
425  mixed_data.jitted_fn = jitted_hash_mixed;
426 
427  Benchmark int_suite("Int Hash");
428  int_suite.AddBenchmark("Fnv", TestFnvIntHash, &int_data);
429  int_suite.AddBenchmark("Boost", TestBoostIntHash, &int_data);
430  int_suite.AddBenchmark("Crc", TestCrcIntHash, &int_data);
431  int_suite.AddBenchmark("Codegen", TestCodegenIntHash, &int_data);
432  cout << int_suite.Measure() << endl;
433 
434  Benchmark mixed_suite("Mixed Hash");
435  mixed_suite.AddBenchmark("Fnv", TestFnvMixedHash, &mixed_data);
436  mixed_suite.AddBenchmark("Boost", TestBoostMixedHash, &mixed_data);
437  mixed_suite.AddBenchmark("Crc", TestCrcMixedHash, &mixed_data);
438  mixed_suite.AddBenchmark("Codegen", TestCodegenMixedHash, &mixed_data);
439  cout << mixed_suite.Measure();
440 
441  return 0;
442 }
static Status LoadImpalaIR(ObjectPool *, const std::string &id, boost::scoped_ptr< LlvmCodeGen > *codegen)
int AddBenchmark(const std::string &name, BenchmarkFunction fn, void *args, int baseline_idx=0)
Definition: benchmark.cc:70
const std::string GetDetail() const
Definition: status.cc:184
void Reset(int num_rows, int batch_size, const std::vector< ColDesc > &columns)
llvm::PointerType * GetPtrType(llvm::Type *type)
Return a pointer type to 'type'.
MemTracker tracker
void TestCrcMixedHash(int batch, void *d)
Utility struct that wraps a variable name and llvm type.
Definition: llvm-codegen.h:149
uint32_t(* CodegenHashFn)(int rows, char *data, int32_t *results)
static std::string GetMachineInfo()
Output machine/build configuration as a string.
Definition: benchmark.cc:124
const StringSearch UrlParser::hash_search & hash
Definition: url-parser.cc:41
void * data
vector< int32_t > results
std::string Measure()
Runs all the benchmarks and returns the result in a formatted string.
Definition: benchmark.cc:83
void * NextBatch(int *rows_returned)
int main(int argc, char **argv)
Function * CodegenCrcHash(LlvmCodeGen *codegen, bool mixed)
std::size_t hash_value(const Decimal4Value &v)
This function must be called 'hash_value' to be picked up by boost.
void TestCodegenMixedHash(int batch, void *d)
void TestFnvMixedHash(int batch, void *d)
See data-provider-test.cc on how to use this.
Definition: data-provider.h:33
ObjectPool * obj_pool()
Returns a local object pool.
Definition: coordinator.h:263
LLVM code generator. This is the top level object to generate jitted code.
Definition: llvm-codegen.h:107
void AddArgument(const NamedVariable &var)
Add argument.
Definition: llvm-codegen.h:171
void * jitted_fn
llvm::Function * GetHashFunction(int num_bytes=-1)
int NumCollisions(TestData *data, int num_buckets)
void TestCrcIntHash(int batch, void *d)
This class is thread-safe.
Definition: mem-tracker.h:61
static const uint32_t FNV_SEED
Definition: hash-util.h:99
void TestCodegenIntHash(int batch, void *d)
static void InitializeLlvm(bool load_backend=false)
Definition: llvm-codegen.cc:78
vector< StringValue > data
void TestBoostMixedHash(int batch, void *d)
static uint32_t CrcHash(const void *data, int32_t bytes, uint32_t hash)
Definition: hash-util.h:37
llvm::Type * GetType(const ColumnType &type)
Returns llvm type for the column type.
uint8_t offset[7 *64-sizeof(uint64_t)]
void TestBoostIntHash(int batch, void *d)
llvm::Value * GetIntConstant(PrimitiveType type, int64_t val)
Returns the constant 'val' of 'type'.
static uint32_t FnvHash64to32(const void *data, int32_t bytes, uint32_t hash)
Definition: hash-util.h:125
static void Init()
Initialize CpuInfo.
Definition: cpu-info.cc:75
llvm::Function * FinalizeFunction(llvm::Function *function)
bool ok() const
Definition: status.h:172
llvm::Type * void_type()
Definition: llvm-codegen.h:394
string name
Definition: cpu-info.cc:50
llvm::LLVMContext & context()
Definition: llvm-codegen.h:214
llvm::PointerType * ptr_type()
Definition: llvm-codegen.h:393
void TestFnvIntHash(int batch, void *d)