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

DM-13854: add general-purpose Cache class #50

Merged
merged 3 commits into from Mar 27, 2018
Merged

Conversation

PaulPrice
Copy link
Contributor

No description provided.

Copy link
Member

@TallJimbo TallJimbo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. I've got a few miscellaneous comments.

In the class documentation I've asked you to write, please also note that:

  • Value and Key must be Copyable.
  • This header should generally only be included in source files, not other headers, because we really don't want all of that multi_index_container stuff being included in source files where it isn't needed. Since this is a class that will be frequently used as a data member, that in turn means that it should usually be held via unique_ptr so it can be forward-declared in the header file.

And to make that last bit of advice easier for users to follow, it might be good to make a CacheFwd.h header file that does that forward-declaration for them. Note that the forward declaration needs to include the template parameter defaults, and then you'll need to remove them from Cache.h and have it include CacheFwd.h.

namespace utils {

/// Cache of most recently used values
template <typename Key, typename Value, typename KeyHash=boost::hash<Key>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C++11 has a std::hash that should be preferred.

#include <boost/multi_index/composite_key.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/format.hpp>
#include <boost/functional/hash.hpp> // boost::hash
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LSST convention is to use "" instead of <> for all but system headers.

namespace lsst {
namespace utils {

/// Cache of most recently used values
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would like to see a bit more in the way of class docs than this.

I believe we've also standardized on using /** ... */ for all but single-line docstrings (https://developer.lsst.io/cpp/api-docs.html#documentation-must-be-delimited-in-javadoc-style).

/// Dtor
#ifdef LSST_CACHE_DEBUG
///
/// Saves the cache requests to file.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes me a bit uncomfortable; having destructors that could possibly throw (and anything that writes could definitely throw) causes headaches. But I suppose if it's only debug mode it's probably okay.

/// possible the user has some function that produces a value
/// when given a key, so chose to adopt that signature; any other
/// signature would likely require use of a lambda always.
Value operator()(Key const& key, std::function<Value(Key const&)>);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While it provides a nice way to document and enforce the function signature you're expecting, std::function comes with virtual function call overheads, so I usually wouldn't use it unless I need its polymorphism. Might be best to just take a template parameter here, since the implementation has to go in the header anyway. But that said, I suppose we don't cache something unless calculating it is slow enough that virtual function overheads shouldn't matter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went with std::function because pybind wraps it without any effort on my part (yay, calling python from C++!). I changed the C++ to template the function type for pure speed, and converted the pybind wrapper to use std::function so that's still handled simply.

/// Lookup a value
///
/// The key must be in the cache already, or an
/// lsst::pex::exceptions::NotFoundError will be raised.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See DevGuide for how to document possible exceptions in Doxygen (and on that note, it would be good to document the exception safety of all of these methods).

}

std::size_t _maxElements; // Maximum number of elements; 0 means infinite
mutable Container _container; // Container of key,value pairs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it might be cleaner to make Container non-mutable, even if that requires more classes that use it to make their Cache data members mutable. I imagine most of those will be calling operator() rather than operator[] anyway, so the constness of operator[] doesn't gain them much.

Value operator[](Key const& key) const;

/// Add a value to the cache
void add(Key const& key, Value const& value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should document behavior if the key is already in the cache.

return numberToWords(hundreds) + " hundred" + ((" " + numberToWords(rest)) if rest > 0 else "")


class CacheTestCase(lsst.utils.tests.TestCase):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

newline here.

void flush();

#ifdef LSST_CACHE_DEBUG
void enableDebugging() { debuggingEnabled = true; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this need an underscore?

_container.template project<Sequence>(it));
}
#ifdef LSST_CACHE_DEBUG
if (debuggingEnabled) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here...

std::size_t _maxElements; // Maximum number of elements; 0 means infinite
mutable Container _container; // Container of key,value pairs
#ifdef LSST_CACHE_DEBUG
bool debuggingEnabled;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here.

@PaulPrice PaulPrice force-pushed the tickets/DM-13854 branch 5 times, most recently from ae36ebb to 4deba98 Compare March 24, 2018 04:11
Copy link
Member

@TallJimbo TallJimbo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, thanks for the improvements!

@PaulPrice PaulPrice force-pushed the tickets/DM-13854 branch 4 times, most recently from a15fedf to 2f06bc9 Compare March 27, 2018 14:49
Cache of most recently used objects, hashed by some key, with user-defined
capacity limit.
Allows us to characterise the cache performance.
C++11 added std::hash, but neglected to include a function for
combining multiple hashes.
@PaulPrice PaulPrice merged commit e68ac28 into master Mar 27, 2018
@ktlim ktlim deleted the tickets/DM-13854 branch August 25, 2018 05:57
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

Successfully merging this pull request may close these issues.

None yet

3 participants