Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Slow performance with Custom Type #118

Closed
DanWillans opened this issue May 9, 2024 · 1 comment
Closed

Slow performance with Custom Type #118

DanWillans opened this issue May 9, 2024 · 1 comment

Comments

@DanWillans
Copy link

DanWillans commented May 9, 2024

Hi,

I'm testing ankerl::unordered_dense::map against std::unordered_map for my custom type. When I profile std::unordered_map against ankerl::unordered_dense::map it's faster, am I using it incorrectly? As you can see in the code below for the std::hash specialisation I'm just returning the uint64_t because I can guarantee they're unique but in the custom_hash_unique_object_representation I'm instantiating wyhash.

If I just use a uint64_t ankerl is faster as the documentation and benchmarking shows.

Example code struct and hash for both std and ankerl.

  struct dan{
    uint64_t id;
    auto operator==(dan const& other) const -> bool {
        return id == other.id;
    }
  };

  namespace std {
  template<> struct hash<dan>
  {
    uint64_t operator()(const dan& id) const noexcept { return id.id; }
  };
  }// namespace std

  struct custom_hash_unique_object_representation {
      using is_avalanching = void;

      [[nodiscard]] auto operator()(dan const& f) const noexcept -> uint64_t {
          static_assert(std::has_unique_object_representations_v<dan>);
          return ankerl::unordered_dense::detail::wyhash::hash(&f, sizeof(f));
      }
  };

// Alternatively, doing the same as std::hash means very slow insertion into the map.
  struct custom_hash_unique_object_representation {
      [[nodiscard]] auto operator()(dan const& f) const noexcept -> uint64_t {
          return f.id;
      }
  };

Example code of usage

  constexpr int entity_count = 1000000;
  ankerl::unordered_dense::map<dan, uint64_t, custom_hash_unique_object_representation> ankerl_map;
  std::unordered_map<dan, uint64_t> std_map;

  for(uint64_t i = 0; i < entity_count; ++i){
    ankerl_map[dan{i}] = i;
    std_map[dan{i}] = i;
  }

  Timer timer_5("ankerl_map_find");
  timer_5.ResetStart();
  for(uint64_t i = 0; i < entity_count; ++i){
    auto& hey = ankerl_map[dan{i}];
    hey = 1;
  }
  timer_5.CaptureTimePoint();
  timer_5.PrintAverageTime();

  Timer timer_6("std_map_find");
  timer_6.ResetStart();
  for(uint64_t i = 0; i < entity_count; ++i){
    auto& hey = std_map[dan{i}];
    hey = 1;
  }
  timer_6.CaptureTimePoint();
  timer_6.PrintAverageTime();
@martinus
Copy link
Owner

martinus commented Oct 5, 2024

Your benchmark is extremely simplistic, you could basically just use an std::vector instead. unordered maps are good at random stuff. Also, use this hash:

struct hash_a {
    using is_avalanching = void;

    [[nodiscard]] auto operator()(dan const& f) const noexcept -> uint64_t {
        return ankerl::unordered_dense::hash<uint64_t>{}(f.id);
    }
};

And use the same hash for both maps. Try a more real-live example than simply iterating a map that has inserted !

@martinus martinus closed this as completed Oct 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants