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

Simplify the ReusableObjectHolder #36068

Merged
Merged
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
51 changes: 26 additions & 25 deletions FWCore/Utilities/interface/ReusableObjectHolder.h
Expand Up @@ -69,11 +69,11 @@
// Created: Fri, 31 July 2014 14:29:41 GMT
//

#include <memory>
#include <cassert>
#include <atomic>
#include "tbb/task.h"
#include "tbb/concurrent_queue.h"
#include <cassert>
#include <memory>

#include <tbb/concurrent_queue.h>

namespace edm {
template <class T, class Deleter = std::default_delete<T>>
Expand Down Expand Up @@ -108,43 +108,44 @@ namespace edm {
/// Use this function in conjunction with add()
std::shared_ptr<T> tryToGet() {
std::unique_ptr<T, Deleter> item;
m_availableQueue.try_pop(item);
if (nullptr == item) {
if (m_availableQueue.try_pop(item)) {
return wrapCustomDeleter(std::move(item));
} else {
return std::shared_ptr<T>{};
}
//instead of deleting, hand back to queue
auto pHolder = this;
auto deleter = item.get_deleter();
++m_outstandingObjects;
return std::shared_ptr<T>{item.release(), [pHolder, deleter](T* iItem) {
pHolder->addBack(std::unique_ptr<T, Deleter>{iItem, deleter});
}};
}

///If there isn't an object already available, creates a new one using iFunc
///Takes an object from the queue if one is available, or creates one using iFunc.
template <typename F>
std::shared_ptr<T> makeOrGet(F iFunc) {
std::shared_ptr<T> returnValue;
while (!(returnValue = tryToGet())) {
add(makeUnique(iFunc()));
std::unique_ptr<T, Deleter> item;
if (m_availableQueue.try_pop(item)) {
return wrapCustomDeleter(std::move(item));
} else {
return wrapCustomDeleter(makeUnique(iFunc()));
}
return returnValue;
}

///If there is an object already available, passes the object to iClearFunc and then
/// returns the object.
///If there is not an object already available, creates a new one using iMakeFunc
///Takes an object from the queue if one is available, or creates one using iMakeFunc.
///Then, passes the object to iClearFunc, and returns it.
template <typename FM, typename FC>
std::shared_ptr<T> makeOrGetAndClear(FM iMakeFunc, FC iClearFunc) {
std::shared_ptr<T> returnValue;
while (!(returnValue = tryToGet())) {
add(makeUnique(iMakeFunc()));
}
std::shared_ptr<T> returnValue = makeOrGet(iMakeFunc);
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe doing

Suggested change
std::shared_ptr<T> returnValue = makeOrGet(iMakeFunc);
std::shared_ptr<T> returnValue = makeOrGet(std::move(iMakeFunc));

would avoid a possible copy of the functor.

Possibly an even better solution would be to change the function signature to

makeOrGetAndClear(FM&& iMakeFunc, FC&& iClearFunc)

and then do

Suggested change
std::shared_ptr<T> returnValue = makeOrGet(iMakeFunc);
std::shared_ptr<T> returnValue = makeOrGet(std::forward<FM>(iMakeFunc));

A similar function signature change to makeOrGet would probably be required to get the most benefit of such a change.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch, thanks.

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 think this change

    template <typename FM, typename FC>
    std::shared_ptr<T> makeOrGetAndClear(FM&& iMakeFunc, FC iClearFunc) {
      std::shared_ptr<T> returnValue = makeOrGet(std::forward<FM>(iMakeFunc));
      iClearFunc(returnValue.get());
      return returnValue;
    }

should be enough, since iMakeFunc is the only one that is copied.

Copy link
Contributor Author

@fwyzard fwyzard Nov 11, 2021

Choose a reason for hiding this comment

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

At least, my confused understanding is that FC iClearFunc (in makeOrGetAndClear) and F iFunc (in makeOrGet) should already take any of the functors by reference if they are lvalues.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think using FC instead of FC&& means the compiler (before optimization) will call the move constructor of FC when passed an lvalue. I'd bet that all compilers will remove that call when optimation is turned on. However, using FC&& does make the requirements of the arguments more explicit.

Copy link
Contributor

Choose a reason for hiding this comment

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

I was wrong. I tested in godbolt and for the case where the object is created directly in the argument list they are identical.

Copy link
Contributor

Choose a reason for hiding this comment

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

It does appear that if the argument is created outside of the function argument list, then FC&& is the better choice as it does avoid an extra copy.

https://godbolt.org/z/7fYG641qq

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK, done - let me know if I missed anything (here or elsewhere).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, I missed the last comment...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

...done, now.

iClearFunc(returnValue.get());
return returnValue;
}

private:
///Wraps an object in a shared_ptr<T> with a custom deleter, that hands the wrapped object
// back to the queue instead of deleting it
std::shared_ptr<T> wrapCustomDeleter(std::unique_ptr<T, Deleter> item) {
auto deleter = item.get_deleter();
++m_outstandingObjects;
return std::shared_ptr<T>{item.release(), [this, deleter](T* iItem) {
this->addBack(std::unique_ptr<T, Deleter>{iItem, deleter});
}};
}

std::unique_ptr<T> makeUnique(T* ptr) {
static_assert(std::is_same_v<Deleter, std::default_delete<T>>,
"Generating functions returning raw pointers are supported only with std::default_delete<T>");
Expand Down