You are on page 1of 42

CODE TIMING AND OBJECT ORIENTATION AND ZOMBIES

Author: Creation Date: Version: Last Updated: Brendan Furey 22 November 2010 1.7 25 September 2012

Bless thee, Bottom! bless thee! thou art translated A Midsummer Nights Dream

110516815.doc

Page 1 of 42

Table of Contents

Introduction.......................................................................................................4 Hardware/Software Summary.......................................................................4 Object Data Structure........................................................................................5 Object Diagram............................................................................................5 Object Structure Table.................................................................................5 Object Method Structure....................................................................................7 Call Structure Diagram.................................................................................7 Public Methods.............................................................................................7
New (constructor function)...................................................................................7 Init Time................................................................................................................7 Increment Time.....................................................................................................7 Get Timer..............................................................................................................8 Write Times...........................................................................................................8

Oracle Implementation (Standard Object Model)................................................9 Timer Set Object..........................................................................................9


Code......................................................................................................................9 Code....................................................................................................................12 Instance Hash Arrays..........................................................................................14 Code Excerpt.......................................................................................................14 With Inner Loop Timing.......................................................................................15 Without Inner Loop Timing..................................................................................15 Notes on Timing Results ....................................................................................15

Utility Package...........................................................................................12 Notes on Oracle (Standard Object Model) Implementation..........................14 Test Program (Employee Hierarchy)...........................................................14 Example Output.........................................................................................15

Oracle Implementation (Zombie Object Model)................................................17 Timer Set Package.....................................................................................17


Code....................................................................................................................17 Notes on Oracle (Zombie Object Model) Implementation..................................21

Example Output.........................................................................................21 Perl Implementation.........................................................................................23 Timer Set Object........................................................................................23


Code....................................................................................................................23 Notes on Perl Implementation ...........................................................................25 Code....................................................................................................................25 Driver Code.........................................................................................................26 Notes on Timing Results ....................................................................................29

Utility Package...........................................................................................25 Test Program (Directory Tree)....................................................................25 Example Output.........................................................................................27 Java Implementation........................................................................................30 Timer Set Object........................................................................................30
Code....................................................................................................................30 Notes on Java Implementation ...........................................................................32 Code....................................................................................................................32 Code....................................................................................................................32 Notes on Timing Results ....................................................................................35
Page 2 of 42

Utility Package...........................................................................................32 Test Driver Program (Web Service Proxy)...................................................32 Example Output.........................................................................................34
110516815.doc

Oracle Object Orientation.................................................................................36 Object-Relational Model and String Theory.................................................36 Objects and Packages................................................................................36
Object Type Header and Body............................................................................36 Oracle Object Limitations....................................................................................36 Advantages of Object Orientation.......................................................................37 Object Orientation without Objects..................................................................37

The Zombie Object Model...........................................................................37

Comparative Notes..........................................................................................39 Feature Comparison Table..........................................................................39 Conclusions.....................................................................................................41 References.......................................................................................................42

Change Record
Date 22-Nov-2010 28-Nov-2010 04-Dec-2010 22-Dec-2010 24-Oct-2011 27-Oct-2011 15-Jan-2012 25-Sep-2012 Author BPF BPF BPF BPF BPF BPF BPF BPF Version 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 Change Reference Initial Added issue and reference concerning lack of private attributes Some minor typos Added CPU times, simplified object structure Placeholder for next revision Title changed from original of A Simple PL/SQL Code Timing Object to reflect rewrite of contents Get_Timer_Stats code: Small bug fix References into hyperlinks

110516815.doc

Page 3 of 42

Introduction
This article proposes an object-oriented design for simple CPU and elapsed timing of computer programs by individual code section or subroutine. The object data structure is first described using a diagram/tabulation approach first used in A Perl Object for Flattened Master-Detail Data in Excel, followed by a section describing method usage, and including a diagram showing a typical call structure. The object class is then translated from the Ur-language of design into the programming languages of Oracle, Perl and Java (one might say that our class of class is instantiated into specific object classes for each language). In each case the code is listed, an example driving program is briefly described, the run results are listed, and any interesting features are highlighted. Oracle's implementation of object-orientation is rather different from other languages, and a section of the article discusses how one can best obtain the advantages of object orientation in Oracle, suggesting that it's often better to bypass the 'official' object structures. Both approaches have been implemented here to help readers judge for themselves. Finally, some notes are collated on differences between the languages.

Hardware/Software Summary
Component Oracle Database Perl Java Oracle JDeveloper Diagrammer Operating System Computer Description Oracle Database 10g Express Edition Release 10.2.0.1.0 - Production ActivePerl 5.12.4.1205, by ActiveState Software Inc. 1.5.0_06 10.1.3.1.0 Microsoft Visio 2003 (11.3216.5606) Microsoft Windows 7 Home Premium (64 bit) Samsung 900X3A, 4GB memory, Intel I5-2537M @ 1.4GHz x 2

110516815.doc

Page 4 of 42

Object Data Structure


The diagram/tabulation approach to design of complex data structures shown here is intended to be quite general and was first used in A Perl Object for Flattened Master-Detail Data in Excel

Object Diagram
The diagram below shows the data structure of the timer set object. Boxes with a double border denote arrays, with hash arrays having the key-value linkage.

Object Structure Table


This section tabulates the type, or class, data structure. The object logically contains the three types specified below; however, note that in Oracle a hash type cannot be physically an instance variable, a major deficiency in Oracle, which has been worked around by storing the hash in a package. Also, in Oracle the system and user CPU times are not available separately, so are stored in aggregate. Type Name Global Data Element Start Time Start CPU Time (User) Start CPU Time (System) Prior Time Prior CPU Time (User) Prior CPU Time (System)
110516815.doc

Category Record Timestamp Integer Integer Timestamp Integer Integer

Description Timer set level data Time of set construction User CPU time offset at set construction System CPU time offset at set construction. Prior time Prior user CPU time offset Prior system CPU time offset
Page 5 of 42

Timer Set Name Timer Name Elapsed Time CPU Time (User) CPU Time (System) Timer Name Timer Index

Timer List

Timer Hash

Character Array Character Number Integer Integer Hash Character Integer

Name of timer set List of timers (built dynamically) Name of timer Elapsed time associated with timer User CPU time associated with timer System CPU time associated with timer. Hash used to find timer in the list without searching Name of timer (hash key) Index of timer in timer list (hash value)

110516815.doc

Page 6 of 42

Object Method Structure


Call Structure Diagram
The timer set object is designed for maximum flexibility with minimum footprint, and can be used by both object-oriented and non-OO programs. The diagram below shows a schematic example of a possible call structure for use by another object.

Public Methods
New (constructor function) This is the constructor function. It initialises the start and prior times and stores the timer set name. Parameters Column Timer Set Name Return Value SELF Init Time This procedure resets the prior time variables. It need be called only if there is a gap from either construction or an earlier timing section. Increment Time If a timer with the name passed in doesnt yet exist, this procedure creates a new timer, and initialises the times to the differences from the stored prior times, while if the timer exists it increments its times. A hash
110516815.doc Page 7 of 42

Type Character

Notes Timer set name

array is used to check existence. Timer creation can be data driven if desired, perhaps by including a parameter in a method that calls this timing method. Parameters Name Timer Name Get Timer This method returns the times and number of calls in seconds for the passed timer name. Details of output variables vary by implementation. Parameters Name Timer Name Write Times This method writes out the timings in seconds, with numbers of calls, preceded by the timer names, followed by the total times and the difference between these and the total tracked times. It also creates a new timer set and increments it by a set number of times, in order to measure how much time the act of timing takes. For timers that show CPU times per call below a certain multiple of the timer overhead, and with significant total CPU time, a warning line is printed. Type Character Notes Timer name Type Character Notes Timer name

110516815.doc

Page 8 of 42

Oracle Implementation (Standard Object Model)


This first Oracle implementation uses standard Oracle objects with a package for utility code, and for a work-around necessary due to limitations in Oracles object model, which are described later.

Timer Set Object


Code
SPOOL C_Timer REM REM Author: Brendan Furey REM Date: 27 October 2011 REM Description: Brendan's Code-Timing Object, as described at scribd.com/BrendanP, Oracle version REM in standard scheme. Type specs, plus one body REM DROP TYPE timer_set_type / DROP TYPE timer_list_type / CREATE OR REPLACE TYPE timer_type AS OBJECT (name VARCHAR2(30), ela_interval INTERVAL DAY(1) TO SECOND, cpu_interval INTEGER, n_calls INTEGER ) / CREATE TYPE timer_list_type AS VARRAY(100) OF timer_type / CREATE OR REPLACE TYPE name_list_type AS VARRAY(100) OF VARCHAR2(30) / CREATE OR REPLACE TYPE int_list_type AS VARRAY(1000) OF INTEGER / CREATE OR REPLACE TYPE num_list_type AS VARRAY(1000) OF NUMBER / CREATE TYPE timer_set_type AS OBJECT ( timer_list timer_set_name start_time prior_time start_time_cpu prior_time_cpu timer_list_type, VARCHAR2(30), TIMESTAMP, TIMESTAMP, INTEGER, INTEGER,

CONSTRUCTOR FUNCTION timer_set_type (p_timer_set_name VARCHAR2) RETURN SELF AS RESULT, MEMBER PROCEDURE Init_Time, MEMBER PROCEDURE Increment_Time (p_timer_name VARCHAR2, p_check_new BOOLEAN DEFAULT TRUE), MEMBER PROCEDURE Get_Timer_Stats (p_timer_name VARCHAR2, x_ela_secs OUT NUMBER, x_cpu_secs OUT NUMBER, x_calls OUT PLS_INTEGER), MEMBER PROCEDURE Write_Times (p_do_self_timer BOOLEAN DEFAULT FALSE) ) / SHO ERR CREATE OR REPLACE TYPE BODY timer_set_type AS /************************************************************************************************** Author: Date: Description: Brendan Furey 27 October 2011 Brendan's Code-Timing Object, as described at scribd.com/BrendanP, Oracle version, in standard scheme. Type body.

***************************************************************************************************/ CONSTRUCTOR FUNCTION timer_set_type (p_timer_set_name VARCHAR2) RETURN SELF AS RESULT IS l_timer timer_type; BEGIN timer_set_name start_time prior_time start_time_cpu prior_time_cpu timer_list RETURN; END timer_set_type; 110516815.doc Page 9 of 42 := := := := := := p_timer_set_name; SYSTIMESTAMP; start_time; DBMS_Utility.Get_CPU_Time; start_time_cpu; NULL;

