// { dg-do run { target c++11 } }

// Copyright (C) 2011-2017 Free Software Foundation, Inc.
//
// This file is part of the GNU ISO C++ Library.  This library is free
// software; you can redistribute it and/or modify it under the
// terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3, or (at your option)
// any later version.

// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License along
// with this library; see the file COPYING3.  If not see
// <http://www.gnu.org/licenses/>.

#include <sstream>
#include <tr1/unordered_set>
#include <unordered_set>
#include <testsuite_performance.h>

namespace
{
  // Bench using an unordered_set<int>. Hash functor for int is quite
  // predictable so it helps bench very specific use cases.
  template<typename _ContType>
    void bench(const char* desc)
    {
      using namespace __gnu_test;

      time_counter time;
      resource_counter resource;

      const int nb = 200000;
      start_counters(time, resource);

      _ContType us;
      for (int i = 0; i != nb; ++i)
	  us.insert(i);

      stop_counters(time, resource);
      std::ostringstream ostr;
      ostr << desc << ": first insert";
      report_performance(__FILE__, ostr.str().c_str(), time, resource);

      start_counters(time, resource);

      // Here is the worst erase use case when hashtable implementation was
      // something like vector<forward_list<>>. Erasing from the end was very
      // costly because we need to return the iterator following the erased
      // one, as the hashtable is getting emptier at each step there are
      // more and more empty bucket to loop through to reach the end of the
      // container and find out that it was in fact the last element.
      for (int j = nb - 1; j >= 0; --j)
	{
	  auto it = us.find(j);
	  if (it != us.end())
	    us.erase(it);
	}

      stop_counters(time, resource);
      ostr.str("");
      ostr << desc << ": erase from iterator";
      report_performance(__FILE__, ostr.str().c_str(), time, resource);

      start_counters(time, resource);

      // This is a worst insertion use case for the current implementation as
      // we insert an element at the beginning of the hashtable and then we
      // insert starting at the end so that each time we need to seek up to the
      // first bucket to find the first non-empty one.
      us.insert(0);
      for (int i = nb - 1; i >= 0; --i)
	us.insert(i);

      stop_counters(time, resource);
      ostr.str("");
      ostr << desc << ": second insert";
      report_performance(__FILE__, ostr.str().c_str(), time, resource);

      start_counters(time, resource);

      for (int j = nb - 1; j >= 0; --j)
	us.erase(j);

      stop_counters(time, resource);
      ostr.str("");
      ostr << desc << ": erase from key";
      report_performance(__FILE__, ostr.str().c_str(), time, resource);
    }

  // Bench using unordered_set<string> that show how important it is to cache
  // hash code as computing string hash code is quite expensive compared to
  // computing it for int.
  template<typename _ContType>
    void bench_str(const char* desc)
    {
      using namespace __gnu_test;

      time_counter time;
      resource_counter resource;

      const int nb = 200000;
      // First generate once strings that are going to be used throughout the
      // bench:
      std::ostringstream ostr;
      std::vector<std::string> strs;
      strs.reserve(nb);
      for (int i = 0; i != nb; ++i)
      {
	ostr.str("");
	ostr << "string #" << i;
	strs.push_back(ostr.str());
      }

      start_counters(time, resource);

      _ContType us;
      for (int i = 0; i != nb; ++i)
	us.insert(strs[i]);

      stop_counters(time, resource);
      ostr.str("");
      ostr << desc << ": first insert";
      report_performance(__FILE__, ostr.str().c_str(), time, resource);

      start_counters(time, resource);

      for (int j = nb - 1; j >= 0; --j)
	{
	  auto it = us.find(strs[j]);
	  if (it != us.end())
	    us.erase(it);
	}

      stop_counters(time, resource);
      ostr.str("");
      ostr << desc << ": erase from iterator";
      report_performance(__FILE__, ostr.str().c_str(), time, resource);

      start_counters(time, resource);

      us.insert(strs[0]);
      for (int i = nb - 1; i >= 0; --i)
	us.insert(strs[i]);

      stop_counters(time, resource);
      ostr.str("");
      ostr << desc << ": second insert";
      report_performance(__FILE__, ostr.str().c_str(), time, resource);

      start_counters(time, resource);

      for (int j = nb - 1; j >= 0; --j)
	us.erase(strs[j]);

      stop_counters(time, resource);
      ostr.str("");
      ostr << desc << ": erase from key";
      report_performance(__FILE__, ostr.str().c_str(), time, resource);
    }
}

template<bool cache>
  using __uset =
	      std::__uset_hashtable<int, std::hash<int>, std::equal_to<int>,
				    std::allocator<int>,
				    std::__uset_traits<cache>>;

template<bool cache>
  using __tr1_uset =
	      std::tr1::__unordered_set<int, std::hash<int>, std::equal_to<int>,
					std::allocator<int>,
					cache>;

template<bool cache>
  using __uset2 =
	      std::_Hashtable<int, int, std::allocator<int>,
			      std::__detail::_Identity,
			      std::equal_to<int>, std::hash<int>,
			      std::__detail::_Mask_range_hashing,
			      std::__detail::_Default_ranged_hash,
			      std::__detail::_Power2_rehash_policy,
			      std::__uset_traits<cache>>;

template<bool cache>
  using __str_uset = 
	      std::__uset_hashtable<std::string, std::hash<std::string>,
				    std::equal_to<std::string>,
				    std::allocator<std::string>,
				    std::__uset_traits<cache>>;

template<bool cache>
  using __tr1_str_uset = 
	      std::tr1::__unordered_set<std::string, std::hash<std::string>,
					std::equal_to<std::string>,
					std::allocator<std::string>,
					cache>;

template<bool cache>
  using __str_uset2 =
	      std::_Hashtable<std::string, std::string, std::allocator<std::string>,
			      std::__detail::_Identity,
			      std::equal_to<std::string>, std::hash<std::string>,
			      std::__detail::_Mask_range_hashing,
			      std::__detail::_Default_ranged_hash,
			      std::__detail::_Power2_rehash_policy,
			      std::__uset_traits<cache>>;

int main()
{
  bench<__tr1_uset<false>>(
	"std::tr1::unordered_set<int> without hash code cached");
  bench<__tr1_uset<true>>(
	"std::tr1::unordered_set<int> with hash code cached");
  bench<__uset<false>>(
	"std::unordered_set<int> without hash code cached");
  bench<__uset<true>>(
	"std::unordered_set<int> with hash code cached");
  bench<std::unordered_set<int>>(
	"std::unordered_set<int> default cache");
  bench<__uset2<false>>(
	"std::unordered_set2<int> without hash code cached");
  bench<__uset2<true>>(
	"std::unordered_set2<int> with hash code cached");
  bench_str<__tr1_str_uset<false>>(
	"std::tr1::unordered_set<string> without hash code cached");
  bench_str<__tr1_str_uset<true>>(
	"std::tr1::unordered_set<string> with hash code cached");
  bench_str<__str_uset<false>>(
	"std::unordered_set<string> without hash code cached");
  bench_str<__str_uset<true>>(
	"std::unordered_set<string> with hash code cached");
  bench_str<std::unordered_set<std::string>>(
	"std::unordered_set<string> default cache");
  bench_str<__str_uset2<false>>(
	"std::unordered_set2<string> without hash code cached");
  bench_str<__str_uset2<true>>(
	"std::unordered_set2<string> with hash code cached");
  return 0;
}
