Good test cases, I get the same result with Oracle 11gR1 (11.1.0.7.0).
Here is what doc has to say on NOCOPY :
Note NOCOPY (described in "NOCOPY").
By default, PL / SQL passes OUT and IN OUT routine parameters by value. Before starting the subroutine, PL / SQL copies each OUT and IN OUT parameter to a temporary variable that contains the parameter value during the execution of the subroutine. If the routine completed normally, then PL / SQL copies the value of the temporary variable to the corresponding actual parameter. If the routine terminates with an unhandled exception, PL / SQL does not change the value of the actual parameter.
When the OUT or IN OUT parameters are large data structures such as collections, records, and ADT instances, copying them slows down execution and increases memory usage, especially for an ADT instance.
For each ADT method call, PL / SQL copies each ADT attribute. If the method runs normally, then PL / SQL applies any changes that the method made to the attributes. If the method terminates with an unhandled exception, PL / SQL does not change the attributes.
If your program does not require the OUT or IN OUT parameter to preserve the pre-invocation value, if the routine ends with an unhandled exception, then include the NOCOPY hint in the parameter declaration. Specifying NOCOPY requests (but does not guarantee) that the compiler passes the corresponding actual parameter by reference instead of a value.
Note that NOCOPY described as just a hint (i.e. not a command). There are times when it is not respected .
In any case, the behavior of NOCOPY is standard for cases (1) and (3) (yes, PL / SQL will restore the value of the OUT parameter in case of an error). How about (2)?
I think NULL procedures are optimized in case (2). Try disabling optimization:
SQL> alter session set plsql_optimize_level=0; Session altered SQL> DECLARE 2 TYPE my_type IS TABLE OF LONG INDEX BY BINARY_INTEGER; 3 my_array my_type; 4 st NUMBER; 5 rt NUMBER; 6 PROCEDURE in_out(m1 IN OUT my_type) IS 7 BEGIN 8 NULL;--dbms_output.put_line(my_array(1)); 9 END in_out; 10 PROCEDURE in_out_nocopy(m1 IN OUT NOCOPY my_type) IS 11 BEGIN 12 NULL;--dbms_output.put_line(my_array(1)); 13 END in_out_nocopy; 14 BEGIN 15 FOR i IN 1 .. 9999999 LOOP 16 my_array(i) := 17 '123456789012345678901234567890123456789012345678901234567890abcd'; 18 END LOOP; 19 st := dbms_utility.get_time; 20 in_out(my_array); 21 rt := (dbms_utility.get_time - st) / 100; 22 dbms_output.put_line('Time needed for in out is: ' 23 || rt || ' seconds!'); 24 st := dbms_utility.get_time; 25 in_out_nocopy(my_array); 26 rt := (dbms_utility.get_time - st) / 100; 27 dbms_output.put_line('Time needed for in out nocopy is: ' 28 || rt || ' seconds!'); 29 END; 30 / Time needed for in out is: 5,59 seconds! Time needed for in out nocopy is: 0 seconds!
As expected, the difference magically appears :)