26

Using logical operators for logical operations is good

 5 years ago
source link: https://www.tuicool.com/articles/hit/y6faiyM
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

My last piece Challenge your performance intuition with C++ operators was about how the context matters more than tricks. Unfortunately, I didn't make my point clear enough for which I'm truly sorry.

It might look like I advocate using math in favor of logic because of the performance gain. Well, I do. But only if a context is well-known and it is not going to change. This is the whole point. The context matters.

I find trickery appropriate when you know your hardware, and your compiler, and you are ready to redo your code from scratch when something changes. This might be the case if you run some computationally heavy algorithm in the cloud. Your environment is predetermined, and you pay per minute, so it makes sense to squeeze every penny from what you got.

But in general case, you should use logical operations to do logic. Not because the short-circuiting, this is also a context-dependent trick, but because in general case compilers do the trickery better than we humans do.

Let's redo a few rounds. The benchmark is the same, the questions are the same. The compiler is the same. The only thing that changes is the platform. This is now CHIP with ARMv7.

This is the original benchmark. I only reduced the number of operations tenfold because the machine itself is much slower.

#include <chrono>
#include <iostream>
#include <random>
#include <array>

int main() {
  using <b>TheType</b> = int;
  constexpr auto <b>TheSize</b> = 16 * 1000000;
  std::mt19937 rng(0);
  std::uniform_int_distribution<<b>TheType</b>> distribution(0, 1);
  std::vector<<b>TheType</b>> xs(<b>TheSize</b>);
  for (auto& digit : xs) {
    digit = distribution(rng);
  }

  volatile auto four_1_in_a_row = 0u;
  auto start = std::chrono::system_clock::now();
  <b>for (auto i = 0u; i < TheSize - 3; ++i)
    if(xs[i] == 1 && xs[i+1] == 1 && xs[i+2] == 1 && xs[i+3] == 1)
      ++four_1_in_a_row;</b>
  auto end = std::chrono::system_clock::now();

  std::cout << "time: " << (end-start).count() * 1e-9
    << "  1111s: " << four_1_in_a_row << "\n";
}

Just like the last time, using your intuition and best judgment, please estimate the relative performance of the code snippets from below.

Round 1. && vs &

The same question. Is && faster than &?

for (auto i = 0u; i < TheSize - 3; ++i)
    if(xs[i] == 1
    && xs[i+1] == 1
    && xs[i+2] == 1
    && xs[i+3] == 1)
      ++four_1_in_a_row;
for (auto i = 0u; i < TheSize - 3; ++i)
    if(xs[i] == 1
     & xs[i+1] == 1
     & xs[i+2] == 1
     & xs[i+3] == 1)
      ++four_1_in_a_row;

They are almost the same.

Round 2. ==, && vs *, +, -

On Intel, substituting logic with arithmetics gave a noticeable gain. Will the trick work on ARMv7?

for (auto i = 0u; i < TheSize - 3; ++i)
    if(xs[i] == 1
    && xs[i+1] == 1
    && xs[i+2] == 1
    && xs[i+3] == 1)
      ++four_1_in_a_row;
...
inline int sq(int x) {
  return x*x;
}
...
  for (auto i = 0u; i < TheSize - 3; ++i)
    if(sq(xs[i] - 1)
     + sq(xs[i+1] - 1)
     + sq(xs[i+2] - 1)
     + sq(xs[i+3] - 1) == 0)
      ++four_1_in_a_row;

They are almost the same.

Round 3. * vs abs

With Intel, switching multiplication to absolute value results in a noticeable loss. How will ARM do?

...
inline int sq(int x) {
  return x*x;
}
...
  for (auto i = 0u; i < TheSize - 3; ++i)
    if(sq(xs[i] - 1)
     + sq(xs[i+1] - 1)
     + sq(xs[i+2] - 1)
     + sq(xs[i+3] - 1) == 0)
      ++four_1_in_a_row;
for (auto i = 0u; i < TheSize - 3; ++i)
    if(std::abs(xs[i] - 1)
     + std::abs(xs[i+1] - 1)
     + std::abs(xs[i+2] - 1)
     + std::abs(xs[i+3] - 1) == 0)
      ++four_1_in_a_row;

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK