387 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			387 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
 | 
						||
#include <algorithm>
 | 
						||
#include <cstdint>
 | 
						||
#include <map>
 | 
						||
#include <random>
 | 
						||
#include <string>
 | 
						||
#include <utility>
 | 
						||
#include <vector>
 | 
						||
 | 
						||
#include "CartesianBenchmarks.h"
 | 
						||
#include "GenerateInput.h"
 | 
						||
#include "benchmark/benchmark.h"
 | 
						||
#include "test_macros.h"
 | 
						||
 | 
						||
namespace {
 | 
						||
 | 
						||
enum class ValueType { Uint32, Uint64, Pair, Tuple, String };
 | 
						||
struct AllValueTypes : EnumValuesAsTuple<AllValueTypes, ValueType, 5> {
 | 
						||
  static constexpr const char* Names[] = {
 | 
						||
      "uint32", "uint64", "pair<uint32, uint32>",
 | 
						||
      "tuple<uint32, uint64, uint32>", "string"};
 | 
						||
};
 | 
						||
 | 
						||
template <class V>
 | 
						||
using Value = std::conditional_t<
 | 
						||
    V() == ValueType::Uint32, uint32_t,
 | 
						||
    std::conditional_t<
 | 
						||
        V() == ValueType::Uint64, uint64_t,
 | 
						||
        std::conditional_t<
 | 
						||
            V() == ValueType::Pair, std::pair<uint32_t, uint32_t>,
 | 
						||
            std::conditional_t<V() == ValueType::Tuple,
 | 
						||
                               std::tuple<uint32_t, uint64_t, uint32_t>,
 | 
						||
                               std::string> > > >;
 | 
						||
 | 
						||
enum class Order {
 | 
						||
  Random,
 | 
						||
  Ascending,
 | 
						||
  Descending,
 | 
						||
  SingleElement,
 | 
						||
  PipeOrgan,
 | 
						||
  Heap,
 | 
						||
  QuickSortAdversary,
 | 
						||
};
 | 
						||
struct AllOrders : EnumValuesAsTuple<AllOrders, Order, 7> {
 | 
						||
  static constexpr const char* Names[] = {"Random",     "Ascending",
 | 
						||
                                          "Descending", "SingleElement",
 | 
						||
                                          "PipeOrgan",  "Heap",
 | 
						||
                                          "QuickSortAdversary"};
 | 
						||
};
 | 
						||
 | 
						||
// fillAdversarialQuickSortInput fills the input vector with N int-like values.
 | 
						||
// These values are arranged in such a way that they would invoke O(N^2)
 | 
						||
// behavior on any quick sort implementation that satisifies certain conditions.
 | 
						||
// Details are available in the following paper:
 | 
						||
// "A Killer Adversary for Quicksort", M. D. McIlroy, Software—Practice &
 | 
						||
// ExperienceVolume 29 Issue 4 April 10, 1999 pp 341–344.
 | 
						||
// https://dl.acm.org/doi/10.5555/311868.311871.
 | 
						||
template <class T>
 | 
						||
void fillAdversarialQuickSortInput(T& V, size_t N) {
 | 
						||
  assert(N > 0);
 | 
						||
  // If an element is equal to gas, it indicates that the value of the element
 | 
						||
  // is still to be decided and may change over the course of time.
 | 
						||
  const int gas = N - 1;
 | 
						||
  V.resize(N);
 | 
						||
  for (int i = 0; i < N; ++i) {
 | 
						||
    V[i] = gas;
 | 
						||
  }
 | 
						||
  // Candidate for the pivot position.
 | 
						||
  int candidate = 0;
 | 
						||
  int nsolid = 0;
 | 
						||
  // Populate all positions in the generated input to gas.
 | 
						||
  std::vector<int> ascVals(V.size());
 | 
						||
  // Fill up with ascending values from 0 to V.size()-1.  These will act as
 | 
						||
  // indices into V.
 | 
						||
  std::iota(ascVals.begin(), ascVals.end(), 0);
 | 
						||
  std::sort(ascVals.begin(), ascVals.end(), [&](int x, int y) {
 | 
						||
    if (V[x] == gas && V[y] == gas) {
 | 
						||
      // We are comparing two inputs whose value is still to be decided.
 | 
						||
      if (x == candidate) {
 | 
						||
        V[x] = nsolid++;
 | 
						||
      } else {
 | 
						||
        V[y] = nsolid++;
 | 
						||
      }
 | 
						||
    }
 | 
						||
    if (V[x] == gas) {
 | 
						||
      candidate = x;
 | 
						||
    } else if (V[y] == gas) {
 | 
						||
      candidate = y;
 | 
						||
    }
 | 
						||
    return V[x] < V[y];
 | 
						||
  });
 | 
						||
}
 | 
						||
 | 
						||
template <typename T>
 | 
						||
void fillValues(std::vector<T>& V, size_t N, Order O) {
 | 
						||
  if (O == Order::SingleElement) {
 | 
						||
    V.resize(N, 0);
 | 
						||
  } else if (O == Order::QuickSortAdversary) {
 | 
						||
    fillAdversarialQuickSortInput(V, N);
 | 
						||
  } else {
 | 
						||
    while (V.size() < N)
 | 
						||
      V.push_back(V.size());
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
template <typename T>
 | 
						||
void fillValues(std::vector<std::pair<T, T> >& V, size_t N, Order O) {
 | 
						||
  if (O == Order::SingleElement) {
 | 
						||
    V.resize(N, std::make_pair(0, 0));
 | 
						||
  } else {
 | 
						||
    while (V.size() < N)
 | 
						||
      // Half of array will have the same first element.
 | 
						||
      if (V.size() % 2) {
 | 
						||
        V.push_back(std::make_pair(V.size(), V.size()));
 | 
						||
      } else {
 | 
						||
        V.push_back(std::make_pair(0, V.size()));
 | 
						||
      }
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
template <typename T1, typename T2, typename T3>
 | 
						||
void fillValues(std::vector<std::tuple<T1, T2, T3> >& V, size_t N, Order O) {
 | 
						||
  if (O == Order::SingleElement) {
 | 
						||
    V.resize(N, std::make_tuple(0, 0, 0));
 | 
						||
  } else {
 | 
						||
    while (V.size() < N)
 | 
						||
      // One third of array will have the same first element.
 | 
						||
      // One third of array will have the same first element and the same second element.
 | 
						||
      switch (V.size() % 3) {
 | 
						||
      case 0:
 | 
						||
        V.push_back(std::make_tuple(V.size(), V.size(), V.size()));
 | 
						||
        break;
 | 
						||
      case 1:
 | 
						||
        V.push_back(std::make_tuple(0, V.size(), V.size()));
 | 
						||
        break;
 | 
						||
      case 2:
 | 
						||
        V.push_back(std::make_tuple(0, 0, V.size()));
 | 
						||
        break;
 | 
						||
      }
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
void fillValues(std::vector<std::string>& V, size_t N, Order O) {
 | 
						||
  if (O == Order::SingleElement) {
 | 
						||
    V.resize(N, getRandomString(64));
 | 
						||
  } else {
 | 
						||
    while (V.size() < N)
 | 
						||
      V.push_back(getRandomString(64));
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
template <class T>
 | 
						||
void sortValues(T& V, Order O) {
 | 
						||
  switch (O) {
 | 
						||
  case Order::Random: {
 | 
						||
    std::random_device R;
 | 
						||
    std::mt19937 M(R());
 | 
						||
    std::shuffle(V.begin(), V.end(), M);
 | 
						||
    break;
 | 
						||
  }
 | 
						||
  case Order::Ascending:
 | 
						||
    std::sort(V.begin(), V.end());
 | 
						||
    break;
 | 
						||
  case Order::Descending:
 | 
						||
    std::sort(V.begin(), V.end(), std::greater<>());
 | 
						||
    break;
 | 
						||
  case Order::SingleElement:
 | 
						||
    // Nothing to do
 | 
						||
    break;
 | 
						||
  case Order::PipeOrgan:
 | 
						||
    std::sort(V.begin(), V.end());
 | 
						||
    std::reverse(V.begin() + V.size() / 2, V.end());
 | 
						||
    break;
 | 
						||
  case Order::Heap:
 | 
						||
    std::make_heap(V.begin(), V.end());
 | 
						||
    break;
 | 
						||
  case Order::QuickSortAdversary:
 | 
						||
    // Nothing to do
 | 
						||
    break;
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
constexpr size_t TestSetElements =
 | 
						||
#if !TEST_HAS_FEATURE(memory_sanitizer)
 | 
						||
    1 << 18;
 | 
						||
#else
 | 
						||
    1 << 14;
 | 
						||
#endif
 | 
						||
 | 
						||
template <class ValueType>
 | 
						||
std::vector<std::vector<Value<ValueType> > > makeOrderedValues(size_t N,
 | 
						||
                                                               Order O) {
 | 
						||
  std::vector<std::vector<Value<ValueType> > > Ret;
 | 
						||
  const size_t NumCopies = std::max(size_t{1}, TestSetElements / N);
 | 
						||
  Ret.resize(NumCopies);
 | 
						||
  for (auto& V : Ret) {
 | 
						||
    fillValues(V, N, O);
 | 
						||
    sortValues(V, O);
 | 
						||
  }
 | 
						||
  return Ret;
 | 
						||
}
 | 
						||
 | 
						||
template <class T, class U>
 | 
						||
TEST_ALWAYS_INLINE void resetCopies(benchmark::State& state, T& Copies,
 | 
						||
                                    U& Orig) {
 | 
						||
  state.PauseTiming();
 | 
						||
  for (auto& Copy : Copies)
 | 
						||
    Copy = Orig;
 | 
						||
  state.ResumeTiming();
 | 
						||
}
 | 
						||
 | 
						||
enum class BatchSize {
 | 
						||
  CountElements,
 | 
						||
  CountBatch,
 | 
						||
};
 | 
						||
 | 
						||
template <class ValueType, class F>
 | 
						||
void runOpOnCopies(benchmark::State& state, size_t Quantity, Order O,
 | 
						||
                   BatchSize Count, F Body) {
 | 
						||
  auto Copies = makeOrderedValues<ValueType>(Quantity, O);
 | 
						||
  auto Orig = Copies;
 | 
						||
 | 
						||
  const size_t Batch = Count == BatchSize::CountElements
 | 
						||
                           ? Copies.size() * Quantity
 | 
						||
                           : Copies.size();
 | 
						||
  while (state.KeepRunningBatch(Batch)) {
 | 
						||
    for (auto& Copy : Copies) {
 | 
						||
      Body(Copy);
 | 
						||
      benchmark::DoNotOptimize(Copy);
 | 
						||
    }
 | 
						||
    state.PauseTiming();
 | 
						||
    Copies = Orig;
 | 
						||
    state.ResumeTiming();
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
template <class ValueType, class Order>
 | 
						||
struct Sort {
 | 
						||
  size_t Quantity;
 | 
						||
 | 
						||
  void run(benchmark::State& state) const {
 | 
						||
    runOpOnCopies<ValueType>(
 | 
						||
        state, Quantity, Order(), BatchSize::CountElements,
 | 
						||
        [](auto& Copy) { std::sort(Copy.begin(), Copy.end()); });
 | 
						||
  }
 | 
						||
 | 
						||
  bool skip() const { return Order() == ::Order::Heap; }
 | 
						||
 | 
						||
  std::string name() const {
 | 
						||
    return "BM_Sort" + ValueType::name() + Order::name() + "_" +
 | 
						||
           std::to_string(Quantity);
 | 
						||
  };
 | 
						||
};
 | 
						||
 | 
						||
template <class ValueType, class Order>
 | 
						||
struct StableSort {
 | 
						||
  size_t Quantity;
 | 
						||
 | 
						||
  void run(benchmark::State& state) const {
 | 
						||
    runOpOnCopies<ValueType>(
 | 
						||
        state, Quantity, Order(), BatchSize::CountElements,
 | 
						||
        [](auto& Copy) { std::stable_sort(Copy.begin(), Copy.end()); });
 | 
						||
  }
 | 
						||
 | 
						||
  bool skip() const { return Order() == ::Order::Heap; }
 | 
						||
 | 
						||
  std::string name() const {
 | 
						||
    return "BM_StableSort" + ValueType::name() + Order::name() + "_" +
 | 
						||
           std::to_string(Quantity);
 | 
						||
  };
 | 
						||
};
 | 
						||
 | 
						||
template <class ValueType, class Order>
 | 
						||
struct MakeHeap {
 | 
						||
  size_t Quantity;
 | 
						||
 | 
						||
  void run(benchmark::State& state) const {
 | 
						||
    runOpOnCopies<ValueType>(
 | 
						||
        state, Quantity, Order(), BatchSize::CountElements,
 | 
						||
        [](auto& Copy) { std::make_heap(Copy.begin(), Copy.end()); });
 | 
						||
  }
 | 
						||
 | 
						||
  std::string name() const {
 | 
						||
    return "BM_MakeHeap" + ValueType::name() + Order::name() + "_" +
 | 
						||
           std::to_string(Quantity);
 | 
						||
  };
 | 
						||
};
 | 
						||
 | 
						||
template <class ValueType>
 | 
						||
struct SortHeap {
 | 
						||
  size_t Quantity;
 | 
						||
 | 
						||
  void run(benchmark::State& state) const {
 | 
						||
    runOpOnCopies<ValueType>(
 | 
						||
        state, Quantity, Order::Heap, BatchSize::CountElements,
 | 
						||
        [](auto& Copy) { std::sort_heap(Copy.begin(), Copy.end()); });
 | 
						||
  }
 | 
						||
 | 
						||
  std::string name() const {
 | 
						||
    return "BM_SortHeap" + ValueType::name() + "_" + std::to_string(Quantity);
 | 
						||
  };
 | 
						||
};
 | 
						||
 | 
						||
template <class ValueType, class Order>
 | 
						||
struct MakeThenSortHeap {
 | 
						||
  size_t Quantity;
 | 
						||
 | 
						||
  void run(benchmark::State& state) const {
 | 
						||
    runOpOnCopies<ValueType>(state, Quantity, Order(), BatchSize::CountElements,
 | 
						||
                             [](auto& Copy) {
 | 
						||
                               std::make_heap(Copy.begin(), Copy.end());
 | 
						||
                               std::sort_heap(Copy.begin(), Copy.end());
 | 
						||
                             });
 | 
						||
  }
 | 
						||
 | 
						||
  std::string name() const {
 | 
						||
    return "BM_MakeThenSortHeap" + ValueType::name() + Order::name() + "_" +
 | 
						||
           std::to_string(Quantity);
 | 
						||
  };
 | 
						||
};
 | 
						||
 | 
						||
template <class ValueType, class Order>
 | 
						||
struct PushHeap {
 | 
						||
  size_t Quantity;
 | 
						||
 | 
						||
  void run(benchmark::State& state) const {
 | 
						||
    runOpOnCopies<ValueType>(
 | 
						||
        state, Quantity, Order(), BatchSize::CountElements, [](auto& Copy) {
 | 
						||
          for (auto I = Copy.begin(), E = Copy.end(); I != E; ++I) {
 | 
						||
            std::push_heap(Copy.begin(), I + 1);
 | 
						||
          }
 | 
						||
        });
 | 
						||
  }
 | 
						||
 | 
						||
  bool skip() const { return Order() == ::Order::Heap; }
 | 
						||
 | 
						||
  std::string name() const {
 | 
						||
    return "BM_PushHeap" + ValueType::name() + Order::name() + "_" +
 | 
						||
           std::to_string(Quantity);
 | 
						||
  };
 | 
						||
};
 | 
						||
 | 
						||
template <class ValueType>
 | 
						||
struct PopHeap {
 | 
						||
  size_t Quantity;
 | 
						||
 | 
						||
  void run(benchmark::State& state) const {
 | 
						||
    runOpOnCopies<ValueType>(
 | 
						||
        state, Quantity, Order(), BatchSize::CountElements, [](auto& Copy) {
 | 
						||
          for (auto B = Copy.begin(), I = Copy.end(); I != B; --I) {
 | 
						||
            std::pop_heap(B, I);
 | 
						||
          }
 | 
						||
        });
 | 
						||
  }
 | 
						||
 | 
						||
  std::string name() const {
 | 
						||
    return "BM_PopHeap" + ValueType::name() + "_" + std::to_string(Quantity);
 | 
						||
  };
 | 
						||
};
 | 
						||
 | 
						||
} // namespace
 | 
						||
 | 
						||
int main(int argc, char** argv) {
 | 
						||
  benchmark::Initialize(&argc, argv);
 | 
						||
  if (benchmark::ReportUnrecognizedArguments(argc, argv))
 | 
						||
    return 1;
 | 
						||
 | 
						||
  const std::vector<size_t> Quantities = {1 << 0, 1 << 2,  1 << 4,  1 << 6,
 | 
						||
                                          1 << 8, 1 << 10, 1 << 14,
 | 
						||
    // Running each benchmark in parallel consumes too much memory with MSAN
 | 
						||
    // and can lead to the test process being killed.
 | 
						||
#if !TEST_HAS_FEATURE(memory_sanitizer)
 | 
						||
                                          1 << 18
 | 
						||
#endif
 | 
						||
  };
 | 
						||
  makeCartesianProductBenchmark<Sort, AllValueTypes, AllOrders>(Quantities);
 | 
						||
  makeCartesianProductBenchmark<StableSort, AllValueTypes, AllOrders>(
 | 
						||
      Quantities);
 | 
						||
  makeCartesianProductBenchmark<MakeHeap, AllValueTypes, AllOrders>(Quantities);
 | 
						||
  makeCartesianProductBenchmark<SortHeap, AllValueTypes>(Quantities);
 | 
						||
  makeCartesianProductBenchmark<MakeThenSortHeap, AllValueTypes, AllOrders>(
 | 
						||
      Quantities);
 | 
						||
  makeCartesianProductBenchmark<PushHeap, AllValueTypes, AllOrders>(Quantities);
 | 
						||
  makeCartesianProductBenchmark<PopHeap, AllValueTypes>(Quantities);
 | 
						||
  benchmark::RunSpecifiedBenchmarks();
 | 
						||
}
 |