efficient / libcuckoo

A high-performance, concurrent hash table
Other
1.61k stars 275 forks source link

An exec_fn functionality suggestion #84

Closed rayrapetyan closed 6 years ago

rayrapetyan commented 7 years ago

It would be great to have some universal way of organizing transactions. Something like below:

 template <typename K, typename F>
    auto exec_fn(const K &key, F fn, mapped_type* foo=nullptr) -> decltype(fn(foo)) {
      const hash_value hv = hashed_key(key);
      const auto b = snapshot_and_lock_two<normal_mode>(hv);
      const table_position pos = cuckoo_find(key, hv.partial, b.i1, b.i2);
      if (pos.status == ok) {
        return fn(&buckets_[pos.index].mapped(pos.slot));
      } else {
        return fn(nullptr);
      }
    }

Notes: In case an element was not found, user's callback receives a nullptr.

Now user can use constructions like:

    MyResType res = exec_fn(123, [&](Payload *p) -> MyResType {
        if (p == nullptr) {
            return 999;
        }
        if (p->p1 == 222) {
            return 2;
        } else {
            ++p->p1;
            return 3;
        }
        return 777;
    });

The only thing user can't do - insert\delete new records from a handler. Probably we can pass a param containing pos index\slot so user can use del_from_bucket (need to make it public)) etc, but it's unsafe.

manugoyal commented 7 years ago

Thanks for the suggestion! Being able to return your own value from a function is certainly convenient, but I think most of the functionality here can be covered by update_fn. For your example, you could write something like:

MyResType res;
bool found = table.update_fn(123, [&res](Payload& p) {
  if (p.p1 == 222) {
    res = 2;
  } else {
    ++p.p1;
    res = 3;
  }
});
if (!found) {
  res = 999;
}

Additionally, the erase_fn and uprase_fn methods will let you delete records from a handler, which should cover your other use case.

Does this usage pattern cover the functionality you're looking for?

rayrapetyan commented 7 years ago

Honestly yes, but in case we find a way to delete\insert records from within a user handler, we can drop upsert\uprase functions completely.

manugoyal commented 6 years ago

I think a general-purpose user handle certainly has its advantages, generally allowing one method to perform all of the upsert/uprase/update/etc functionality in a single method. The major downside I felt is the need to define a custom handle type and have callers define all their lambdas with this custom type, which felt clunky to me as I re-wrote the tests and examples.

Given that most of these variants are really just syntactic sugar to the most-general uprase_fn and erase_fn methods, it doesn't seem like too much repetition to leave it as is, and it seems to be nicer from a usability perspective IMO.