MEMBER PROCEDURE Init_Time IS BEGIN prior_time := SYSTIMESTAMP; prior_time_cpu := DBMS_Utility.Get_CPU_Time; END Init_Time; MEMBER PROCEDURE Increment_Time (p_timer_name VARCHAR2, p_check_new BOOLEAN DEFAULT TRUE) IS l_cpu_time INTEGER := DBMS_Utility.Get_CPU_Time; l_systimestamp TIMESTAMP := SYSTIMESTAMP; l_timer_ind PLS_INTEGER := 0; l_timer timer_type; l_bool BOOLEAN; BEGIN l_timer := timer_type (p_timer_name, l_systimestamp - prior_time, l_cpu_time - prior_time_cpu, 1); IF timer_list IS NULL THEN timer_list := timer_list_type (l_timer); l_bool := Utils.Timer_Hash (To_Char(start_time) || p_timer_name, l_timer_ind); ELSE l_timer_ind := timer_list.COUNT; IF NOT p_check_new OR NOT Utils.Timer_Hash (To_Char(start_time) || p_timer_name, l_timer_ind) THEN timer_list.EXTEND; timer_list(timer_list.COUNT) := l_timer; l_timer_ind := timer_list.COUNT; ELSE timer_list (l_timer_ind).ela_interval := timer_list (l_timer_ind).ela_interval + l_systimestamp prior_time; timer_list (l_timer_ind).cpu_interval := timer_list (l_timer_ind).cpu_interval + l_cpu_time prior_time_cpu; timer_list (l_timer_ind).n_calls := timer_list (l_timer_ind).n_calls + 1; END IF; END IF; prior_time := l_systimestamp; prior_time_cpu := l_cpu_time; END Increment_Time; MEMBER PROCEDURE Get_Timer_Stats (p_timer_name VARCHAR2, x_ela_secs OUT NUMBER, x_cpu_secs OUT NUMBER, x_calls OUT PLS_INTEGER) IS l_index PLS_INTEGER := Utils.Timer_Hash (To_Char(start_time) || p_timer_name); BEGIN IF l_index = RETURN; ELSE x_ela_secs x_cpu_secs x_calls := END IF; 0 THEN := Utils.Get_Seconds (timer_list (l_index).ela_interval); := 0.01*timer_list (l_index).cpu_interval; timer_list (l_index).n_calls;

END Get_Timer_Stats; MEMBER PROCEDURE Write_Times (p_do_self_timer BOOLEAN DEFAULT FALSE) IS c_self_timer_name CONSTANT VARCHAR2(10) := 'STN'; l_sum_ela NUMBER := 0; l_sum_cpu NUMBER := 0; l_ela_seconds NUMBER; l_head_len PLS_INTEGER; l_self_timer timer_set_type; l_time_ela NUMBER := 0; l_time_cpu NUMBER := 0; l_n_calls PLS_INTEGER := 0; l_n_calls_sum PLS_INTEGER := 0; i PLS_INTEGER := 0; PROCEDURE Write_Lines IS c_lines_1 CONSTANT VARCHAR2(1000) := RPad ('-', l_head_len, '-'); c_lines CONSTANT VARCHAR2(10) := '----------'; BEGIN Utils.Write_Big (c_lines_1 || ' 110516815.doc ' || c_lines || ' ' || c_lines || ' ' || '--' || c_lines || ' Page 10 of 42

---' || c_lines || ' END Write_Lines;

---' || c_lines);

FUNCTION Form_Time (p_time INTEGER, p_dp PLS_INTEGER DEFAULT 2) RETURN VARCHAR2 IS l_dp_zeros VARCHAR2(10) := Substr ('0000000000', 1, p_dp); BEGIN IF p_dp > 0 THEN l_dp_zeros := '.' || l_dp_zeros; END IF; RETURN ' ' || To_Char (p_time, '99,990' || l_dp_zeros); END Form_Time; FUNCTION Form_Calls (p_calls INTEGER) RETURN VARCHAR2 IS BEGIN RETURN ' ' || To_Char (p_calls, '999,999,990'); END Form_Calls; PROCEDURE Write_Time_Line (p_timer VARCHAR2, p_ela NUMBER, p_cpu NUMBER, p_n_calls PLS_INTEGER) IS BEGIN Utils.Write_Big (RPad (p_timer, l_head_len) || Form_Time (p_ela) || Form_Time (0.01*(p_cpu)) || Form_Calls (p_n_calls) || Form_Time (p_ela/p_n_calls, 5) || Form_Time (0.01*(p_cpu/p_n_calls), 5)); IF p_timer != '***' AND p_cpu/p_n_calls < 10 * l_time_cpu AND p_cpu > 100 THEN Write_Time_Line ('***', p_ela - p_n_calls*l_time_ela, p_cpu - p_n_calls*l_time_cpu, p_n_calls); END IF; END Write_Time_Line; BEGIN prior_time := start_time; prior_time_cpu := start_time_cpu; Increment_Time ('Total', FALSE); Utils.Heading ('Timer Set: ' || timer_set_name || ', constructed at ' || To_Char (start_time, Utils.c_datetime_fmt) || ', written at ' || To_Char (SYSDATE, Utils.c_time_fmt)); l_head_len := 7; FOR i IN 1..timer_list.COUNT LOOP IF Length (timer_list(i).name) > l_head_len THEN l_head_len := Length (timer_list(i).name); END IF; END LOOP; l_self_timer := timer_set_type ('Self'); FOR i IN 1..1000 LOOP l_self_timer.Increment_time (c_self_timer_name); END LOOP; l_self_timer.Get_Timer_Stats (p_timer_name => c_self_timer_name, x_ela_secs => l_time_ela, x_cpu_secs => l_time_cpu, x_calls => l_n_calls); Utils.Write_Big ('[Timer timed: Elapsed (per call): ' || LTrim (Form_Time (l_time_ela)) || ' (' || LTrim (Form_Time (l_time_ela/l_n_calls, 6)) || '), CPU (per call): ' || LTrim (Form_Time (l_time_cpu)) || ' (' || LTrim (Form_Time(l_time_cpu/l_n_calls, 6)) || '), calls: ' || l_n_calls || ', ''***'' denotes corrected line below]'); l_time_ela := l_time_ela/l_n_calls; l_time_cpu := 100*l_time_cpu/l_n_calls; -- Get_Timer_Stats converts to seconds, Write_Time_Line assumes csecs Utils.Write_Big (RPad ('Timer', l_head_len) || ' Ela/Call CPU/Call'); Write_Lines; FOR i IN 1..timer_list.COUNT LOOP l_ela_seconds := Utils.Get_Seconds (timer_list(i).ela_interval); l_sum_ela := l_sum_ela + l_ela_seconds; l_sum_cpu := l_sum_cpu + timer_list(i).cpu_interval; l_n_calls := timer_list(i).n_calls; l_n_calls_sum := l_n_calls_sum + l_n_calls; IF i = timer_list.COUNT THEN Write_Time_Line ('(Other)', 2*l_ela_seconds - l_sum_ela, 2*timer_list(i).cpu_interval - l_sum_cpu, 1); Write_Lines; l_n_calls := l_n_calls_sum; 110516815.doc Page 11 of 42 Elapsed CPU Calls

END IF; Write_Time_Line (timer_list(i).name, l_ela_seconds, timer_list(i).cpu_interval, l_n_calls); END LOOP; Write_Lines; END Write_Times; END; / SHO ERR l CREATE PUBLIC SYNONYM timer_set_type FOR timer_set_type / GRANT ALL ON timer_set_type TO PUBLIC / CREATE PUBLIC SYNONYM name_list_type FOR name_list_type / GRANT ALL ON name_list_type TO PUBLIC / CREATE PUBLIC SYNONYM int_list_type FOR name_list_type / GRANT ALL ON int_list_type TO PUBLIC / CREATE PUBLIC SYNONYM num_list_type FOR num_list_type / GRANT ALL ON num_list_type TO PUBLIC / SPOOL OFF

Utility Package
The Utility package has a few procedures concerned with writing and formatting output, as well as an overloaded function Timer_Hash that implements the instance hash array work-around. Code
CREATE OR REPLACE PACKAGE Utils AS /************************************************************************************************** Author: Date: Description: Brendan Furey 27 October 2011 Utils package used by Brendan's Code-Timing Object, described at scribd.com/BrendanP, Oracle version. Package spec.

***************************************************************************************************/ PROCEDURE Clear_Log; PROCEDURE Write_Log (p_text VARCHAR2); PROCEDURE Write_Big (p_text VARCHAR2, p_level PLS_INTEGER DEFAULT 0, p_debug_level PLS_INTEGER DEFAULT 0); FUNCTION Get_Seconds (p_interval INTERVAL DAY TO SECOND) RETURN NUMBER; FUNCTION Timer_Hash (p_set_name VARCHAR2, x_timer_ind IN OUT PLS_INTEGER) RETURN BOOLEAN; FUNCTION Timer_Hash (p_set_name VARCHAR2) RETURN PLS_INTEGER; PROCEDURE Heading (p_head VARCHAR2); c_time_fmt c_datetime_fmt g_debug_level g_id g_line_size CONSTANT VARCHAR2(30) := 'HH24:MI:SS'; CONSTANT VARCHAR2(30) := 'DD Mon RRRR ' || c_time_fmt; PLS_INTEGER := 1; VARCHAR2(30); PLS_INTEGER := 150;

END Utils; / SHOW ERROR CREATE OR REPLACE PACKAGE BODY Utils AS /************************************************************************************************** Author: Date: Description: Brendan Furey 27 October 2011 Utils package used by Brendan's Code-Timing Object, described at scribd.com/BrendanP, Oracle version, in standard scheme. Package body.

***************************************************************************************************/ c_head_under CONSTANT VARCHAR2(200) := '==================================================================================================== ===================================================================================================='; g_timer_list timer_list_type; g_start_time 110516815.doc TIMESTAMP; Page 12 of 42

g_init_time TYPE hash_type IS g_timer_hash

TIMESTAMP; TABLE OF PLS_INTEGER INDEX BY VARCHAR2(60); hash_type;

FUNCTION Timer_Hash (p_set_name VARCHAR2, x_timer_ind IN OUT PLS_INTEGER) RETURN BOOLEAN IS BEGIN IF g_timer_hash.EXISTS(p_set_name) THEN x_timer_ind := g_timer_hash(p_set_name); RETURN TRUE; ELSE x_timer_ind := x_timer_ind + 1; g_timer_hash(p_set_name) := x_timer_ind; RETURN FALSE; END IF; END Timer_Hash; FUNCTION Timer_Hash (p_set_name VARCHAR2) RETURN PLS_INTEGER IS BEGIN IF g_timer_hash.EXISTS(p_set_name) THEN RETURN g_timer_hash(p_set_name); ELSE RETURN 0; END IF; END Timer_Hash; PROCEDURE Clear_Log BEGIN IS

DELETE output_log WHERE id = g_id; END Clear_Log; PROCEDURE Write_Log (p_text VARCHAR2) IS PRAGMA AUTONOMOUS_TRANSACTION; BEGIN INSERT INTO output_log ( line_ind, line_text, id, creation_date ) VALUES ( output_log_s.NEXTVAL, p_text, g_id, SYSTIMESTAMP); COMMIT; END Write_Log; PROCEDURE Write_Big (p_text DEFAULT 0) AS i l_len l_padding l_text BEGIN VARCHAR2, p_level PLS_INTEGER DEFAULT 0, p_debug_level PLS_INTEGER

PLS_INTEGER; PLS_INTEGER; VARCHAR2(50); VARCHAR2(4000);

IF p_debug_level > g_debug_level THEN RETURN; END IF; FOR i IN 1..p_level LOOP IF i < 51 THEN l_padding := l_padding || ' '; ELSE l_padding := i || Substr (l_padding, 1, 49); END IF; END LOOP; l_text := l_padding || p_text; i := 1; l_len := Length(l_text); WHILE (i <= l_len) LOOP Write_Log (Substr(l_text, i, g_line_size)); i := i + g_line_size; END LOOP; END Write_Big; FUNCTION Get_Seconds (p_interval INTERVAL DAY TO SECOND) RETURN NUMBER IS BEGIN 110516815.doc Page 13 of 42

RETURN EXTRACT (SECOND FROM p_interval) + 60 * EXTRACT (MINUTE FROM p_interval) + 3600 * EXTRACT (HOUR FROM p_interval); END Get_Seconds; PROCEDURE Heading (p_head VARCHAR2) IS l_under VARCHAR2(200) := Substr (c_head_under, 1, Length (p_head)); BEGIN Write_Log(''); Write_Log(p_head); Write_Log(l_under); END Heading; END Utils; / SHOW ERROR GRANT EXECUTE ON Utils TO PUBLIC; CREATE OR REPLACE PUBLIC SYNONYM Utils FOR Utils;

Notes on Oracle (Standard Object Model) Implementation


Oracles object-relational model requires objects to be defined on the database and then allows them to be used programmatically and also to be included in database tables, if desired. Compared with nondatabase object-oriented languages, there are some significant limitations in Oracles model for objects, one of which is mentioned below (and see also the later section on Oracles object orientation). Instance Hash Arrays It is a serious limitation that hash arrays (associative arrays in Oracles terminology) cannot be instance variables. Hash arrays are of course an important construct in any modern programming language, and the timer set object requires one to ensure good performance in cases where a large number of timers are created. We have had to work around this limitation by putting the hash array in a package, and manually objectifying it by including the object construction timestamp as a prefix to the key. This is not foolproof although should work in most cases, but is one of the key reasons for the (later) second Oracle implementation that does not have the drawback.

Test Program (Employee Hierarchy)


I was interested to know how the performance of a tree listing using Oracles built in SQL constructs compares with what can be achieved using an efficient PL/SQL program (normally its faster to do things in SQL, but I thought this might be an exception). The PL/SQL program uses batch fetching into arrays, then recursion to traverse the tree in memory. In the example below, a 12-level employee hierarchy was created in Oracles standard HR schema with 3 employees per manager. The hierarchy is written to file using a separate package Test_Queries that implements buffered writing using Oracles UTL_File API. This package uses the timer set object to time the writing, and the main program has another timer set. This example illustrates, in particular: Code Excerpt
BEGIN Utils.g_id := 'T_Tree1'; Tree_Emp; g_level := -1; g_level_max := 0; Test_Queries.Open_File ('Tree'); List_Tree (l_emp_list.COUNT, l_emp_list(l_emp_list.COUNT).id); Test_Queries.Close_File; l_timer.Increment_Time ('Recursion for ' || l_emp_list(l_emp_list.COUNT).id || ' (' || g_level_max || ' levels)'); l_timer.Write_Times (TRUE); END; / 110516815.doc Page 14 of 42

Usage of timer set instances in multiple packages at once Detection of timing at too high a resolution

Example Output
With Inner Loop Timing
265720 parents and 797161 employees loaded Timer Set: File Writer, constructed at 30 Sep 2011 09:08:47, written at 09:08:56 ================================================================================ [Timer timed: Elapsed (per call): 0.02 (0.000021), CPU (per call): 0.03 (0.000030), calls: 1000, '***' denotes corrected line below] Timer Elapsed CPU Calls Ela/Call CPU/Call -----------------------------------------------------------Lines 5.63 5.28 1,263 0.00446 0.00418 (Other) 3.79 4.13 1 3.79100 4.13000 -----------------------------------------------------------Total 9.42 9.41 1,264 0.00745 0.00744 -----------------------------------------------------------Timer Set: Tree, constructed at 30 Sep 2011 09:08:25, written at 09:08:56 ========================================================================= [Timer timed: Elapsed (per call): 0.02 (0.000022), CPU (per call): 0.02 (0.000020), calls: 1000, '***' denotes corrected line below] Timer Elapsed CPU Calls Ela/Call CPU/Call ----------------------------------------------------------------------------------Open 0.03 0.03 1 0.02800 0.03000 First Fetch 0.44 0.44 1 0.44400 0.44000 Inner Loop 20.40 20.35 797,161 0.00003 0.00003 *** 2.86 4.41 797,161 0.00000 0.00001 Array Copying 0.00 0.02 16 0.00006 0.00125 Remaining fetching 0.81 0.82 16 0.05056 0.05125 Recursion for 1000 (12 levels) 9.50 9.48 1 9.49700 9.48000 (Other) 0.00 0.00 1 0.00100 0.00000 ----------------------------------------------------------------------------------Total 31.18 31.14 797,197 0.00004 0.00004 *** 13.64 15.20 797,197 0.00002 0.00002 -----------------------------------------------------------------------------------

Without Inner Loop Timing


265720 parents and 797161 employees loaded Timer Set: File Writer, constructed at 30 Sep 2011 08:52:32, written at 08:52:42 ================================================================================ [Timer timed: Elapsed (per call): 0.03 (0.000029), CPU (per call): 0.04 (0.000040), calls: 1000, '***' denotes corrected line below] Timer Elapsed CPU Calls Ela/Call CPU/Call -----------------------------------------------------------Lines 5.55 5.68 1,263 0.00440 0.00450 (Other) 3.87 3.71 1 3.86700 3.71000 -----------------------------------------------------------Total 9.42 9.39 1,264 0.00745 0.00743 -----------------------------------------------------------Timer Set: Tree, constructed at 30 Sep 2011 08:52:29, written at 08:52:42 ========================================================================= [Timer timed: Elapsed (per call): 0.03 (0.000033), CPU (per call): 0.03 (0.000030), calls: 1000, '***' denotes corrected line below] Timer Elapsed CPU Calls Ela/Call CPU/Call ----------------------------------------------------------------------------------Open 0.03 0.03 1 0.03000 0.03000 First Fetch 0.47 0.48 1 0.47000 0.48000 Array Copying 2.14 2.12 16 0.13375 0.13250 Remaining fetching 0.74 0.75 16 0.04625 0.04688 Recursion for 1000 (12 levels) 9.51 9.49 1 9.51200 9.49000 (Other) 0.00 0.00 1 0.00000 0.00000 ----------------------------------------------------------------------------------Total 12.89 12.87 36 0.35811 0.35750 -----------------------------------------------------------------------------------

Notes on Timing Results Inner Loop Timing The timing process itself takes some time, and its important to ensure this is negligible. The first box above illustrates this problem, and its detection by the timer set object. The main program has two stages, the batch fetching from the dataset, and copying into arrays, then the recursion. The first stage has two loops, an outer one to fetch the batches into a staging array, then an inner one that builds the main arrays. There is one iteration of the inner loop for each employee and each takes very little time individually although the total is significant. The *** highlights the problem and attempts to correct for it (although not very accurately). The second box gives the output when the inner loop timer is excluded.
110516815.doc Page 15 of 42

File Writer I wanted to exclude the file-writing time from the recursion, but this was made more difficult by the desire to use an existing buffered writing utility program, and, as the writing is buffered, timing each call would result in the timer time problem shown above. The problem is solved by having the utility do its own timing, only when actually writing, not buffering. We can deduce that the recursion section timing of 9.49s CPU can be reduced by 5.68s CPU for file writing, to give a net figure of 3.81s, assuming the time for pure buffering is small (it looks like about 0.1s).

110516815.doc

Page 16 of 42

Oracle Implementation (Zombie Object Model)


This second Oracle implementation uses PL/SQL records and packages, without Oracle objects. Reasons for such an implementation are given in the later section on Oracles object orientation.

Timer Set Package


Code
CREATE OR REPLACE PACKAGE Timer_Set AS FUNCTION Construct (p_timer_set_name VARCHAR2) RETURN PLS_INTEGER; PROCEDURE Init_Time (p_timer_set_ind PLS_INTEGER); PROCEDURE Destroy (p_timer_set_ind PLS_INTEGER); PROCEDURE Increment_Time (p_timer_set_ind PLS_INTEGER, p_timer_name VARCHAR2); PROCEDURE Get_Timer_Stats (p_timer_set_ind PLS_INTEGER, p_timer_name VARCHAR2, x_ela_secs OUT NUMBER, x_cpu_secs OUT NUMBER, x_calls OUT PLS_INTEGER); PROCEDURE Write_Times (p_timer_set_ind PLS_INTEGER); PROCEDURE Summary_Times; END Timer_Set; / SHOW ERROR CREATE OR REPLACE PACKAGE BODY Timer_Set AS /************************************************************************************************** Author: Date: Description: Brendan Furey 27 October 2011 Brendan's Code-Timing Object, as described at scribd.com/BrendanP, Oracle version. Type body, in Zombie scheme.

***************************************************************************************************/ TYPE timer_type IS RECORD ( name VARCHAR2(30), ela_interval INTERVAL DAY(1) TO SECOND, cpu_interval INTEGER, n_calls INTEGER); TYPE timer_list_type IS VARRAY(100) OF timer_type; TYPE timer_set_type IS RECORD ( timer_set_name VARCHAR2(30), start_time TIMESTAMP, prior_time TIMESTAMP, start_time_cpu INTEGER, prior_time_cpu INTEGER, timer_list timer_list_type); TYPE hash_type IS TABLE OF PLS_INTEGER INDEX BY VARCHAR2(30); TYPE timer_set_h_type IS RECORD (timer_set timer_set_type, timer_hash hash_type); TYPE timer_set_list_type IS TABLE OF timer_set_h_type; g_timer_set_list timer_set_list_type; FUNCTION Construct (p_timer_set_name VARCHAR2) RETURN PLS_INTEGER IS l_start_time TIMESTAMP := SYSTIMESTAMP; l_start_time_cpu PLS_INTEGER := DBMS_Utility.Get_CPU_Time; l_new_ind PLS_INTEGER; l_timer_set timer_set_type; l_timer_set_h timer_set_h_type; BEGIN l_timer_set.timer_set_name l_timer_set.start_time l_timer_set.prior_time l_timer_set.start_time_cpu l_timer_set.prior_time_cpu l_timer_set_h.timer_set := := := := := p_timer_set_name; l_start_time; l_start_time; l_start_time_cpu; l_start_time_cpu; := l_timer_set;

IF g_timer_set_list IS NULL THEN l_new_ind := 1; g_timer_set_list := timer_set_list_type (l_timer_set_h); ELSE l_new_ind := g_timer_set_list.LAST + 1; g_timer_set_list.EXTEND; g_timer_set_list (l_new_ind).timer_set := l_timer_set; END IF; -g_timer_set_list (l_new_ind).timer_set := l_timer_set; RETURN l_new_ind; 110516815.doc Page 17 of 42

END Construct; PROCEDURE Destroy (p_timer_set_ind PLS_INTEGER) IS BEGIN g_timer_set_list.DELETE (p_timer_set_ind); END Destroy; PROCEDURE Init_Time (p_timer_set_ind PLS_INTEGER) IS BEGIN g_timer_set_list (p_timer_set_ind).timer_set.prior_time := SYSTIMESTAMP; g_timer_set_list (p_timer_set_ind).timer_set.prior_time_cpu := DBMS_Utility.Get_CPU_Time; END Init_Time; PROCEDURE Increment_Time (p_timer_set_ind PLS_INTEGER, p_timer_name VARCHAR2) IS l_cpu_time l_systimestamp l_timer_ind l_timer l_timer_list l_timer_hash l_prior_time l_prior_time_cpu BEGIN l_timer.name := p_timer_name; l_timer.ela_interval := l_systimestamp - l_prior_time; l_timer.cpu_interval := l_cpu_time - l_prior_time_cpu; l_timer.n_calls := 1; IF l_timer_list IS NULL THEN l_timer_list := timer_list_type (l_timer); g_timer_set_list (p_timer_set_ind).timer_set.timer_list := l_timer_list; g_timer_set_list (p_timer_set_ind).timer_hash (p_timer_name) := 1; ELSE IF l_timer_hash.EXISTS (p_timer_name) THEN l_timer_ind := l_timer_hash (p_timer_name); g_timer_set_list (p_timer_set_ind).timer_set.timer_list (l_timer_ind).ela_interval := l_timer_list (l_timer_ind).ela_interval + l_systimestamp - l_prior_time; g_timer_set_list (p_timer_set_ind).timer_set.timer_list (l_timer_ind).cpu_interval := l_timer_list (l_timer_ind).cpu_interval + l_cpu_time - l_prior_time_cpu; g_timer_set_list (p_timer_set_ind).timer_set.timer_list (l_timer_ind).n_calls := l_timer_list (l_timer_ind).n_calls + 1; ELSE l_timer_ind := l_timer_list.COUNT + 1; g_timer_set_list (p_timer_set_ind).timer_set.timer_list.EXTEND; g_timer_set_list (p_timer_set_ind).timer_set.timer_list (l_timer_ind) := l_timer; g_timer_set_list (p_timer_set_ind).timer_hash (p_timer_name) := l_timer_ind; END IF; END IF; g_timer_set_list (p_timer_set_ind).timer_set.prior_time := l_systimestamp; g_timer_set_list (p_timer_set_ind).timer_set.prior_time_cpu := l_cpu_time; END Increment_Time; PROCEDURE Get_Timer_Stats (p_timer_set_ind PLS_INTEGER, p_timer_name VARCHAR2, x_ela_secs OUT NUMBER, x_cpu_secs OUT NUMBER, x_calls OUT PLS_INTEGER) IS l_timer_ind PLS_INTEGER; l_timer timer_type; BEGIN IF g_timer_set_list (p_timer_set_ind).timer_hash.EXISTS (p_timer_name) THEN l_timer_ind := g_timer_set_list (p_timer_set_ind).timer_hash (p_timer_name); l_timer := g_timer_set_list (p_timer_set_ind).timer_set.timer_list (l_timer_ind); x_ela_secs := Utils.Get_Seconds (l_timer.ela_interval); x_cpu_secs := 0.01*l_timer.cpu_interval; x_calls := l_timer.n_calls; 110516815.doc Page 18 of 42 INTEGER := DBMS_Utility.Get_CPU_Time; TIMESTAMP := SYSTIMESTAMP; PLS_INTEGER := 0; timer_type; timer_list_type := g_timer_set_list (p_timer_set_ind).timer_set.timer_list; hash_type := g_timer_set_list (p_timer_set_ind).timer_hash; TIMESTAMP := g_timer_set_list (p_timer_set_ind).timer_set.prior_time; PLS_INTEGER := g_timer_set_list (p_timer_set_ind).timer_set.prior_time_cpu;

ELSE RETURN; END IF; END Get_Timer_Stats; PROCEDURE Write_Header (p_type VARCHAR2, p_head_len PLS_INTEGER) IS BEGIN Utils.Write_Big (' '); Utils.Write_Big (RPad (p_type, p_head_len) || ' Elapsed Ela/Call CPU/Call'); END Write_Header;

CPU

Calls

PROCEDURE Write_Lines (p_head_len PLS_INTEGER) IS c_lines_1 CONSTANT VARCHAR2(1000) := RPad ('-', p_head_len, '-'); c_lines CONSTANT VARCHAR2(10) := '----------'; BEGIN Utils.Write_Big (c_lines_1 || ' ' || c_lines || ' ---' || c_lines || ' ---' || c_lines); END Write_Lines; FUNCTION Form_Time (p_time INTEGER, p_dp PLS_INTEGER DEFAULT 2) RETURN VARCHAR2 IS l_dp_zeros VARCHAR2(10) := Substr ('0000000000', 1, p_dp); BEGIN IF p_dp > 0 THEN l_dp_zeros := '.' || l_dp_zeros; END IF; RETURN ' ' || To_Char (p_time, '99,990' || l_dp_zeros); END Form_Time; FUNCTION Form_Calls (p_calls INTEGER) RETURN VARCHAR2 IS BEGIN RETURN ' ' || To_Char (p_calls, '999,999,990'); END Form_Calls; PROCEDURE Write_Time_Line (p_timer VARCHAR2, p_head_len PLS_INTEGER, p_ela NUMBER, p_cpu NUMBER, p_n_calls PLS_INTEGER, p_ela_self NUMBER DEFAULT 0, p_cpu_self NUMBER DEFAULT 0) IS BEGIN Utils.Write_Big (RPad (p_timer, p_head_len) || Form_Time (p_ela) || Form_Time (0.01*(p_cpu)) || Form_Calls (p_n_calls) || Form_Time (p_ela/p_n_calls, 5) || Form_Time (0.01*(p_cpu/p_n_calls), 5)); IF p_timer != '***' AND p_cpu/p_n_calls < 10 * p_cpu_self AND p_cpu > 100 THEN Write_Time_Line ('***', p_head_len, p_ela - p_n_calls*p_ela_self, p_cpu - p_n_calls*p_cpu_self, p_n_calls); END IF; END Write_Time_Line; PROCEDURE Write_Times (p_timer_set_ind PLS_INTEGER) IS c_self_timer_name CONSTANT VARCHAR2(10) := 'STN'; l_timer_list l_sum_ela l_sum_cpu l_ela_seconds l_head_len l_self_timer l_time_ela l_time_cpu l_n_calls l_n_calls_sum i BEGIN g_timer_set_list (p_timer_set_ind).timer_set.prior_time := g_timer_set_list (p_timer_set_ind).timer_set.start_time; g_timer_set_list (p_timer_set_ind).timer_set.prior_time_cpu := g_timer_set_list (p_timer_set_ind).timer_set.start_time_cpu; Increment_Time (p_timer_set_ind, 'Total'); l_timer_list := g_timer_set_list (p_timer_set_ind).timer_set.timer_list; Utils.Heading ('Timer Set: ' || g_timer_set_list (p_timer_set_ind).timer_set.timer_set_name || ', Constructucted at ' || To_Char (g_timer_set_list (p_timer_set_ind).timer_set.start_time, 110516815.doc Page 19 of 42 timer_list_type := g_timer_set_list (p_timer_set_ind).timer_set.timer_list; NUMBER := 0; NUMBER := 0; NUMBER; PLS_INTEGER; PLS_INTEGER; NUMBER := 0; NUMBER := 0; PLS_INTEGER := 0; PLS_INTEGER := 0; PLS_INTEGER := 0; ' || c_lines || ' ' || '--' || c_lines || '

Utils.c_datetime_fmt) || ', written at ' || To_Char (SYSDATE, Utils.c_time_fmt)); l_head_len := 7; FOR i IN 1..l_timer_list.COUNT LOOP IF Length (l_timer_list(i).name) > l_head_len THEN l_head_len := Length (l_timer_list(i).name); END IF; END LOOP; l_self_timer := Construct ('Self'); FOR i IN 1..1000 LOOP Increment_time (l_self_timer, c_self_timer_name); END LOOP; Get_Timer_Stats (p_timer_set_ind => l_self_timer, p_timer_name => c_self_timer_name, x_ela_secs => l_time_ela, x_cpu_secs => l_time_cpu, x_calls => l_n_calls); Destroy (l_self_timer); Utils.Write_Big ('[Timer timed: Elapsed (per call): ' || LTrim (Form_Time (l_time_ela)) || ' (' || LTrim (Form_Time (l_time_ela/l_n_calls, 6)) || '), CPU (per call): ' || LTrim (Form_Time (l_time_cpu)) || ' (' || LTrim (Form_Time(l_time_cpu/l_n_calls, 6)) || '), calls: ' || l_n_calls || ', ''***'' denotes corrected line below]'); l_time_ela := l_time_ela/l_n_calls; l_time_cpu := 100*l_time_cpu/l_n_calls; -- Get_Timer_Stats converts to seconds, Write_Time_Line assumes csecs Write_Header ('Timer', l_head_len); Write_Lines (l_head_len); FOR i IN 1..l_timer_list.COUNT LOOP l_ela_seconds := Utils.Get_Seconds (l_timer_list(i).ela_interval); l_sum_ela := l_sum_ela + l_ela_seconds; l_sum_cpu := l_sum_cpu + l_timer_list(i).cpu_interval; l_n_calls := l_timer_list(i).n_calls; l_n_calls_sum := l_n_calls_sum + l_n_calls; IF i = l_timer_list.COUNT THEN Write_Time_Line ('(Other)', l_head_len, 2*l_ela_seconds - l_sum_ela, 2*l_timer_list(i).cpu_interval - l_sum_cpu, 1); Write_Lines (l_head_len); l_n_calls := l_n_calls_sum; END IF; Write_Time_Line (l_timer_list(i).name, l_head_len, l_ela_seconds, l_timer_list(i).cpu_interval, l_n_calls, l_time_ela, l_time_cpu); END LOOP; Write_Lines (l_head_len); END Write_Times; PROCEDURE Summary_Times IS l_head_len PLS_INTEGER; l_timer timer_type; PROCEDURE Loop_Sets (p_sizing BOOLEAN DEFAULT FALSE) IS i PLS_INTEGER; l_timer_set_name VARCHAR2(30); BEGIN i := g_timer_set_list.FIRST; WHILE i IS NOT NULL LOOP l_timer_set_name := g_timer_set_list(i).timer_set.timer_set_name; IF g_timer_set_list(i).timer_set.timer_set_name IS NOT NULL THEN IF p_sizing THEN IF Length (l_timer_set_name) > l_head_len THEN l_head_len := Length (l_timer_set_name); END IF; ELSE l_timer := g_timer_set_list(i).timer_set.timer_list (g_timer_set_list(i).timer_set.timer_list.COUNT); Write_Time_Line (l_timer_set_name, l_head_len, Utils.Get_Seconds (l_timer.ela_interval), l_timer.cpu_interval, l_timer.n_calls); END IF; END IF; 110516815.doc Page 20 of 42

i := g_timer_set_list.NEXT (i); END LOOP; END Loop_Sets; BEGIN IF g_timer_set_list IS NULL THEN RETURN; END IF; l_head_len := 9; Loop_Sets (TRUE); Utils.Heading ('Timer Set Summary'); Write_Header ('Timer Set', l_head_len); Write_Lines (l_head_len); Loop_Sets; g_timer_set_list.DELETE;-- seem to need both g_timer_set_list := NULL; END Summary_Times; END Timer_Set; / l SHOW ERROR GRANT EXECUTE ON Timer_Set TO PUBLIC; CREATE OR REPLACE PUBLIC SYNONYM Timer_Set FOR Timer_Set;

Notes on Oracle (Zombie Object Model) Implementation Package Record Array All the code in this implementation is stored in packages, and the objects are now elements of a nested table array of record type, which are managed by package procedures that pass the array index as object identifier. This is further explained in a later section. Summary_Times (Class Method) Using the second scheme (as described in a later section) of package zombie objects allows a procedure to summarise the timer set objects at the session level, which might be implemented as a class method in other languages. Oracle Collection Types This implementation uses all three of Oracles collection types: Varying array (varray) used to store the timers within the timer set object, and allows the results to be easily reported in order of timer construction (which is why we dont use only an associative array) Associative array used to store the indexes in the above array of the timers by name, avoiding performance problems for large sets Nested table used to store the list of object instances, and allows objects to be deleted at any element

Example Output
50000 rows loaded 50000 rows loaded 50000 rows loaded 50000 rows loaded 50000 rows loaded 50000 rows loaded 50000 rows loaded 50000 rows loaded 50000 rows loaded 50000 rows loaded 50000 rows loaded 50000 rows loaded 50000 rows loaded 50000 rows loaded 50000 rows loaded 47161 rows loaded 265720 parents and 797161 employees loaded 110516815.doc Page 21 of 42

Timer Set: File Writer, Constructucted at 17 Oct 2011 07:34:00, written at 07:34:09 =================================================================================== [Timer timed: Elapsed (per call): 0.03 (0.000026), CPU (per call): 0.02 (0.000020), calls: 1000, '***' denotes corrected line below] Timer ------Lines (Other) ------Total ------Elapsed ---------4.99 3.48 ---------8.46 ---------CPU ---------4.88 3.56 ---------8.44 ---------Calls -----------1,263 1 -----------1,264 -----------Ela/Call ------------0.00395 3.47500 ------------0.00670 ------------CPU/Call ------------0.00386 3.56000 ------------0.00668 -------------

Timer Set: Tree, Constructucted at 17 Oct 2011 07:33:57, written at 07:34:09 ============================================================================ [Timer timed: Elapsed (per call): 0.02 (0.000019), CPU (per call): 0.03 (0.000030), calls: 1000, '***' denotes corrected line below] Timer -----------------------------Open First Fetch Array Copying Remaining fetching Recursion for 1000 (12 levels) (Other) -----------------------------Total -----------------------------Timer Set Summary ================= Timer Set ----------Tree File Writer Elapsed ---------12.03 8.46 CPU ---------12.00 8.44 Calls -----------1 1 Ela/Call ------------12.03300 8.46400 CPU/Call ------------12.00000 8.44000 Elapsed ---------0.00 0.46 2.29 0.78 8.49 0.00 ---------12.03 ---------CPU ---------0.00 0.47 2.29 0.77 8.47 0.00 ---------12.00 ---------Calls -----------1 1 16 16 1 1 -----------36 -----------Ela/Call ------------0.00000 0.46100 0.14338 0.04900 8.49400 0.00000 ------------0.33425 ------------CPU/Call ------------0.00000 0.47000 0.14313 0.04813 8.47000 0.00000 ------------0.33333 -------------

110516815.doc

Page 22 of 42

Perl Implementation
Timer Set Object
Code
package TimerSet; # # Author: Brendan Furey # Date: 27 October 2011 # Description: Brendan's Code-Timing Object, as described at scribd.com/BrendanP, Perl version # my ($maxName, $selfEla, $selfUsr, $selfSys, $selfCalls); use strict; use warnings; use Time::HiRes qw( gettimeofday ); use Utils; my @timeList; sub new { my $setname = $_[1]; my $this = []; bless $this; &_getTimes; $this->[0] = {}; # Row 0 stores the hash of indexes for the timer names $this->[1] = [@timeList, @timeList, $setname]; # Row 1, first 3 are prior times; second 3 are start times; last is set name return $this; } sub initTime { my $this = shift; &_getTimes; for (my $i = 0; $i < 3; $i++) { $this->[1]->[$i] = $timeList[$i]; }

} sub incrementTime {

my ($this, $key) = @_; &_getTimes; my $ind; if (exists $this->[0]->{$key}){ $ind = $this->[0]->{$key}; } else { $ind = $#{$this} + 1; $this->[0]->{$key} = $ind; $this->[$ind]->[0] = $key; $this->[$ind]->[4] = 0; } for (my $i = 0; $i < 3; $i++) { $this->[$ind]->[$i+1] += $timeList[$i] - $this->[1]->[$i]; $this->[1]->[$i] = $timeList[$i]; } $this->[$ind]->[4] += 1; } sub getTimer { my ($this, $key) = @_; my $ind; if (exists $this->[0]->{$key}){ $ind = $this->[0]->{$key}; return ($this->[$ind]->[1], $this->[$ind]->[2], $this->[$ind]->[3], $this->[$ind]->[4]); } else { return (0, 0, 0, 0); }

} sub _getTimes { my ($user, $system, $cuser, $csystem) = times; @timeList = (scalar gettimeofday, $user + $cuser, $system + $csystem); } sub _formTime { my ($t, $dp) = @_; my $width = 8 + $dp; my $dpfm = '%'.$width.".$dp".'f'; # return sprintf " %11.3f", shift; return sprintf " $dpfm", $t; } 110516815.doc

Page 23 of 42

sub _formTimeTrim { my $trim = _formTime (@_); $trim =~ s/ //g; return $trim; } sub _formCalls { my $calls = shift; return sprintf " %10s", formInt($calls); } sub _formName { my ($name, $maxlen) = @_; return sprintf "%-$maxlen".'s', $name; } sub _writeLines { my $maxlen = shift; my $lines = '----------'; my $lines_n = substr '---------------------------------------------------------------------------------------------', 0, $maxlen; printf "%-s %s %s %s %s %s %s %s\n", $lines_n, $lines, $lines, $lines, $lines, $lines, $lines.'---', $lines.'---'; } sub _writeTimeLine { my ($timer, $ela, $usr, $sys, $calls) = @_; print &_formName ($timer, $maxName), &_formTime ($ela, 2), &_formTime ($usr + $sys, 2), &_formTime ($usr, 2), &_formTime ($sys, 2), &_formCalls ($calls), &_formTime ($ela/$calls, 5), &_formTime (($usr + $sys)/$calls, 5), "\n"; if ($timer ne "***" && ($usr + $sys)/$calls < 10 * ($selfUsr + $selfSys) && ($usr + $sys) > 0.1) { _writeTimeLine ("***", $ela - $calls*$selfEla, $usr - $calls*$selfUsr, $sys - $calls*$selfSys, $calls); } } sub writeTimes { my $this = shift; for (my $i=0; $i < 3; $i++) { $this->[1]->[$i] = $this->[1]->[$i+3]; } $this->incrementTime ('Totals'); $maxName = maxList (keys %{$this->[0]}); my $setName = "Timer Set: $this->[1]->[6]"; my $selfTimer = new TimerSet ('self'); for (my $i=0; $i < 10000; $i++) { $selfTimer->incrementTime ('x'); } ($selfEla, $selfUsr, $selfSys, $selfCalls) = $selfTimer->getTimer('x'); print "\n"; heading ("$setName, constructed at ".shortTime ($this->[1]->[3]).", written at ".substr (shortTime,

9));

print '[Timer timed: Elapsed (per call): ' . _formTimeTrim ($selfEla, 2) . ' (' . _formTimeTrim ($selfEla/$selfCalls, 6) . '), CPU (per call): ' . _formTimeTrim ($selfUsr + $selfSys, 2) . ' (' . _formTimeTrim(($selfUsr + $selfSys)/$selfCalls, 6) . '), calls: ' . $selfCalls . ", '***' denotes corrected line below]"; $selfEla /= $selfCalls; $selfUsr /= $selfCalls; $selfSys /= $selfCalls; print "\n\n", &_formName ('Timer', $maxName), sprintf (" %10s", 'Elapsed'), sprintf (" %10s", 'CPU'), sprintf (" %10s", '= User'), sprintf (" %10s", '+ System'), sprintf (" %10s", 'Calls'), sprintf (" %10s", 'Ela/Call'), sprintf (" %10s\n", 'CPU/Call'); _writeLines ($maxName); my @sumTime = (0, 0, 0, 0); for (my $i=2; $i < @$this; $i++) { my @curTime = ($this->[$i]->[1], $this->[$i]->[2], $this->[$i]->[3], $this->[$i]->[4]); for (my $j = 0; $j < 4; $j++) { $sumTime[$j] += $curTime[$j]; } if ($i == @$this - 1) { _writeTimeLine ('(Other)', 2*$curTime[0] - $sumTime[0], 2*$curTime[1] - $sumTime[1], 2*$curTime[2] - $sumTime[2], 1); _writeLines ($maxName); $curTime[3] = $sumTime[3]; } _writeTimeLine ($this->[$i]->[0], $curTime[0], $curTime[1], $curTime[2], $curTime[3]); } _writeLines ($maxName); 110516815.doc Page 24 of 42

} 1;

