nicovank created this revision. Herald added subscribers: carlosgalvezp, xazax.hun, mgorny. Herald added a project: All. nicovank edited the summary of this revision. nicovank edited the summary of this revision. nicovank updated this revision to Diff 452886. nicovank updated this revision to Diff 452894. nicovank added a comment. nicovank updated this revision to Diff 452896. Eugene.Zelenko added reviewers: alex, aaron.ballman, njames93, LegalizeAdulthood. Eugene.Zelenko added a project: clang-tools-extra. nicovank updated this revision to Diff 453026. nicovank marked an inline comment as done. nicovank published this revision for review. nicovank added subscribers: marksantaniello, ivanmurashko, 0x1eaf. nicovank marked an inline comment as done. Herald added a subscriber: cfe-commits.
Rename second test file nicovank added a comment. Add isLanguageVersionSupported. ================ Comment at: clang-tools-extra/clang-tidy/performance/ExpensiveFlatContainerOperationCheck.h:31 + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +private: ---------------- Please add `isLanguageVersionSupported`. ================ Comment at: clang-tools-extra/docs/clang-tidy/checks/performance/expensive-flat-container-operation.rst:6 + +This check operates on vector-based containers such as +``boost::container::flat_(map|set)`` and ``folly::sorted_vector_(map|set)``. ---------------- Please synchronize first statement with Release Notes. This check has been enabled internally at Facebook for a few months now (with `OnlyWarnInLoops` disabled), where `folly::sorted_vector_map` is pretty widely used. I saw `std::flat_(map|set)` and `std::flat_multi(map|set)` will appear in C++23, at which point this check can be updated to include these. `folly::heap_vector_(map|set)` is another example of such containers, though still relatively new so not included here yet. Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D131939 Files: clang-tools-extra/clang-tidy/performance/CMakeLists.txt clang-tools-extra/clang-tidy/performance/ExpensiveFlatContainerOperationCheck.cpp clang-tools-extra/clang-tidy/performance/ExpensiveFlatContainerOperationCheck.h clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp clang-tools-extra/docs/ReleaseNotes.rst clang-tools-extra/docs/clang-tidy/checks/list.rst clang-tools-extra/docs/clang-tidy/checks/performance/expensive-flat-container-operation.rst clang-tools-extra/test/clang-tidy/checkers/performance/expensive-flat-container-operation-only-warn-in-loops.cpp clang-tools-extra/test/clang-tidy/checkers/performance/expensive-flat-container-operation.cpp
Index: clang-tools-extra/test/clang-tidy/checkers/performance/expensive-flat-container-operation.cpp =================================================================== --- /dev/null +++ clang-tools-extra/test/clang-tidy/checkers/performance/expensive-flat-container-operation.cpp @@ -0,0 +1,383 @@ +// RUN: %check_clang_tidy %s performance-expensive-flat-container-operation %t -- \ +// RUN: -config="{CheckOptions: \ +// RUN: [{key: performance-expensive-flat-container-operation.OnlyWarnInLoops, \ +// RUN: value: false}] \ +// RUN: }" + +namespace std { +template <class T1, class T2> +struct pair { + pair(T1, T2); +}; + +template<class T> +struct initializer_list { + initializer_list(); +}; + +template<class T> struct remove_reference { typedef T type; }; +template<class T> struct remove_reference<T&> { typedef T type; }; +template<class T> struct remove_reference<T&&> { typedef T type; }; + +template<class T> +typename std::remove_reference<T>::type&& move(T&&) noexcept; +} // namespace std + +// Copied from boost/container/flat_map.hpp and boost/container/flat_set.hpp and simplified. +namespace boost { +namespace container { +struct ordered_unique_range_t {}; + +template <typename Key, typename T> +struct flat_map { + typedef Key key_type; + typedef T mapped_type; + typedef std::pair<Key, T> value_type; + + typedef int size_type; + typedef struct {} iterator; + typedef struct {} const_iterator; + + const_iterator begin() const noexcept; + const_iterator end() const noexcept; + + template <class... Args> std::pair<iterator, bool> emplace(Args&&...); + + template <class... Args> iterator emplace_hint(const_iterator, Args&&...); + + template <class... Args> std::pair<iterator, bool> try_emplace(const key_type&, Args&&...); + template <class... Args> iterator try_emplace(const_iterator, const key_type&, Args&&...); + template <class... Args> std::pair<iterator, bool> try_emplace(key_type&&, Args&&...); + template <class... Args> iterator try_emplace(const_iterator, key_type&&, Args&&...); + + std::pair<iterator, bool> insert(const value_type&); + std::pair<iterator, bool> insert(value_type&&); + iterator insert(const_iterator, const value_type&); + iterator insert(const_iterator, value_type&&); + template <typename InputIterator> void insert(InputIterator, InputIterator); + template <typename InputIterator> void insert(ordered_unique_range_t, InputIterator, InputIterator); + void insert(std::initializer_list<value_type>); + void insert(ordered_unique_range_t, std::initializer_list<value_type>); + + iterator erase(const_iterator); + size_type erase(const key_type&); + iterator erase(const_iterator, const_iterator); +}; + +template <typename Key> +struct flat_set { + typedef Key key_type; + typedef Key value_type; + + typedef int size_type; + typedef struct {} iterator; + typedef struct {} const_iterator; + + const_iterator begin() const noexcept; + const_iterator end() const noexcept; + + template <class... Args> std::pair<iterator, bool> emplace(Args&&...); + + template <class... Args> iterator emplace_hint(const_iterator, Args&&...); + + std::pair<iterator, bool> insert(const value_type&); + std::pair<iterator, bool> insert(value_type&&); + iterator insert(const_iterator, const value_type&); + iterator insert(const_iterator, value_type&&); + template <typename InputIterator> void insert(InputIterator, InputIterator); + template <typename InputIterator> void insert(ordered_unique_range_t, InputIterator, InputIterator); + void insert(std::initializer_list<value_type>); + void insert(ordered_unique_range_t, std::initializer_list<value_type>); + + iterator erase(const_iterator); + size_type erase(const key_type&); + iterator erase(const_iterator, const_iterator); +}; +} // namespace container +} // namespace boost + +// Copied from folly/sorted_vector_types.h and simplified. +namespace folly { +template <class Key, class Value> struct sorted_vector_map { + typedef Key key_type; + typedef Value mapped_type; + typedef std::pair<Key, Value> value_type; + + typedef int size_type; + typedef struct {} iterator; + typedef struct {} const_iterator; + + const_iterator begin() const; + const_iterator end() const; + + std::pair<iterator, bool> insert(const value_type&); + std::pair<iterator, bool> insert(value_type&&); + iterator insert(const_iterator, const value_type&); + iterator insert(const_iterator, value_type&&); + template <class InputIterator> void insert(InputIterator, InputIterator); + void insert(std::initializer_list<value_type>); + + template <typename... Args> std::pair<iterator, bool> emplace(Args&&...); + std::pair<iterator, bool> emplace(const value_type&); + std::pair<iterator, bool> emplace(value_type&&); + + template <typename... Args> iterator emplace_hint(const_iterator, Args&&...); + iterator emplace_hint(const_iterator, const value_type&); + iterator emplace_hint(const_iterator, value_type&&); + + size_type erase(const key_type&); + iterator erase(const_iterator); + iterator erase(const_iterator, const_iterator); +}; + +template <class T> struct sorted_vector_set { + typedef T value_type; + typedef T key_type; + + typedef int size_type; + typedef struct {} iterator; + typedef struct {} const_iterator; + + const_iterator begin() const; + const_iterator end() const; + + std::pair<iterator, bool> insert(const value_type&); + std::pair<iterator, bool> insert(value_type&&); + iterator insert(const_iterator, const value_type&); + iterator insert(const_iterator, value_type&&); + template <class InputIterator> void insert(InputIterator, InputIterator); + void insert(std::initializer_list<value_type>); + + template <typename... Args> std::pair<iterator, bool> emplace(Args&&...); + std::pair<iterator, bool> emplace(const value_type&); + std::pair<iterator, bool> emplace(value_type&&); + + template <typename... Args> iterator emplace_hint(const_iterator, Args&&...); + iterator emplace_hint(const_iterator, const value_type&); + iterator emplace_hint(const_iterator, value_type&&); + + size_type erase(const key_type&); + iterator erase(const_iterator); + iterator erase(const_iterator, const_iterator); +}; +} // namespace folly + +struct Foo { + Foo() {} + Foo(const Foo&) = default; + Foo(Foo&&) = default; +}; + +void TestBoostFlapMap() { + boost::container::flat_map<Foo, int> bfm; + std::pair<Foo, int> foo(Foo(), 13); + boost::container::ordered_unique_range_t bar; + + bfm.emplace(Foo(), 13); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + bfm.emplace_hint(bfm.begin(), Foo(), 13); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + bfm.try_emplace(Foo(), 13); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + bfm.try_emplace(bfm.begin(), Foo(), 13); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + bfm.try_emplace(Foo(), 13); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + bfm.try_emplace(bfm.begin(), Foo(), 13); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + bfm.insert(foo); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + bfm.insert(std::move(foo)); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + bfm.insert(bfm.begin(), foo); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + bfm.insert(bfm.begin(), std::move(foo)); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + bfm.insert(bfm.begin(), bfm.end()); + bfm.insert(bar, bfm.begin(), bfm.end()); + bfm.insert(std::initializer_list<std::pair<Foo, int>>()); + bfm.insert(bar, std::initializer_list<std::pair<Foo, int>>()); + + bfm.erase(Foo()); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + bfm.erase(bfm.begin()); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + bfm.erase(bfm.begin(), bfm.end()); +} + +void TestBoostFlatSet() { + boost::container::flat_set<Foo> bfs; + Foo foo; + boost::container::ordered_unique_range_t bar; + + bfs.emplace(); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + bfs.emplace_hint(bfs.begin()); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + bfs.insert(foo); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + bfs.insert(std::move(foo)); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + bfs.insert(bfs.begin(), foo); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + bfs.insert(bfs.begin(), std::move(foo)); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + bfs.insert(bfs.begin(), bfs.end()); + bfs.insert(bar, bfs.begin(), bfs.end()); + bfs.insert(std::initializer_list<Foo>()); + bfs.insert(bar, std::initializer_list<Foo>()); + + bfs.erase(foo); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + bfs.erase(bfs.begin()); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + bfs.erase(bfs.begin(), bfs.end()); +} + +void TestFollySortedVectorMap() { + folly::sorted_vector_map<Foo, int> fsvm; + std::pair<Foo, int> foo(Foo(), 13); + + fsvm.insert(foo); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + fsvm.insert(std::move(foo)); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + fsvm.insert(fsvm.begin(), foo); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + fsvm.insert(fsvm.begin(), std::move(foo)); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + fsvm.insert(fsvm.begin(), fsvm.end()); + fsvm.insert(std::initializer_list<std::pair<Foo, int>>()); + + fsvm.emplace(Foo(), 13); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + fsvm.emplace(foo); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + fsvm.emplace(std::move(foo)); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + fsvm.emplace_hint(fsvm.begin(), Foo(), 13); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + fsvm.emplace_hint(fsvm.begin(), foo); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + fsvm.emplace_hint(fsvm.begin(), std::move(foo)); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + fsvm.erase(Foo()); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + fsvm.erase(fsvm.begin()); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + fsvm.erase(fsvm.begin(), fsvm.end()); +} + +void TestFollySortedVectorSet() { + folly::sorted_vector_set<Foo> fsvs; + Foo foo; + + fsvs.insert(foo); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + fsvs.insert(std::move(foo)); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + fsvs.insert(fsvs.begin(), foo); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + fsvs.insert(fsvs.begin(), std::move(foo)); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + fsvs.insert(fsvs.begin(), fsvs.end()); + fsvs.insert(std::initializer_list<Foo>()); + + fsvs.emplace(); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + fsvs.emplace(foo); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + fsvs.emplace(std::move(foo)); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + fsvs.emplace_hint(fsvs.begin()); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + fsvs.emplace_hint(fsvs.begin(), foo); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + fsvs.emplace_hint(fsvs.begin(), std::move(foo)); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + fsvs.erase(foo); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + fsvs.erase(fsvs.begin()); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + fsvs.erase(fsvs.begin(), fsvs.end()); +} + +struct A {}; +struct B : A {}; + +static int key = 13; +static int value = 31; + +void TestPossibleFalseNegatives() { + folly::sorted_vector_set<long> fsvs; + fsvs.insert(5); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + folly::sorted_vector_set<A> fsvs2; + fsvs2.insert(B()); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + folly::sorted_vector_set<std::pair<int, int>> fsvs3; + fsvs3.insert(std::pair<int, int>(13, 31)); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + using CustomSortedVectorMapType = folly::sorted_vector_map<int, int>; + CustomSortedVectorMapType map; + map.insert(std::pair<int, int>(5, 5)); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + folly::sorted_vector_map<int, int>* fsvmp = new folly::sorted_vector_map<int, int>; + fsvmp->emplace(std::move(key), value); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + (&(*fsvmp))->emplace(std::move(key), value); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + auto& fsvmr = *fsvmp; + fsvmr.emplace(key, value); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. +} Index: clang-tools-extra/test/clang-tidy/checkers/performance/expensive-flat-container-operation-only-warn-in-loops.cpp =================================================================== --- /dev/null +++ clang-tools-extra/test/clang-tidy/checkers/performance/expensive-flat-container-operation-only-warn-in-loops.cpp @@ -0,0 +1,106 @@ +// RUN: %check_clang_tidy %s performance-expensive-flat-container-operation %t -- \ +// RUN: -config="{CheckOptions: \ +// RUN: [{key: performance-expensive-flat-container-operation.OnlyWarnInLoops, \ +// RUN: value: true}] \ +// RUN: }" + +namespace std { +template <class T1, class T2> +struct pair { + pair(T1, T2); +}; + +template<class T> +struct initializer_list { + initializer_list(); +}; +} // namespace std + +// Copied from boost/container/flat_map.hpp and simplified. +namespace boost { +namespace container { +struct ordered_unique_range_t {}; + +template <typename Key> +struct flat_set { + typedef Key key_type; + typedef Key value_type; + + typedef int size_type; + typedef struct {} iterator; + typedef struct {} const_iterator; + + const_iterator begin() const noexcept; + const_iterator end() const noexcept; + + template <class... Args> std::pair<iterator, bool> emplace(Args&&...); + + template <class... Args> iterator emplace_hint(const_iterator, Args&&...); + + std::pair<iterator, bool> insert(const value_type&); + std::pair<iterator, bool> insert(value_type&&); + iterator insert(const_iterator, const value_type&); + iterator insert(const_iterator, value_type&&); + template <typename InputIterator> void insert(InputIterator, InputIterator); + template <typename InputIterator> void insert(ordered_unique_range_t, InputIterator, InputIterator); + void insert(std::initializer_list<value_type>); + void insert(ordered_unique_range_t, std::initializer_list<value_type>); + + iterator erase(const_iterator); + size_type erase(const key_type&); + iterator erase(const_iterator, const_iterator); +}; +} // namespace container +} // namespace boost + +static boost::container::flat_set<int> gbfs; +boost::container::flat_set<int>& getGlobalSetRef() { + return gbfs; +}; + +void TestCheckWithLoops() { + boost::container::flat_set<int> bfs; + bfs.insert(13); + bfs.erase(13); + + for (int i = 0; i < 10; ++i) { + bfs.insert(i); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + if (i % 2 == 0) { + bfs.erase(i); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + } + } + + while (true) { + bfs.erase(bfs.begin()); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + } + + do { + (&bfs)->emplace(13); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + } while (false); + + for (int i = 0; i < 10; ++i) { + boost::container::flat_set<long> bfs; + if (i % 2 == 0) { + bfs.insert(i); + + gbfs.insert(i); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + getGlobalSetRef().insert(i); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + + (gbfs).insert(i); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + } + + for (int j = 0; j < i; ++j) { + bfs.insert(2 * j); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Single element operations are expensive for flat containers. + } + } +} Index: clang-tools-extra/docs/clang-tidy/checks/performance/expensive-flat-container-operation.rst =================================================================== --- /dev/null +++ clang-tools-extra/docs/clang-tidy/checks/performance/expensive-flat-container-operation.rst @@ -0,0 +1,68 @@ +.. title:: clang-tidy - performance-expensive-flat-container-operation + +performance-expensive-flat-container-operation +============================================== + +Warns when calling an O(N) operation on a flat container. + +This check operates on vector-based flat containers such as +``boost::container::flat_(map|set)`` and ``folly::sorted_vector_(map|set)``. +While these containers' behavior is identical to usual maps/sets, the insert and +erase operations are O(N). This check flags such operations, which are a common +bad pattern, notably in loops. + +Below is an example of a typical bad pattern: inserting some values one by one +into a flat container. This is O(N^2), as the container will need to shift +elements right after each insertion. + +.. code-block:: c++ + + std::random_device generator; + std::uniform_int_distribution<int> distribution; + + boost::container::flat_set<int> set; + for (auto i = 0; i < N; ++i) { + set.insert(distribution(generator)); + } + +The code above can be improved using a temporary vector, later inserting all +values at once into the ``flat_set``. + +.. code-block:: c++ + + std::vector<int> temporary; + for (auto i = 0; i < N; ++i) { + temporary.push_back(distribution(generator)); + } + boost::container::flat_set<int> set(temporary.begin(), temporary.end()); + +For expensive-to-copy objects, ``std::move_iterator`` should be used. It is also +possible to move the temporary vector and use it directly as the underlying +container when using folly's ``sorted_vector_(map|set)``. + +Other solutions exist, including notably using a different map such as +``std::unordered_map`` when order is not important, bulk insertion with +iterators, ``boost::transform_iterator`` and ``erase_if``. + +This check is not capable of flagging insertions into a map via ``operator[]``, +as it is not possible at compile-time to know whether this will trigger an +insertion. These cases will have to be detected via dynamic profiling. + +Options +------- + +.. option:: OnlyWarnInLoops + + When enabled, the check will only warn when the single element operation is + directly enclosed by a loop, hence directly actionable. At the very least, + these cases can be improved using some temporary container. + + Default is `true`. + +.. option:: FlatContainers + + A semicolon-separated list of flat containers, with ``insert``, ``emplace`` + and/or ``erase`` operations. + + Default includes ``boost::container::flat_(map|set)`` and + ``folly::sorted_vector_(map|set)``. Index: clang-tools-extra/docs/clang-tidy/checks/list.rst =================================================================== --- clang-tools-extra/docs/clang-tidy/checks/list.rst +++ clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -302,6 +302,7 @@ `objc-super-self <objc/super-self.html>`_, "Yes" `openmp-exception-escape <openmp/exception-escape.html>`_, `openmp-use-default-none <openmp/use-default-none.html>`_, + `performance-expensive-flat-container-operation <performance/expensive-flat-container-operation.html>`_, `performance-faster-string-find <performance/faster-string-find.html>`_, "Yes" `performance-for-range-copy <performance/for-range-copy.html>`_, "Yes" `performance-implicit-conversion-in-loop <performance/implicit-conversion-in-loop.html>`_, Index: clang-tools-extra/docs/ReleaseNotes.rst =================================================================== --- clang-tools-extra/docs/ReleaseNotes.rst +++ clang-tools-extra/docs/ReleaseNotes.rst @@ -104,6 +104,11 @@ Warns when a struct or class uses const or reference (lvalue or rvalue) data members. +- New :doc:`performance-expensive-flat-container-operation + <clang-tidy/checks/performance/expensive-flat-container-operation>` check. + + Warns when calling an O(N) operation on a flat container. + New check aliases ^^^^^^^^^^^^^^^^^ Index: clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp =================================================================== --- clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp +++ clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp @@ -9,6 +9,7 @@ #include "../ClangTidy.h" #include "../ClangTidyModule.h" #include "../ClangTidyModuleRegistry.h" +#include "ExpensiveFlatContainerOperationCheck.h" #include "FasterStringFindCheck.h" #include "ForRangeCopyCheck.h" #include "ImplicitConversionInLoopCheck.h" @@ -32,6 +33,8 @@ class PerformanceModule : public ClangTidyModule { public: void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck<ExpensiveFlatContainerOperationCheck>( + "performance-expensive-flat-container-operation"); CheckFactories.registerCheck<FasterStringFindCheck>( "performance-faster-string-find"); CheckFactories.registerCheck<ForRangeCopyCheck>( Index: clang-tools-extra/clang-tidy/performance/ExpensiveFlatContainerOperationCheck.h =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/performance/ExpensiveFlatContainerOperationCheck.h @@ -0,0 +1,44 @@ +//===--- ExpensiveFlatContainerOperationCheck.h - clang-tidy ----*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_EXPENSIVEFLATCONTAINEROPERATIONCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_EXPENSIVEFLATCONTAINEROPERATIONCHECK_H + +#include "../ClangTidyCheck.h" + +#include <vector> + +namespace clang { +namespace tidy { +namespace performance { + +/// Warns when calling an O(N) operation on a flat container. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/performance/expensive-flat-container-operation.html +class ExpensiveFlatContainerOperationCheck : public ClangTidyCheck { +public: + ExpensiveFlatContainerOperationCheck(StringRef Name, + ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } + +private: + const bool OnlyWarnInLoops; + const std::vector<StringRef> FlatContainers; +}; + +} // namespace performance +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_EXPENSIVEFLATCONTAINEROPERATIONCHECK_H Index: clang-tools-extra/clang-tidy/performance/ExpensiveFlatContainerOperationCheck.cpp =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/performance/ExpensiveFlatContainerOperationCheck.cpp @@ -0,0 +1,100 @@ +//===--- ExpensiveFlatContainerOperationCheck.cpp - clang-tidy ------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "ExpensiveFlatContainerOperationCheck.h" + +#include "../utils/OptionsUtils.h" + +using namespace clang::ast_matchers; + +namespace { +const auto DefaultFlatContainers = + "::folly::sorted_vector_set; ::folly::sorted_vector_map;" + "::boost::container::flat_set; ::boost::container::flat_map;"; +} // namespace + +namespace clang { +namespace tidy { +namespace performance { + +ExpensiveFlatContainerOperationCheck::ExpensiveFlatContainerOperationCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + OnlyWarnInLoops(Options.get("OnlyWarnInLoops", true)), + FlatContainers(utils::options::parseStringList( + Options.get("FlatContainers", DefaultFlatContainers))) {} + +void ExpensiveFlatContainerOperationCheck::registerMatchers( + MatchFinder *Finder) { + const auto SoughtContainerDecl = cxxRecordDecl( + hasAnyName(FlatContainers), + has(typedefNameDecl( + hasName("value_type"), + hasType(hasUnqualifiedDesugaredType(type().bind("value_type")))))); + + const auto HasSoughtArrowMemberExpr = has(memberExpr( + isArrow(), hasObjectExpression( + hasType(pointerType(pointee(hasUnqualifiedDesugaredType( + recordType(hasDeclaration(SoughtContainerDecl))))))))); + + // We don't need to make sure the MemberExpr is not an arrow call, if it was + // the code wouldn't compile anyway. + const auto HasSoughtPeriodMemberExpr = + has(memberExpr(hasObjectExpression(hasType(hasUnqualifiedDesugaredType( + recordType(hasDeclaration(SoughtContainerDecl))))))); + + const auto HasEmplaceCall = callee(cxxMethodDecl(matchesName("emplace"))); + + const auto HasInsertCallWithValueTypeArgument = + cxxMemberCallExpr(callee(cxxMethodDecl(matchesName("insert"))), + hasAnyArgument(expr(hasType(hasUnqualifiedDesugaredType( + type(equalsBoundNode("value_type"))))))); + + const auto HasEraseCallWithOneArgument = cxxMemberCallExpr( + callee(cxxMethodDecl(matchesName("erase"))), argumentCountIs(1)); + + const auto SoughtFlatContainerOperation = + cxxMemberCallExpr( + anyOf(HasSoughtArrowMemberExpr, HasSoughtPeriodMemberExpr), + anyOf(HasEmplaceCall, HasInsertCallWithValueTypeArgument, + HasEraseCallWithOneArgument)) + .bind("call"); + + if (OnlyWarnInLoops) { + const auto IsWithinLoop = cxxMemberCallExpr( + hasAncestor(stmt(anyOf(forStmt(), whileStmt(), doStmt())).bind("loop")), + // An easy false positive case: the variable is declared in the loop. + unless(onImplicitObjectArgument(declRefExpr(hasDeclaration( + decl(hasAncestor(stmt(equalsBoundNode("loop"))))))))); + + Finder->addMatcher( + cxxMemberCallExpr(SoughtFlatContainerOperation, IsWithinLoop), this); + } else { + Finder->addMatcher(SoughtFlatContainerOperation, this); + } +} + +void ExpensiveFlatContainerOperationCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *Call = Result.Nodes.getNodeAs<CXXMemberCallExpr>("call"); + + diag(Call->getExprLoc(), + "Single element operations are expensive for flat containers. " + "Consider using available bulk operations instead."); +} + +void ExpensiveFlatContainerOperationCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "OnlyWarnInLoops", OnlyWarnInLoops); + Options.store(Opts, "FlatContainers", + utils::options::serializeStringList(FlatContainers)); +} + +} // namespace performance +} // namespace tidy +} // namespace clang Index: clang-tools-extra/clang-tidy/performance/CMakeLists.txt =================================================================== --- clang-tools-extra/clang-tidy/performance/CMakeLists.txt +++ clang-tools-extra/clang-tidy/performance/CMakeLists.txt @@ -4,6 +4,7 @@ ) add_clang_library(clangTidyPerformanceModule + ExpensiveFlatContainerOperationCheck.cpp FasterStringFindCheck.cpp ForRangeCopyCheck.cpp ImplicitConversionInLoopCheck.cpp
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits