Unroll a loop using a macro that has a goto and a

2019-09-22 07:33发布

问题:

This question is strictly related to the C or C++ language capabilities. I don't recommend the code below as a design pattern. I don't use it and I don't encourage it. But I'm just curious to improve my knowledge!

I have a define that contains a label and a goto condition.

#define BROP(num, sum)                  \
num = rand_lcg(generated);              \
if (num % 2)                            \
{                                       \
rng1:                                   \
    generated = rand_lcg(generated);    \
    if (generated < 512)                \
        sum -= generated;               \
    else                                \
        goto rng1;                      \
}

And later in the code I use it like this:

for (i = 0; i < iterations; i++)
{
    BROP(num, sum);
    BROP(num, sum);
    BROP(num, sum);
    // ...
}

I end up in a situation where the loop gets unrolled and the label gets redefined.

Is there a smart construct I can use to make the compiler rename the label each time the define is "instantiated"?

I know of ALL the alternatives of avoiding this statement but still, I don't know the answer to the question.

回答1:

You really should make it a do...while loop:

#define BROP(num, sum)  do{                \
   bool again = false;                     \
   num = rand_lcg(generated);              \
   if (num % 2)  {                         \
   do { again = false;                     \
     generated = rand_lcg(generated);      \
     if (generated < 512)                  \
           sum -= generated;               \
     else                                  \
        again = true;                      \
   } while(again); } while(0)

See this for the old outerdo{...}while(0) trick.

If you (wrongly) insist on having a label and are using the GCC compiler (or Clang/LLVM which should be compatible on that), you could use local labels extension (i.e. with __label__ ...)

You might also generate the label from the __LINE__ number using concatenation in the preprocessor. Take inspiration from

#define STUPID_LOOP_BIS(Test,Lin) do { \
 lab##Lin: if (Test) goto lab##Lin; } while(0)
#define STUPID_LOOP_AT(Test,Lin) STUPID_LOOP_BIS(Test,Lin)
#define STUPID_LOOP(Test) STUPID_LOOP_AT(Test,__LINE__)

for obscure reasons you need all the three macros!

And use

  STUPID_LOOP(x++ < 100);
  STUPID_LOOP(y-- > 0);

on separate lines. Of course adapt and improve for your need.

You definitely should use and trust more the compiler optimization abilities and have static inline functions. Not every test is compiled to a machine branch (e.g. because of CMOV instructions); not every loop is compiled to a machine loop (e.g. because of loop unrolling). You are probably losing your developer's time, and more importantly, you are disabling optimizations by your tricks (so your code will likely go slower, not faster).

If using GCC or Clang enable optimizations and warnings: so compile with
gcc -Wall -Wextra -O3 -mtune=native



回答2:

Ignoring the why of it, the following version of BROP compiles cleanly as both C and C++

#define BROP(num, sum, lbl)            \
num = rand_lcg(generated);              \
if (num % 2)                            \
{                                       \
lbl :                                   \
    generated = rand_lcg(generated);    \
    if (generated < 512)                \
        sum -= generated;               \
    else                                \
        goto lbl;                      \
}

I invoked it as

for (i = 0; i < 1000; i++)
{
    BROP(num,sum, lbl1);
    BROP(num,sum, lbl2);
}

This doesn't rely on any compiler extensions, so you should be able to use it across a large range of compilers.