Notes on Perl Implementation Object Data Structure In Perl objects are references defined in a package, which for complex data structures are normally to an anonymous array or hash.

Utility Package
This package contains formatting and other utility subroutines. Code
package Utils; require Exporter; @ISA = qw(Exporter); @EXPORT = qw(formInt indent heading shortTime maxList now); # # Author: Brendan Furey # Date: 27 October 2011 # Description: Brendan's utility package, used in Code-Timing Object, as described at scribd.com/BrendanP, Perl version # use strict; use warnings; our $INDENT = 2; my $spaces = ' '; sub formInt { my $int = shift; my $str = sprintf ("%d", $int); $str =~ s/(\d{1,3}?)(?=(\d{3})+$)/$1,/g; # print "str2 = $str\n"; return $str; } sub indent { my ($str, $level, $maxlen) = @_; return sprintf ("%-$maxlen".'s', substr ($spaces, 0, $level * $INDENT).$str); } sub heading { my @str = @_; printf "%s\n", join (' ', @str); my $equals = join '|||', @str; $equals =~ s/[^|]/=/g; $equals =~ s/[|]/ /g; print "$equals\n"; } sub maxList { my $maxlen = 0; foreach (@_) { my $curlen = length ($_); $maxlen = $curlen if ($curlen > $maxlen); } return $maxlen; } sub shortTime { my $t = shift; my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst); if (defined $t) { ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime ($t); } else { ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime; } return sprintf "%02s/%02s/%02s %02s:%02s:%02s", $mday, ($mon+1), ($year-100), $hour, $min, $sec; } sub now { my $tm = localtime; return $tm; } 1;

Test Program (Directory Tree)


The test program writes out a file system directory tree, and comprises a driving program and an object, DirTree. The constructor method reads the entire tree into arrays, and methods are available to list subsets of the tree according to constraints on file name, size or modified date, and with ordering on one
110516815.doc Page 25 of 42

of these attributes. The design was intended to allow multiple listings to be produced with only one (relatively expensive) file system traversal. This example illustrates, in particular: Driver Code
use strict; use warnings; use TimerSet; use DirTree; my %srtType = ('name' => 0, 'date' => 1, 'size' => 2); my $dir = "C:/"; my ($name_str, $date_min, $date_max, $size_min, $size_max) = ('.', 10000, 0, -1, 100000000); my $timer = new TimerSet("Tree Driver"); my $tree = DirTree->new ($dir); $timer->incrementTime ("Contruct $dir"); $tree->listTree ($srtType{'size'}, $name_str, $date_min, $date_max, $size_min, $size_max, '(All)'); $timer->incrementTime ("Tree by size, all"); $tree->listTree ($srtType{'size'}, $name_str, $date_min, $date_max, 5000, $size_max, '(5 MB)'); $timer->incrementTime ("Tree by size, 5MB"); $timer->writeTimes; $tree->printTimer;

Use of the timer set object as an instance variable within another object Dynamic timer partitioning: The listTree methods last parameter is appended to the timer names within the DirTree object

