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