3

From David Abrahams and Aleksey Gurtovoy's book "C++ Template Metaprogramming", I learned that iter_swap (see below) would be much slower than std::swap sometimes. Although the book has some explanation, I did not quite get it, could someone explains the reason behind it with more details.

template <typename ForwardIt1>
void iter_swap(ForwardIt1 it1, ForwardIt1 it2){
  typedef typename std::iterator_traits<ForwardIt1>::value_type T;
  T tmp = *it1;
  *it1 = *it2;
  *it2 = tmp;
}

template <typename ForwardIt1>
void swap_wrapper(ForwardIt1 it1, ForwardIt1 it2){
  std::swap(*it1, *it2);
}

By applying them on std::list<std::vector<std::string>>::iterator, I found the first is 10X slower than the second even when the size of the vector (whose elements are all small strings, length less than 10) is just 10.

Michael Burr
  • 333,147
  • 50
  • 533
  • 760
Long Gong
  • 54
  • 1
  • 3
  • Those are both *valid* `iter_swap`s. Neither are `swap`s. And there already exists `std::iter_swap`. – Caleth Feb 20 '19 at 17:10
  • Can you elaborate on what you don't understand about the book's explanation (on page 19): "copying a vector means copying all of its elements, and each string element copied or assigned is likely to require a dynamic memory allocation and a bitwise copy of the string’s characters" whereas `std:swap()` "provides an efficient version of swap for vectors that just exchanges a few internal pointers"? – Michael Burr Feb 20 '19 at 17:50
  • Actually, I can understand the explanation "literally". I cannot understand why cannot we just use the optimization technique used for `std::swap` for `iter_swap` to make `iter_swap` as efficient as `std::swap`. However, after you (@MichaelBurr) edited my question, I just realized that my question is stupid because `iter_swap` can be implemented as efficient as `std::swap`, i.e., `std::iter_swap`. Thanks @MichaelBurr ! – Long Gong Feb 20 '19 at 18:33
  • All I did was correct Aleksey Gurtovoy's name! The key thing to realize is that `std::swap()` is efficient for `std::vector` because `std::vector` provides a specialization for `swap()` that knows about and takes advantage of the internals of `std::vector`. – Michael Burr Feb 20 '19 at 21:42

2 Answers2

6

Your iter_swap() is often sub-optimal, if it "works" at all, and is not flat-out wrong.

  1. There is a reason std::iter_swap() delegates to swap() using :
    Picking up tailored implementations.

  2. In addition, the fallback std::swap() takes advantage of move-semantics when swapping, potentially eliminating costly resource-acquisition, which you didn't use.

Of course, both are irrelevant for trivial types.

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
  • 2
    You’re right but the question was *why* it’s suboptimal (you hint at that with move semantics but it’s probably not obvious how this impacts OP’s code). – Konrad Rudolph Feb 20 '19 at 17:21
  • @KonradRudolph Clarified it. – Deduplicator Feb 20 '19 at 17:45
  • @KonradRudolph I've tried to add `std::move` (but I am not sure whether it will force move-semantics) on the RHSs of the three "assignment" in `iter_swap`. After "optimization", the first is still 5X slower than the second. I guess that some more advanced optimization techniques are used in `std::swap`. – Long Gong Feb 20 '19 at 18:40
  • 1
    @LongGong 1. Did you ask your compiler to optimize? 2. While the fallback `std::swap()` uses move-semantics, ADL leads to using the custom implementation, which is better. – Deduplicator Feb 20 '19 at 18:57
  • @Deduplicator I used the default optimization level (i.e., `O0` for gcc). I have tried other optimization levels, I found that with `Ox` (x>0), for the version with `std::move`, the gap between the first and the second can be reduced to 2X, i.e., the first is still 2X slower. For the one without `std::move`, after turning on optimization, the gap gets even larger (up to 100X). – Long Gong Feb 20 '19 at 19:20
2

std::swap of a vector is a constant complexity operation.

The complexity of copying a vector increases linearly in relation to the length of the vector.

T tmp = *it1 calls the copy constructor. *it1 = *it2 and *it2 = tmp call the copy assignment operator.

eerorika
  • 232,697
  • 12
  • 197
  • 326