110516815.doc

Page 26 of 42

Example Output
Tree constructed from root C:/ at Sun Oct 2 11:55:19 2011, having 54,168 dirs and 248,185 files, 25 levels, including 246 unreadable dirs Tree Listing from C:/, sorting by size DESC: Printing 49,646 of 54,168 dirs and 248,181 of 248,185 files ======================================================================================================== Parameters ========== Name String: . Date Range: 16/05/-16 11:55:19 to 02/10/11 11:55:19 Size Range: -1 to 100000000 ========== Name File Size Dir. Size Modified Created Accessed ================================ ========= ========== ============== ============== ============== (root) 8,211,591 87,040,217 01/01/-20 00:00:00 01/01/-20 00:00:00 01/01/-20 00:00:00 ------------hiberfil.sys 4,105,776 01/10/11 18:12:50 24/05/11 20:06:52 24/05/11 04:34:40 pagefile.sys 4,105,776 01/10/11 18:12:55 25/05/11 03:47:32 25/05/11 03:47:32 debug1214.txt 36 02/10/11 09:54:27 24/07/11 17:56:10 02/10/11 09:54:26 RHDSetup.log 2 24/05/11 04:10:09 24/05/11 04:09:56 24/05/11 04:09:56 Setup.log 0 24/07/11 17:38:57 24/05/11 04:09:56 24/07/11 17:38:48 ------------Download 1,744,873 16,402,773 24/07/11 19:32:13 24/07/11 19:31:21 24/07/11 19:32:13 ---------------------------wls1033_oepe111150_win32.exe 1,021,310 08/10/10 18:01:30 24/07/11 19:31:36 24/07/11 19:31:36 . continues . Tree Listing from C:/, sorting by size DESC: Printing 1,609 of 54,168 dirs and 2,401 of 248,185 files ===================================================================================================== Parameters ========== Name String: . Date Range: 16/05/-16 11:55:42 to 02/10/11 11:55:42 Size Range: 5000 to 100000000 ========== . continues . Timer Set: Tree Driver, constructed at 02/10/11 11:52:58, written at 11:55:45 ============================================================================= [Timer timed: Elapsed (per call): 0.14 (0.000014), CPU (per call): 0.14 (0.000014), calls: 10000, '***' denotes corrected line below] Timer ----------------Contruct C:/ Tree by size, all Tree by size, 5MB (Other) ----------------Totals ----------------110516815.doc Elapsed ---------141.38 22.39 3.45 0.00 ---------167.22 ---------CPU ---------116.86 22.17 3.43 0.00 ---------142.46 ---------= User ---------30.56 21.61 3.42 0.00 ---------55.58 ---------+ System ---------86.30 0.56 0.02 0.00 ---------86.88 ---------Calls ---------1 1 1 1 ---------4 ---------. Ela/Call ------------141.38444 22.38828 3.44557 0.00003 ------------41.80458 ------------CPU/Call ------------116.86100 22.16700 3.43200 0.00000 ------------35.61500 -------------

Page 27 of 42

Timer Set: Tree - C:/, constructed at 02/10/11 11:52:58, written at 11:55:45 ============================================================================ [Timer timed: Elapsed (per call): 0.14 (0.000014), CPU (per call): 0.16 (0.000016), calls: 10000, '***' denotes corrected line below] Timer ------------------------Contructor Pre Tree (All) Headings (All) Directory Printing (All) *** File Sort (All) File Printing (All) *** Directory Sort (All) *** Pre Tree (5 MB) Headings (5 MB) Directory Printing (5 MB) File Sort (5 MB) File Printing (5 MB) Directory Sort (5 MB) *** (Other) ------------------------Totals ------------------------Elapsed ---------141.38 2.26 0.00 3.00 2.30 11.93 2.44 1.73 1.00 0.26 1.79 0.00 0.12 0.54 0.06 0.41 0.16 2.42 ---------167.36 ---------CPU ---------116.86 2.23 0.00 3.02 2.24 11.85 2.15 1.38 0.95 0.14 1.78 0.00 0.09 0.59 0.02 0.47 0.18 2.60 ---------142.60 ---------= User ---------30.56 2.23 0.00 2.89 2.12 11.77 1.82 1.05 0.95 0.14 1.78 0.00 0.09 0.59 0.02 0.45 0.17 2.57 ---------55.72 ---------+ System ---------86.30 0.00 0.00 0.12 0.12 0.08 0.33 0.33 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.02 0.02 0.03 ---------86.88 ---------Calls ---------1 1 1 49,646 49,646 49,646 49,646 49,646 51,831 51,831 1 1 1,609 1,609 1,609 17,999 17,999 1 ---------223,601 ---------Ela/Call ------------141.38443 2.26308 0.00046 0.00006 0.00005 0.00024 0.00005 0.00003 0.00002 0.00001 1.79230 0.00040 0.00007 0.00033 0.00004 0.00002 0.00001 2.42352 ------------0.00075 ------------CPU/Call ------------116.86100 2.23100 0.00000 0.00006 0.00005 0.00024 0.00004 0.00003 0.00002 0.00000 1.77800 0.00000 0.00006 0.00037 0.00001 0.00003 0.00001 2.59800 ------------0.00064 -------------

110516815.doc

Page 28 of 42

Notes on Timing Results Dynamic timer partitioning The internal section timers within DirTree are partitioned by the parameter passed in (All) and (5 MB). Directory Tree Algorithm As expected, the constructor method takes most of the time used (82% of CPU time) because the subsequent method calls involve only array processing. The next highest proportion, of 8%, is taken by a section including the sorting of files (in an array) for the first listTree call that prints almost all files. Notice that the same section on the second call, listing only files above 5MB in size, takes only 0.4%. This is because sorting is applied only after the filtering process, when the numbers of files are greatly reduced on the second call. Filtering occurs within a first recursion process (labelled Pre Tree above) without sorting, while a second recursion to do the printing sorts the sibling files and directories. The constructor method neither sorts nor filters. A Fresh Look at Efficient Perl Sorting is a very interesting article on Perl sorting.

110516815.doc

Page 29 of 42

