Impala
Impalaistheopensource,nativeanalyticdatabaseforApacheHadoop.
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros
growing-test.cc
Go to the documentation of this file.
1 #include <gtest/gtest.h>
2 
3 #include "hash-store.h"
4 #include "standard-hash-table.h"
5 #include "tuple-types.h"
6 #include "util/cpu-info.h"
7 #include "util/debug-util.h"
8 #include "util/stopwatch.h"
9 
10 using namespace impala;
11 
12 namespace impala {
13 
14 // Tests performance of hash aggregation with HashStore, which grows to contain multiple
15 // hash tables as needed. Allows exploring how buffer size and size of the keyspace
16 // affect performance.
17 class GrowingTest : public testing::Test {
18  public:
19  static const uint64_t NUM_PROBE_TUPLES = 100000000; // 10^8
20 
21  template <int buffer_size>
22  inline static uint64_t AggregateTest(uint64_t num_probe_tuples, int max_id,
23  int num_tables) {
24  ProbeTuple* tuples = GenTuples(num_probe_tuples, max_id);
25  HashStore<buffer_size> hs;
26  StopWatch watch;
27  watch.Start();
28  hs.Aggregate(tuples, num_probe_tuples);
29  watch.Stop();
30  SanityCheck<buffer_size>(hs, num_probe_tuples, max_id, num_tables);
31  free(tuples);
32  return watch.Ticks();
33  }
34 
35  // Confirm that hs appears to be the correct result of aggregating num_probe_tuples,
36  // with a keyspace of size max_id, with a fanout into num_tables tables.
37  template <int buffer_size>
38  static void SanityCheck(const HashStore<buffer_size> & hs,
39  uint64_t num_probe_tuples, int max_id, int num_tables) {
40  uint64_t total_count = 0;
41  for (int i = 0; i < hs.num_tables_; ++i) {
42  StandardHashTable& ht = hs.tables_[i];
43  for (StandardHashTable::Iterator it = ht.Begin(); it.HasNext(); it.Next()) {
44  total_count += it.GetRow()->count;
45  }
46  }
47  ASSERT_EQ(num_probe_tuples, total_count);
48  ASSERT_EQ(num_tables, hs.num_tables_)
49  << "You do not have the number of tables that you hoped.\n"
50  << "This could mean that there weren't enough probe tuples to fill the keyspace, "
51  << "skewed hashing lead a hash table that we expected not to overflow to overflow, "
52  << "or a genuine bug.";
53  if (buffer_size > 0) {
54  ASSERT_EQ(buffer_size, sizeof(typename HashStore<buffer_size>::Buffer));
55  }
56  }
57 
58  template <int buffer_size>
59  inline static uint64_t NextTestCase(int build_tuples, uint64_t prev, int num_tables) {
60  uint64_t time = AggregateTest<buffer_size>(NUM_PROBE_TUPLES, build_tuples, num_tables);
61  int64_t delta;
62  if (prev == 0) {
63  // just print 0 for the delta the first time.
64  delta = 0;
65  }
66  else {
67  delta = static_cast<int64_t>(time) - static_cast<int64_t>(prev);
68  }
69  LOG(ERROR) << build_tuples << "\t"
70  << PrettyPrinter::Print(time, TUnit::CPU_TICKS)
71  << "\t" << PrettyPrinter::Print(delta, TUnit::CPU_TICKS);
72  return time;
73  }
74 
75  // Run a test aggregation with buffers of size buffer_size bytes.
76  // Try the full range of working set size (and thus fanout) that we can do with
77  // single-level fanout.
78  template <int buffer_size>
79  inline static void Test() {
80  LOG(ERROR) << "Buffer size " << HashStore<buffer_size>::BUFFER_SIZE << " tuples ("
81  << PrettyPrinter::Print(buffer_size, TUnit::BYTES)
82  << "):";
83  LOG(ERROR) << "#BuildTuples\tTime\tdTime";
84  uint64_t prev = 0;
85  // only go up to 2^12 hashtables because after that, there are at least as many
86  // buffers as L2 cache lines. We won't let this happen; we'll do multi-level fanout.
87  // Note that we should probably allow 2 cache lines per buffer (since a write might
88  // span a line break), plus we need some memory aside from the buffers, so we'll
89  // actually stop at 2^11, or maybe 2^12. And we might want to keep them in L1,
90  // in which case we'll stop at 2^7 or so.
91  for (int num_tables = 1; num_tables <= 1<<12; num_tables *= 2) {
92  // how many total build tuples we'll use to fill num_tables tables
93  // Needs to be comfortably less than the total capacity of num_tables tables
94  // (the below expression without the constant factor multiplied by it)
95  // because the hashes will not be spread perfectly.
96  // But should be more than 1/2 of that expression because otherwise if the hashes
97  // spread out perfectly, it will fit in num_tables / 2 and not give us the fanout
98  // we expect. (A test will fail in this case so that we know.)
99  int build_tuples = (StandardHashTable::NODES * num_tables / 10) * 8;
100  prev = NextTestCase<buffer_size>(build_tuples, prev, num_tables);
101  }
102  }
103 };
104 
105 }
106 
107 int main(int argc, char** argv) {
108  google::InitGoogleLogging(argv[0]);
109  ::testing::InitGoogleTest(&argc, argv);
110  CpuInfo::Init();
111  LOG(ERROR) << "Testing with " << GrowingTest::NUM_PROBE_TUPLES << " tuples.";
112  return RUN_ALL_TESTS();
113 }
114 
116  // test without buffers
117  GrowingTest::Test<0>();
118  // test with one of the best buffer sizes
119  // Make it slightly more than a power of 2 cache lines so that they are offset.
120  GrowingTest::Test< (1 << 13) + 3 * 64 >();
121 }
static void Test()
Definition: growing-test.cc:79
static void SanityCheck(const HashStore< buffer_size > &hs, uint64_t num_probe_tuples, int max_id, int num_tables)
Definition: growing-test.cc:38
ProbeTuple tuples[BUFFER_TUPLES]
TEST(AtomicTest, Basic)
Definition: atomic-test.cc:28
#define BUFFER_SIZE
static std::string Print(bool value, TUnit::type ignored, bool verbose=false)
static uint64_t NextTestCase(int build_tuples, uint64_t prev, int num_tables)
Definition: growing-test.cc:59
static const uint64_t NUM_PROBE_TUPLES
Definition: growing-test.cc:20
uint64_t Test(T *ht, const ProbeTuple *input, uint64_t num_tuples)
int main(int argc, char **argv)
static void Init()
Initialize CpuInfo.
Definition: cpu-info.cc:75
static uint64_t AggregateTest(uint64_t num_probe_tuples, int max_id, int num_tables)
Definition: growing-test.cc:22