This is an archive of the discontinued LLVM Phabricator instance.

[mlir] Optimize usage of llvm::mapped_iterator
ClosedPublic

Authored by rriddle on Nov 9 2021, 1:15 PM.

Details

Summary

mapped_iterator is a useful abstraction for applying a
map function over an existing iterator, but our current
usage ends up allocating storage/making indirect calls
even with the map function is a known function, which
is horribly inefficient. This commit refactors the usage
of mapped_iterator to avoid this, and allows for directly
referencing the map function when dereferencing.

Fixes PR52319

Diff Detail

Event Timeline

rriddle created this revision.Nov 9 2021, 1:15 PM
rriddle requested review of this revision.Nov 9 2021, 1:15 PM
Herald added projects: Restricted Project, Restricted Project. · View Herald TranscriptNov 9 2021, 1:15 PM

Whoa, thank you for tackling this! The trick with getFunction() is interesting, I hadn't thought about that.

I'm curious: did you consider splitting this into a new static_mapped_iterator class instead of keeping the mapped_iterator design of having a stored function? The static_mapped_iterator could be implemented in roughly the same way, but instead of returning a closure, it could just invoke the "getFunction" member as a static member. This would eliminate the lambdas and the function_ref in ResultElementTypeIterator.

I'm not sure if that makes sense. I'm imagining that clients would look like this:

template <typename UseIteratorT, typename OperandType>
class ValueUserIterator final :  public llvm::static_mapped_iterator<...
public:

  /// Return the map function used by this iterator.
  static Operation *mapElement(OperandType &value) {
      return value.getOwner();
  }

This would reduce pressure on the optimizer (good for -O0 builds in particular) and force a cleaner model.

WDYT?

Whoa, thank you for tackling this! The trick with getFunction() is interesting, I hadn't thought about that.

I'm curious: did you consider splitting this into a new static_mapped_iterator class instead of keeping the mapped_iterator design of having a stored function? The static_mapped_iterator could be implemented in roughly the same way, but instead of returning a closure, it could just invoke the "getFunction" member as a static member. This would eliminate the lambdas and the function_ref in ResultElementTypeIterator.

Logically in mind I treated mapped_iterator_base as the base for all "map" iterators, and the current "mapped_iterator" is just a specialization of that which uses a stored callable. I opted against using static given that some of the iterators use state in the callable, e.g. the FloatElementIterator uses the float semantics field on the iterator, but maybe static still makes sense given that the callable itself is static?

I'm not sure if that makes sense. I'm imagining that clients would look like this:

template <typename UseIteratorT, typename OperandType>
class ValueUserIterator final :  public llvm::static_mapped_iterator<...
public:

  /// Return the map function used by this iterator.
  static Operation *mapElement(OperandType &value) {
      return value.getOwner();
  }

This would reduce pressure on the optimizer (good for -O0 builds in particular) and force a cleaner model.

WDYT?

Yeah, I considered that but had some downstream users that wanted to interact with the mapping function directly.
I'll try to rewrite those to do something a bit different, let me update this patch.

rriddle updated this revision to Diff 386243.Nov 10 2021, 10:42 AM

resolve comments and add tests

lattner accepted this revision.Nov 10 2021, 11:14 AM

This is great, thank you!

This revision is now accepted and ready to land.Nov 10 2021, 11:14 AM
This revision was landed with ongoing or failed builds.Nov 10 2021, 7:44 PM
This revision was automatically updated to reflect the committed changes.
dblaikie added inline comments.
llvm/include/llvm/ADT/STLExtras.h
310–312

Could this be implemented in mapped_iterator as an empty optimization if the mapping function happens to be stateless?

rriddle added inline comments.Nov 16 2021, 11:40 AM
llvm/include/llvm/ADT/STLExtras.h
310–312

I think we can make it work for lambda callable types that capture no state. For non-lambdas I'm not sure though, given that we don't have a reference to the callable, just its type.

dexonsmith added inline comments.Nov 16 2021, 3:01 PM
llvm/include/llvm/ADT/STLExtras.h
310–312

Maybe this (mapped_iterator_base) could be moved to a base class of mapped_iterator, where the latter adds the getFunction() API, implements mapElement(), and friends mapped_iterator_base to give it access. This would keep the two implementations in sync.

For the EBO, mapped_iterator could delegate getFunction() to a new type mapped_iterator_storage, which avoids storing the function when possible.

315

It'd be nice to avoid requiring ReferenceTy, using the same declval/decltype trick as above.

324

I think this can just be using BaseT = mapped_iterator_base;. Also, is it needed? (see other comment below)

llvm/unittests/ADT/MappedIteratorTest.cpp
54

Seems like this would be more clearly written as:

using BaseT = CustomMapIterator::mapped_iterator_base;