Java Implementation
Timer Set Object
Code
package TimerSet; /** Author: Brendan Furey Date: 27 October 2011 Description: Brendan's Code-Timing Object, as described at scribd.com/BrendanP, Java version **/ import java.lang.management.ManagementFactory; import java.lang.management.ThreadMXBean; import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.Date; import java.util.HashMap; import Utility.Utils; public class TimerSet { private long[] startTime = new long[3], priorTime = new long[3], curTime = new long[4]; private int nTimes = 0; private int maxName; private String setName; private static long selfEla, selfUsr, selfSys; TimerType[] timerTypeList = new TimerType[100]; HashMap<String, Integer> timerNames = new HashMap<String, Integer>(); private static NumberFormat formatter = new DecimalFormat("###,###,###"); private Date ctime = new Date(); public TimerSet(String setName) { this.setName = "Timer Set: "+setName;; maxName = 7; initTime(); for (int i = 0; i < 3; i++) { startTime[i] = priorTime[i]; } } public void incrementTime(String timerName) { int timerInd; if (timerNames.containsKey(timerName)) { timerInd = timerNames.get(timerName); } else { timerNames.put(timerName, nTimes); timerTypeList[nTimes] = new TimerType(timerName); timerInd = nTimes++; } timerTypeList[timerInd].incrementTime(); } public void initTime() { getTimes(); for (int i = 0; i < 3; i++) { priorTime[i] = curTime[i]; } } private void getTimes() { curTime[0] = System.nanoTime(); ThreadMXBean bean = ManagementFactory.getThreadMXBean(); curTime[1] = bean.getCurrentThreadUserTime(); curTime[2] = bean.getCurrentThreadCpuTime() - curTime[1]; } private static String formTime (long time, int dp) { int width = 8 + dp; String dpfm = String.format( "%s.%sf", width, dp); return String.format( " %"+dpfm, (float) time / 1000000000); } private static String formTimeTrim (long time, int dp) { return new String (formTime (time, dp).replace (" ", "")); } private static String formName (String name, int maxName) { return String.format( "%-"+maxName+"s", name); } private static String formCalls (long nCalls) { return String.format("%1$13s", formatter.format (nCalls)) ; 110516815.doc Page 30 of 42

} private static void writeLines (int maxName) { String lines = "----------"; String lines_n = "---------------------------------------------------------------------------------------------"; System.out.println(String.format( "%s %s %s %s %s %s %s %s", lines_n.substring(0, maxName), lines, lines, lines, lines, lines, lines+"---", lines+"---")); } private static void writeTimeLine (String timer, long ela, long usr, long sys, long nCalls, int maxName) { System.out.println(formName (timer, maxName)+ formTime (ela, 2)+ formTime (usr + sys, 2)+ formTime (usr, 2)+ formTime (sys, 2)+ formCalls (nCalls)+ formTime (ela/nCalls, 5)+ formTime ((usr + sys)/nCalls, 5) ); if (timer != "***" && (usr + sys)/nCalls < 10 * (selfUsr + selfSys) && (usr + sys) > 1000000000) { writeTimeLine ("***", ela - nCalls*selfEla, usr - nCalls*selfUsr, sys - nCalls*selfSys, nCalls, maxName); } } public long[] getTimer (String timerName) { int timerInd; if (timerNames.containsKey(timerName)) { timerInd = timerNames.get(timerName); } else { return new long[] {0, 0, 0, 0}; } return new long[] {timerTypeList[0].getInterval(0), timerTypeList[0].getInterval(1), timerTypeList[0].getInterval(2), timerTypeList[0].getInterval(3)}; } public void writeTimes() { long sumTime[] = {0, 0, 0, 0}; long selfCPUPer; Date wtime = new Date(); for (int i = 0; i < 3; i++) { priorTime[i] = startTime[i]; } incrementTime("Totals"); TimerSet selfTimer = new TimerSet("self"); for (int i=0; i < 10000; i++) { selfTimer.incrementTime ("x"); } curTime = selfTimer.getTimer ("x"); selfEla = curTime[0]; selfUsr = curTime[1]; selfSys = curTime[2]; Utils.Heading (setName+", constructed at "+ctime.toString()+", written at "+wtime.toString().substring(11, 19)); System.out.println ( "[Timer timed: Elapsed (per call): " + formTimeTrim (selfEla, 2) + " (" + formTimeTrim (selfEla/curTime[3], 6) + "), CPU (per call): " + formTimeTrim ((selfUsr + selfSys), 2) + " (" + formTimeTrim((selfUsr + selfSys)/curTime[3], 6) + "), calls: " + curTime[3] + ", '***' denotes corrected line below]"); System.out.println('\n'+ formName("Timer", maxName)+String.format( " %10s %10s %10s %10s %10s %10s %10s", "Elapsed", "CPU", "= User", "+ System", "Calls", "Ela/Call", "CPU/Call")); writeLines(maxName); for (int i = 0; i < nTimes; i++) { for (int j = 0; j < 4; j++) { curTime[j] = timerTypeList[i].getInterval(j); sumTime[j] += curTime[j]; } if (i == nTimes - 1) { writeTimeLine ("(Other)", 2*curTime[0]-sumTime[0], 2*curTime[1]-sumTime[1], 2*curTime[2]sumTime[2], 1, maxName); writeLines(maxName); curTime[3] = sumTime[3]; } writeTimeLine (timerTypeList[i].getName(), curTime[0], curTime[1], curTime[2], curTime[3], maxName); } writeLines(maxName); } private class TimerType { 110516815.doc Page 31 of 42

String name; long interval[] = {0, 0, 0, 0}; private TimerType(String name) { this.name = name; if (name.length() > maxName) { maxName = name.length(); } } private void incrementTime() { getTimes(); for (int i = 0; i < 3; i++) { interval[i] += curTime[i] - priorTime[i]; priorTime[i] = curTime[i]; } interval[3]++; } private String getName() { return name; } private long getInterval(int i) { return interval[i]; } } }

Notes on Java Implementation Inner Classes In Java one can nest a class within another class and have it accessible only to that class, and this feature has been used here as the individual timers are not intended for direct external use.

Utility Package
Code
package Utility; public class Utils { private static String underline = "===================================================================================================="; public Utils() { } public static void Heading (String title) { System.out.println (""); System.out.println (title); System.out.println (underline.substring(0, title.length())); } }

Test Driver Program (Web Service Proxy)


