Can't recursive functions be built in?

Possible duplicate:
Can a recursive function be inline?

What are the trade-offs associated with creating recursive functions inside.

+4
source share
12 answers

Recursive functions that can be optimized using reverse recursion can certainly be built in. If the last thing the function does is call itself, then it can be converted into a simple loop.

+12
source

Arbitrary recursive functions cannot be built in for the same reason that a snake cannot acquire its own tail.

+9
source

[Edit: just noticed that although your title says “be inline”, your actual question says “do functions inline”. These two actually have nothing to do with each other, they just have vaguely similar names. In modern compilers, the main inline effect is what was originally on C99 (I think), just a necessary detail to do the built-in work in general: to allow several character definitions with external connection. This is because modern compilers do not pay much attention to the opinion of the programmer about whether the function should be built-in. However, they pay, so the confusion of concepts persists. I answered the question in the header, which is the decision that the compiler makes, and not the question in the body, which is the decision of the programmer.]

An investment is not necessarily an all-or-nothing transaction. One strategy that compilers use to decide whether it is built-in is to continue to embed function calls while the resulting code is "too large." "Big" is defined by some reliable reasonable heuristic.

So, consider the following recursive function (which is not intentionally tail-recursive):

 int triangle(int n) { if (n == 1) return 1; return n + triangle(n-1); } 

If it is called like this:

 int t100() { return triangle(100); } 

Then there is no fundamental reason, in principle, that the usual rules used by the compiler for embedding should not lead to the following:

 int t100() { // inline call to triangle(100) int result; if (100 == 1) { result = 1; } else { // inline call to triangle(99) int t99; if (100 - 1 == 1) { t99 = 1; } else { // inline call to triangle(98) int t98; if (100 - 1 - 1 == 1) { t98 = 1; } else { // oops, "too big", no more inlining t98 = triangle(100 - 1 - 1 - 1) + 98; } t99 = t98 + 99; } result = t99 + 100; } return result; } 

Obviously, the optimizer will have a field day with this, so it is much “smaller” than it looks:

 int t100() { return triangle(97) + 297; } 

The code in triangle itself can be “deployed” in several steps to several insertion levels, in the same way, except that it does not have the advantages of constants:

 int triangle(int n) { if (n == 1) return 1; if (n == 2) return 3; if (n == 3) return 6; return triangle(n-3) + 3*n - 3; } 

I doubt that compilers really do this, although I don't think I ever noticed this [Edit: MSVC, if you say that, thanks peterchen].

There is an obvious potential benefit in maintaining overhead, but by contrast, people do not expect recursive functions to be included in the system, and there is no particular guarantee that regular heuristic sketches will work well with recursive functions (where there are two different locations, call site and recursive call that can be embedded, with different advantages in each case). In addition, during compilation, it is difficult to evaluate how deep the recursion, and the built-in heuristics, would probably like to consider the depth of the call for decision making. Perhaps the compiler is simply not worried.

Functional language compilers are usually much more aggressive than recursion than C or C ++ compilers. The corresponding trade-off is that so many functions written in functional languages ​​are recursive that performance can be hopeless if the compiler cannot optimize tail recursion. Therefore, Lisp programmers usually rely on good optimization of recursive functions, while C and C ++ programmers usually do not.

+7
source

If your compiler does not support it, you can try to manually insert ...

 int factorial(int n) { int result = 1; if (n-- == 0) { return result; } else { result *= 1; if (n-- == 0) { return result; } else { result *= 2; if (n-- == 0) { return result; } else { result *= 3; if (n-- == 0) { return result; } else { result *= 4; if (n-- == 0) { return result; } else { // ... } } } } } } 

To solve a problem?

+4
source

Now hold on. The recursive tail function can be easily deployed and deployed. Apparently there are compilers that do this, but I don't know the specifics.

+3
source

Tail recursion (a special case of recursion) can be built in by smart compilers.

+3
source

Of course. Any function can be built in if it makes sense to do this:

 int f(int i) { if (i <= 0) return 1; else return i * f(i - 1); } int main() { return f(10); } 

pseudo-assembly (f is built into the main):

 main: mov r0, #10 ; Pass 10 to f f: cmp r0, #0 ; arg <= 0? ... bge 1l mov r0, #1 ; ... is so, return 1 ret 1: mov r0, -(sp) ; if not, save arg. dec r0 ; pass arg - 1 to f call f ; just because it inlined doesn't mean I can't call it. mul r0, (sp)+ ; compute the result ret ; done. 

; -)

+1
source

When you call a regular function, when you change the order of the command execution and jump (call or jmp) to some address where the function is located. Attachment means that in all cases of this function you place the commands of this function, therefore you do not have one place where you could jump, and you can also use other types of optimizations, such as defining the parameters of the pushing / popping function.

0
source

When you know that the recursive chain will not be too long in normal cases, you can attach to a predetermined level (I don’t know if any existing compiler is smart enough for this today).

The insertion of a recursive function is very similar to a loop reversal. You will have a lot of duplicate code, but in some cases this can be useful:

  • The number of recursive calls (chain length) is usually short (in cases when it becomes longer than the predetermined one, just do regular recursion)
  • The overhead of function calls is relatively large compared to the logic - so some “expanding” ones, for example, five instances, and ultimately make a recursive call again - will save 80% of the overhead.
  • Of course, tail-recursive is a special case, but others have spoken about this.
0
source

Some cna compilers return tail recursion to regular loops and therefore build them normally.

Recursion without a tail can be embedded to a given depth, usually determined by the compiler.

I have never seen a practical application for this, since the call cost is already not high enough to compensate for the increase in code size.

[edit] (to clarify that: although I like to play with these things and often check which code that my compiler generates for “funny things” just out of curiosity, I have not seen a case where any such doesn’t mean that they do not exist or cannot be built.

The only place this helps is to pre-compute low iterations at compile time. However, in my experience, this significantly increases compilation time for the often negligible performance benefits at runtime.

Note that Visual Studio 2008 (and earlier) gives you some pretty specific control over this:

 #pragma inline_recursion(on) #pragma inline_depth(N) __forceinline 

Be careful with the latter, it can easily overload the compiler :)

0
source

Of course, you can declare inline. The inline keyword is just a hint to the compiler. In many cases, the compiler simply ignores it, and depending on the compiler, this may be one of these situations.

0
source

Inline means that at every place a function is called that is marked as built-in, the compiler places a copy of the specified function code there. This avoids function calling mechanisms, and the usual argument stack is a click, saving time in gazillion-calls-per-second situations. Do you see the consequences for static variables and the like? everything went ...

So, if you had a built-in recursive call, or your compiler is super smart, and shows if the number of instances is deterministic, it will say “Cannot make it built-in” because it did not know when to stop.

-1
source

Source: https://habr.com/ru/post/1300407/


All Articles