Wednesday, July 6, 2016

Wait, did the PL/SQL compiler just REMOVE my code?

The PL/SQL compiler does more than compile - it also:
  1. automatically optimizes your code to run faster
  2. offers advice in the form of compile-time warnings to improve the quality and/or performance of your code
  3. allows you to conditionally include or exclude portions of your code for compilation
That's just fantastic - but it can now and then result in some confusing moments for the Oracle Database developer (well, at least this developer).

Recently, I was looking over the warnings I had gotten for a new package I'd written and saw this:


Wait - my "procedure" user_goals_cur was removed?

I could tell by the name that it was not a procedure - it was a cursor. So clearly the warning message hasn't been customized to the type of code removed. OK, that's no big deal - I can deal with that. 

But when I see the PLW-06006 warning, it has meant that I'd written a nested subprogram in a procedure or function but it was no longer used. It was superfluous code that should have been removed - so the compiler removed it for me (from the compiled code, not my source code).

But a cursor? Did I declare a cursor and then not use it? Entirely possible. 

So I opened up the package body and saw this:


As I expect you can see, the cursor is used. OK, now I was worried. It was removing code that I was using? How could that make any sense?

Well, you know what they say: Trust The Compiler. And trust people you know are way smarter than you - in this case, the PL/SQL development team.

So rather than panicking about the removal of essential code, I went back and took a closer look at the warnings report. And this time I noticed the second warning that referenced the same "USER_GOALS_CUR":


The cursor was inlined! Now this was interesting news for two reasons:
  1. I hadn't even realized that the compiler performed the inlining operation on cursors.
  2. It made me think that perhaps the inlining was somehow related to the removal of the cursor.
But first for those who are unfamiliar with the inlining feature, I will take a moment to explain.

Suppose I have a procedure defined as follows:

PROCEDURE p
IS
   FUNCTION f RETURN NUMBER
   IS BEGIN ... END;
BEGIN
   IF f() THEN ...
END;

When f is executed at runtime, PL/SQL spends a small amount of time finding the code for f. If you'd like to avoid this overhead, you can ask the compiler to inline the function. This means that when you compile your program, the invocation of a subprogram is replaced by the code itself. Your compiled code size increases, but the runtime performance is faster.

We recommend that you set your PL/SQL optimization level to 3 (default is 2) to take full advantage of this feature. You an also selectively request to inline or disable inlining with the PRAGMA INLINE statement. Check the doc for lots more information on inlining.

So when you see the PLW-06006 warning, you know that inlining optimization is enabled and has been put into effect.

And that's when the light bulb goes off in my head and I realize:
  1. A cursor really is no different than a function. You "pass in" bind variables and "return" one or more values via the SELECT list. So the compiler doesn't just inline procedures and functions. It inlines cursors, too! How cool is that?
  2. Once the cursor (or procedure or function) has been inlined, there is no reason to keep it defined as a cursor or subprogram in the declaration section. So the compiler removes it.
That all makes perfect sense, but, gee, that's also kind of confusing. False alarm, and all that. 

The bottom line is that the PL/SQL compiler will "remove" code (leave it "behind" in the source code and not move it to the compiled code) only when it is truly not used or not needed to execute your code.

So remember:

Trust your compiler.

But verify your understanding of what it is doing.









10 comments:

  1. Trust the compiler, but, still not sure what is the advantage of inline function here. Did we avoid the context switch using inline function ( I am sure no), What different it is when we open a cursor and return the value returned by SQL engine to the PLSQL record, compared to using a function which in turn calls the SQL which does the same thing.

    ReplyDelete
  2. Context switches do not come into play here, since there is no switch between the SQL and PL/SQL engines. Instead, inlining avoids the overhead of looking up the code block for the procedure or function or (as it turns out) cursor. Instead, the code is simple "there", inline in the current block.

    ReplyDelete
  3. If you in-line a procedure or function you never have to allocate a stack frame for it. I.e. a local scope for the procedure's state: variables, etc. And then you don't have to remove that stack frame after exiting the procedure/function.

    This can be a significant win if you're calling a procedure in a loop (allocate frame, de-allocate frame, repeat), and the more state associated with the procedure the bigger the gain.

    ReplyDelete
  4. Thanks, Scott. That is a very concise and clear explanation. Maybe you should write a book! :-)

    But I do see you've been busily adding useful code to Github! Check it out folks: https://github.com/scott-swank/plsql-util

    ReplyDelete
    Replies
    1. Thank ya Steven.

      And actually I have a few updates to push up to github now that you mention it. In point of fact, once I get a couple things tidied up I'd like to talk to someone about providing a bit of my code (not yet on github) to Oracle. Who should I talk to on that front?

      Delete
    2. Scott, I am pretty use that Oracle will not accept code from outside contributors but why don't you follow up via email steven at feuerstein dot com, and we can explore further.

      Delete
    3. ARGH. I can't even type my sort of email address right. I meant to type: steven dot feuerstein at oracle dot com. But no worries. I got your email via my stevenfeuerstein domain. Thanks!

      Delete
  5. Hi Steven,

    As you have mentioned, The function call get replaced by the the code itself and that hasnt any associated context switches. So, if i have the below function with a huge code which gets replaced at the occurence of Function call, how is the procedural part of my code executed. A small description (any web link with detailed description) would be appreciated here.

    Function F return number is
    begin

    IF condition_1 THEN
    SELECT INTO value1
    ELSIF condition_2 THEN
    statements_2
    [ ELSIF condition_3 THEN
    statements_3
    ]...
    [ ELSE
    else_statements
    ]
    END IF;

    For rec in 1..10
    loop
    sum:=sum+rec;
    end loop;

    Return sum+value1;
    End if;


    Also, with reference to context switches, I always had a doubt of how does the inbuilt functions like nvl,decode, sum, avg etc. work ( i am aware of the standard package which sets the environment), but do we really call a PLSQL code in the background and are there any associated context switches.

    ReplyDelete
  6. re: how is the procedural part of my code executed

    I suggest that the best answer to this question is: don't worry about it. You don't need to know about or concern yourself with the internal operations of the compiler (which can change with each new release anyway). It does the right thing and ensures that the logical result of your code remains unchanged.

    re: context switches and inbuilt functions like nvl,decode, sum, avg

    These functions are implemented directly in the SQL engine. There is no context switch for the built-in SQL functions.

    ReplyDelete