I wrote an Oracle package some time ago to allow web service calls to be made from Oracle PL/SQL programs without the calling programs having to do any complex HTTP or XML processing, and wanted to compare with processing via a Java web service proxy. The test program consists of a Java class with main method that calls methods from a proxy generated by Oracle JDeveloper and deployed to a JAR file. The proxy was generated for an external web service (Public Web Service functions for Visual DataFlex football pool) that returns information about the 2010 football world cup, and the test program uses the operation returning all game information. After calling the allGames proxy method, the program loops over the 64 games printing the description and executing an inner loop to print goal details for each game. This example illustrates, in particular: Code
package TestWS; import Foot.proxy.*; import TimerSet.TimerSet; public class Caller { 110516815.doc Page 32 of 42

Using two timer sets to time at different levels of detail A situation, web service calls, where both elapsed time and CPU times are important, as the external processing will not register under CPU

private Foot.proxy.InfoSoapType _port; private static Info info; private static TimerSet timerSet = new TimerSet("World Cup 2010"); private static TimerSet timerSetTop = new TimerSet("World Cup 2010 - Top"); public static void main(String[] args) { try { InfoSoapClient infoSoapClient = new InfoSoapClient(); timerSet.incrementTime("infoSoapType"); TGameInfo[] tGameInfoAll = infoSoapClient.allGames(); TGameInfo tGameInfo; timerSet.incrementTime("allGames"); timerSetTop.incrementTime("Initialising"); System.out.println("Iterating through ArrayList elements..."); for (int i = 0; i < tGameInfoAll.length; i++) { tGameInfo = tGameInfoAll[i]; System.out.println(i+": Description: "+tGameInfo.getSDescription()); timerSet.incrementTime("Iterating, outer"); for (int j = 0; j < tGameInfo.getGoals().length; j++) { TGoal tGoal = tGameInfo.getGoals()[j]; System.out.println(String.format ("%30s", "Goal @ ") + tGoal.getIMinute() + " minutes, by " + tGoal.getSPlayerName()); } timerSet.incrementTime("Iteration, inner"); } timerSetTop.incrementTime("Games"); timerSet.writeTimes(); timerSetTop.writeTimes(); } catch (Exception e) { throw e; // TODO } // catch } // main } // class

110516815.doc

Page 33 of 42

Example Output
Iterating through ArrayList 0: Description: Round 1 Goal Goal . . continues . 63: Description: Final Goal elements... @ 79 minutes, by Rafael Mrquez @ 55 minutes, by Siphiwe Tshabalala

@ 116 minutes, by Andrs Iniesta

Timer Set: World Cup 2010, constructed at Sun Oct 02 12:46:43 GMT 2011, written at 12:46:50 =========================================================================================== [Timer timed: Elapsed (per call): 0.03 (0.000003), CPU (per call): 0.03 (0.000003), calls: 10000, '***' denotes corrected line below] Timer ---------------infoSoapType allGames Iterating, outer Iteration, inner (Other) ---------------Totals ---------------Elapsed ---------0.48 6.32 0.01 0.05 0.00 ---------6.86 ---------CPU ---------0.41 0.51 0.00 0.03 0.00 ---------0.95 ---------= User ---------0.34 0.50 0.00 0.03 0.00 ---------0.87 ---------+ System ---------0.06 0.02 0.00 0.00 0.00 ---------0.08 ---------Calls ---------1 1 64 64 1 ---------131 ---------Ela/Call ------------0.47614 6.31736 0.00019 0.00081 0.00004 ------------0.05235 ------------CPU/Call ------------0.40560 0.51480 0.00000 0.00049 0.00000 ------------0.00726 -------------

Timer Set: World Cup 2010 - Top, constructed at Sun Oct 02 12:46:43 GMT 2011, written at 12:46:50 ================================================================================================= [Timer timed: Elapsed (per call): 0.02 (0.000002), CPU (per call): 0.02 (0.000002), calls: 10000, '***' denotes corrected line below] Timer -----------Initialising Games (Other) -----------Totals -----------Process exited Elapsed CPU ------------------6.79 0.92 0.06 0.03 0.05 0.05 ------------------6.90 1.00 ------------------with exit code 0. = User ---------0.84 0.03 0.03 ---------0.90 ---------+ System ---------0.08 0.00 0.02 ---------0.09 ---------Calls ---------1 1 1 ---------3 ---------Ela/Call ------------6.79091 0.06379 0.05024 ------------2.30164 ------------CPU/Call ------------0.92041 0.03120 0.04680 ------------0.33280 -------------

110516815.doc

Page 34 of 42

Notes on Timing Results Internal/External Times The results show that almost all the time occurred in the two calls to the proxy, the first (infoSoapType) to initialise the proxy, and the second (allGames) to make the web service call for the chosen operation. It can be seen that the web service call takes about 5.8 elapsed seconds of external time (i.e. waiting to get the response after sending the request), and 0.5 seconds of internal CPU time to make the request and process the response. The initialisation takes about 0.4 seconds of internal CPU time. The total internal processing time of 0.92 CPU seconds may seem quite high.

110516815.doc

Page 35 of 42

Oracle Object Orientation


Oracle added object-oriented features in version 8, and its treatment differs significantly from that of other object-oriented languages such as Perl and Java. This section discusses some of the features of the Oracle implementation.

Object-Relational Model and String Theory


In non-database languages, including Perl and Java, objects are purely in-memory structures, although they can be persisted to an external database, such as Oracle. This is usually performed by converting to a relational structure, but, since version 8, Oracle has object-relational features that allow object storage within tables. Although storing data object-relationally may reduce the need for conversion in a given instance, it has the drawback that querying the data becomes more difficult, requiring unwrapping of the object data that have been curled up rather like the hidden dimensions in modern theories of space-time (Imagining Other Dimensions). Performance is also likely to be reduced in general purpose querying, to which the relational model is ideally suited. Its also possible to have an object-oriented interface to a relational data model using Oracles object views (Oracle Database Application Developers Guide Object-Relational Features 10g Release 2 (10.2)). Relational data storage is therefore generally preferred, although there will be exceptional cases where object-relational is preferred (data subsetting may be one such case).

Objects and Packages


Code in Oracles PL/SQL programming language can be stored in various entities, including database triggers, stand-alone procedures, and anonymous blocks in script files. However, it is usually considered best practice to organise most code in production systems using packages having procedures that can be called from other database entities, scripts and external systems (Oracle Database PL/SQL User's Guide and Reference 10g Release 2 (10.2)). Packages are stored on the database and consist of separate specification and optional body components. Package variables defined outside of procedures (or functions) persist between calls for the length of the database session, but, unlike object variables, exist in only one instance, unless defined as an element of an array. Package code can reference objects and object bodies can reference packages. Object Type Header and Body Object types (Oracles term for classes), like packages, must be defined on the database with a type header and optional type body. The type body, if present, contains the methods for the object. Oracle supplies an implicit default constructor for types that may be overridden by a body constructor method. The type header specifies all instance variables and any applicable methods, and variables can be of any database type including (other) object types and collection types, but not of PL/SQL-only record or associative array types. Object type header, by allowing nesting of other object types and collection types, provide the capability to refer to data structures of arbitrary complexity through a single variable. Since their introduction in version 8, object type headers have become very widely used, but object type bodies are very rarely used. Oracle Object Limitations Oracle objects have the following limitations, compared with other languages or, in some cases, PL/SQL packages: Private variables do not exist: all instance variables are public Static methods are available, but not static variables: Character fields are limited to the SQL limit of 4000 in length, compared with the PL/SQL limit of 32,767 Associative array types (Oracles term for hashes) are PL/SQL only and thus cannot be used for object instance variables

110516815.doc

Page 36 of 42

The Zombie Object Model


Advantages of Object Orientation Object orientation is generally regarded as implying a bundle of features concerning both data structures and methods. For example, James Gosling and Henry McGilton assert, in their well-known white paper on Java (The Java Language Environment) (my formatting): To be truly considered "object oriented", a programming language should support at a minimum four characteristics: Encapsulation--implements information hiding and modularity (abstraction) Polymorphism--the same message sent to different objects results in behavior that's dependent on the nature of the object receiving the message Inheritance--you define new classes and behavior based on existing classes to obtain code re-use and code organization Dynamic binding--objects could come from anywhere, possibly across the network. You need to be able to send messages to objects without having to know their specific type at the time you write your code. Dynamic binding provides maximum flexibility while a program is executing

They go on to motivate object orientation by its direct correspondence with the real world, objects having state and behavior': The espresso machine can be modelled as an object. It has state (water temperature, amount of coffee in the hopper) and it has behavior (emits steam, makes noise, and brews a perfect cup of java). This is convincing, but it is striking how little it has to do with the extra features that they deem essential to object orientation, such as polymorphism and inheritance. If we were to try to unbundle the more important features, we might describe them as follows: Data encapsulation: A complex data structure can be maintained outside the calling program and persists between calls (i.e. constitutes a state) Operation encapsulation: Operations on the data structure are performed by calls to methods in a module outside the calling program

These features provide the modular approach that reduces code duplication, combined with the specific object benefit whereby the data structure can be created once, in possibly multiple instances, and then later operated on in various ways by method calls. In our timer set object, the timing functionality is provided with minimum footprint on the caller and allows multiple code components to use it without interfering with each other. In the Perl directory tree object the separation between construction call and later operations provides better performance than would a non-object implementation. Other features within the object-oriented bundle, such as method inheritance and polymorphism, were not necessary in the objects described here. Object Orientation without Objects Notice that, although data and methods are usually encapsulated in the same object entity, there is no fundamental reason why the methods cant be encapsulated separately, for example within a package in Oracle. One could obtain the advantages of data encapsulation using the object type specification, but encapsulate the methods within a package. In this way, the object would have no bodily functions itself, but would be controlled externally (whence Zombie). However, it may be better then to omit objects altogether since Oracle provides PL/SQL data structures called records that allow similar encapsulation of complex data structures. [For another example of an otherwise dull work jazzed up by the addition of zombies, see Pride and Prejudice and Zombies Book Trailer, an adaptation of a book written, incidentally, by the inventor of 'chicklit']. The advantages of this approach would be that Oracles object limitations would be bypassed, and furthermore, code would not be split between objects and packages but remain in one entity. In the white paper previously mentioned (The Java Language Environment), in a section No More Functions, the authors say in relation to Java: It's not to say that functions and procedures are inherently wrong. But given classes and methods, we're now down to only one way to express a given task. By eliminating functions, your job as a programmer is immensely simplified: you work only with classes and their methods. While perhaps a little overstated, one might apply the converse in relation to Oracle, where packages are central. There are two possible implementation schemes.
110516815.doc Page 37 of 42

Client Zombie Objects Here the required record type is exposed in the package specification, so that the client program can define its own instances. The package then provides a constructor that returns the record, and other methods that pass the record as an input/output parameter, normally by reference. Here the package has no knowledge of object instances other than during method calls. Package Zombie Objects Here the required record type is maintained in the package body, along with an array of instances of the record, probably using Oracles nested table structure to allow deletion of elements. The package then provides a constructor that initialises an instance stored in a new element of the array, and returns the array index. The other methods now take the instance index as an input parameter. This second scheme is marginally more complex, but allows for package procedures that report on the objects globally (i.e. that have been created within the session). The timer set object has been implemented using this scheme, as described above, as well as using the standard object model. The hash array in this package zombie object model is simply a part of the object record, with no work-around necessary. A procedure is included to give a session summary of the current timer sets.

110516815.doc

Page 38 of 42

Comparative Notes
Having implemented the same functionality in Oracle, Perl and Java, it seems worth noting a few of the differences between the languages, not at all exhaustively.

Feature Comparison Table


Feature Oracle Three collection types are available in PL/SQL, being two variations of standard arrays and a hash array type. Complex data structures including nested arrays are implemented using nesting of types. Types may be both native types such as the array types, or named user-defined types. Record and object types provide for a set of named fields of specified types. Oracle packages are code modules, that group together related procedures, and provide session-persistent variables. They are split into header and body sections, and can be used to implement some of the key features of object-orientation as shown here. Object oriented features are provided explicitly through object type headers and bodies. However, these have significant limitations in Oracle and key features of object orientation can also be provided through packages with either record types or object type headers without bodies. Perl Perl has 1-dimensional standard arrays and hash arrays. Complex data structures including nested arrays are implemented using array references in the higher level array. Record types with field names do not exist, arrays being used instead (hash arrays provide name-value pairs). Java Core Java includes a 1dimensional array type that can be applied to any native type or class. Complex data structures including nested arrays are implemented using class nesting. The (named) attributes in a class correspond roughly to Oracles record or object types. Variations on the basic array type, including for hash arrays, are provided by classes available in Java libraries.

Collections

Packages

Perl packages are essentially namespaces, and usually correspond to files.

Java packages are directories where class files are stored.

Object Orientation

Objects in Perl are simply references to arrays within packages that have been blessed into objects of the package type. As in Oracle, object orientation is optional, and can be used only when useful. Subroutines may be passed a single input array, which, as mentioned, always consists of scalar variables, passed by reference but which may include array or scalar references. Often the input array is copied into local variables that dont affect the underlying input references. A return value may be specified as a scalar or array of scalars (again possibly including references), otherwise the value of the last expression evaluated is returned. Perl subroutines may be defined within other subroutines but are not in fact local to that subroutine, but accessible throughout the relevant package. This results in rather bizarre behaviour in relation to the inner subroutines access to

Java was designed around the concept of object orientation, and all code is organised into object classes. However, class methods and variables do not require object instantiation.

Parameters

Procedures may have a set of named parameters of any type, which can be input, output or both and can be passed by value or reference. Functions return a value of specified type.

Java methods may take a set of named input parameters of any type, passed by value, but object types are essentially references and so any methods called act on the underlying objects. Methods may be declared void, or return a value of specified type.

Procedure Nesting

Oracle procedures may have nested procedures that are only callable within their nesting structure, and which can access variables defined at higher levels.

Java classes may be nested within other classes (called inner classes, and used in our timer set class) and are then only callable within the outer class.

110516815.doc

Page 39 of 42

Scoping

Separation of packages into headers and bodies provides for public and private procedures and variables. Objects, as noted earlier, cannot have private instance attributes. Increment operators allow succinct application of arithmetic operators incrementally. Oracle unfortunately lacks this feature that most modern languages have, so one must write, for example: x := x + y; x := x + 1; Largest Standard Object (including the hash code): - Lines: 259 - Statements: 192 - Words: 1,139 - Characters: 12,563 Zombie Object (excluding session summary code): - Lines: 201 - Statements: 152 - Words: 940 - Characters: 10,535 PL/SQL combines the datamanipulating power of SQL with the processing power of procedural languages. - Oracle Database PL/SQL User's Guide and Reference 10g Release 2 (10.2)

higher level (but not global, lexical) variables: On first entry to the inner subroutine the variables are shared with the outer subroutines, but on first exit, subsequent accesses are no longer shared. This can be avoided in a number of ways, including the use of globals (although of course thats not always a good idea), or accessing the inner subroutine via reference. Perl does not have explicitly private and public subroutines, but there is a convention to prefix subroutines not intended to be called directly with an underscore. We have followed this convention.

The private and public keywords define what can be accessed outside a class.

Increment Operator

Perl has increment operators: $x += $y; $x++;

Java has increment operators that work similarly to Perl.

Code Size (Here various measures are taken of the timer set object code, omitting blank and comment lines)

Smallest - Lines: - Statements: - Words: - Characters:

131 92 560 4,965

Middle -

Lines: Statements: Words: Characters:

153 95 656 6,614

In their own words

The most important principle of language design is simply that easy things should be easy, and hard things should be possible - Programming Perl (a nicely written manual with a lot of good material on programming in general)

Java is: Simple--the number of language constructs you need to understand to get your job done is minimal. Familiar--Java looks like C and C++ while discarding the overwhelming complexities of those languages. - The Java Language Environment

110516815.doc

Page 40 of 42

Conclusions
A generic design for a code timing object in any programming language has been presented Implementations of the design have been given, and usage demonstrated, for Oracle, Perl and Java A general design approach for complex data structures, involving diagram and tabulation, has been illustrated by application to this object class Oracle's object-orientation features have been discussed, and it has been suggested that a 'zombie' object model that uses record types rendered 'undead' by packages, may often be superior to the standard object model The Oracle object has been implemented using both approaches, allowing readers to judge for themselves A few notes have been included on differences between the three languages

110516815.doc

Page 41 of 42

References
REF REF-1 REF-2 REF-3 REF-4 REF-5 REF-6 REF-7 REF-8 REF-9 REF-10 Document Oracle Database Application Developers Guide - ObjectRelational Features 10g Release 2 (10.2) Oracle Database PL/SQL User's Guide and Reference 10g Release 2 (10.2) Programming Perl A Fresh Look at Efficient Perl Sorting The Java Tutorials The Java Language Environment Imagining Other Dimensions A Perl Object for Flattened Master-Detail Data in Excel Public Web Service functions for Visual DataFlex football pool Pride and Prejudice and Zombies Book Trailer Details

Larry Wall, Tom Christiansen & Randal L. Schwartz, 1996 Uri Guttman and Larry Rosler Oracle A White Paper, by James Gosling & Henry McGilton, May 1996 Rick Groleau, 28 October 2003.This is a link from this interesting site: http://www.pbs.org/wgbh/nova/elegant/ BP Furey, August 2011 Web service Youtube video

110516815.doc

Page 42 of 42

You might also like