Why expose BaseT?

rriddle added inline comments.Nov 16 2021, 3:27 PM
llvm/include/llvm/ADT/STLExtras.h
310–312

Let me try to clean this up, thanks.

315

I'm not sure we can here, given that we don't have the exact function type, and I don't think we can resolve DerivedT::mapElement at this point? I could be wrong here.

llvm/unittests/ADT/MappedIteratorTest.cpp
54

This is inheriting the constructor of the base class.

Why expose BaseT?

I've gotten into a habit of using BaseT to avoid the need to have long constructor inheritance lines, though that probably isn't necessary here given that you've pointed out that we don't need the super long template params (thanks for the comment on the other git commit BTW, need to clean other uses of this in MLIR. I wasn't sure if that was allowed on all of the compilers we support).

dexonsmith added inline comments.Nov 16 2021, 5:59 PM
llvm/include/llvm/ADT/STLExtras.h
315

No, I think you're right.

llvm/unittests/ADT/MappedIteratorTest.cpp
54

This is inheriting the constructor of the base class.

Right, thanks, I misread it as exposing the typedef :).

Why expose BaseT?

I've gotten into a habit of using BaseT to avoid the need to have long constructor inheritance lines, though that probably isn't necessary here given that you've pointed out that we don't need the super long template params (thanks for the comment on the other git commit BTW, need to clean other uses of this in MLIR. I wasn't sure if that was allowed on all of the compilers we support).

Yup, figured that was the reason; I only realized we could do this myself when I saw it in iterator_adaptor_base recently (I was thinking of adding a BaseT...).

I didn't intend to be oblique; wondered if there was some generic algorithm relying on the BaseT typedef and it seemed like this test was ensuring it worked (due to misreading the inherited constructor).

(IMO we should skip these now where possible now that we know we can (the verbose thing is more clear and avoids the extra ad-hoc typedef), but not a big deal if you'd prefer to keep it for some reason.)

aganea added a subscriber: aganea.Nov 24 2022, 6:48 AM
aganea added inline comments.
mlir/include/mlir/IR/BuiltinAttributes.h
353

Hello! With a recent version of MSVC 2022 (using 17.4.1) I'm seeing a bunch of warnings, such as:

[2055/3926] Building CXX object tools\mlir\lib\IR\CMakeFiles\obj.MLIRIR.dir\BuiltinAttributes.cpp.obj
D:\git\llvm-project\mlir\include\mlir/IR/BuiltinAttributes.h(353): warning C4996: 'std::complex<llvm::APFloat>::complex': warning STL4037: The effect of instantiating the template std::complex for any type other than float, double, or long double is unspecified. You can define _SILENCE_NONFLOATING_COMPLEX_DEPRECATION_WARNING to suppress this warning.
D:\git\llvm-project\mlir\lib\IR\BuiltinAttributes.cpp(683): warning C4996: 'std::complex<llvm::APInt>::complex': warning STL4037: The effect of instantiating the template std::complex for any type other than float, double, or long double is unspecified. You can define _SILENCE_NONFLOATING_COMPLEX_DEPRECATION_WARNING to suppress this warning.
D:\git\llvm-project\stage1_msvc_debug\tools\mlir\include\mlir/IR/BuiltinAttributes.h.inc(529): warning C4996: 'std::complex<llvm::APInt>::complex': warning STL4037: The effect of instantiating the template std::complex for any type other than float, double, or long double is unspecified. You can define _SILENCE_NONFLOATING_COMPLEX_DEPRECATION_WARNING to suppress this warning.
D:\git\llvm-project\mlir\include\mlir/IR/BuiltinAttributes.h(985): note: see reference to function template instantiation 'std::complex<llvm::APInt> mlir::SparseElementsAttr::getZeroValue<T>(void) const' being compiled
        with
        [
            T=std::complex<llvm::APInt>
        ]
D:\git\llvm-project\stage1_msvc_debug\tools\mlir\include\mlir/IR/BuiltinAttributeInterfaces.h.inc(310): note: see reference to function template instantiation 'mlir::FailureOr<llvm::mapped_iterator<llvm::detail::SafeIntIterator<__int64,false>,std::function<T (ptrdiff_t)>,_Ret>> mlir::SparseElementsAttr::try_value_begin_impl<T>(mlir::detail::ElementsAttrTrait<ConcreteAttr>::OverloadToken<T>) const' being compiled
        with
        [
            T=std::complex<llvm::APInt>,
            _Ret=std::complex<llvm::APInt>,
            ConcreteAttr=mlir::SparseElementsAttr
        ]

I'm not sure what to do with this. It seems usage of std::complex with anything else than float, double or long double is UB: https://en.cppreference.com/w/cpp/numeric/complex