Code Timing and Object Orientation and Zombies
Total Page:16
File Type:pdf, Size:1020Kb
CODE TIMING AND OBJECT ORIENTATION AND ZOMBIES
Author: Brendan Furey Creation Date: 22 November 2010 Version: 1.7 Last Updated: 25 September 2012
“Bless thee, Bottom! bless thee! thou art translated” – A Midsummer Night’s Dream
0919c4cd41449f04124a876bcc11428a.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 8 Get Timer 8 Write Times 8 Oracle Implementation (Standard Object Model) 9 Timer Set Object 9 Code 9 Utility Package 12 Code 12 Notes on Oracle (Standard Object Model) Implementation 14 Instance Hash Arrays 14 Test Program (Employee Hierarchy) 14 Code Excerpt14 Example Output 15 With Inner Loop Timing 15 Without Inner Loop Timing 15 Notes on Timing Results 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 Utility Package 25 Code 25 Test Program (Directory Tree) 25 Driver Code 26 Example Output 27 Notes on Timing Results 29 Java Implementation 30 Timer Set Object 30 Code 30 0919c4cd41449f04124a876bcc11428a.doc Page 2 of 42 Notes on Java Implementation 32 Utility Package 32 Code 32 Test Driver Program (Web Service Proxy) 32 Code 32 Example Output 34 Notes on Timing Results 35 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 The Zombie Object Model 37 Advantages of Object Orientation 37 Object Orientation without ‘Objects’ 37 Comparative Notes 39 Feature Comparison Table 39 Conclusions 41 References 42
Change Record Date Author Version Change Reference 22-Nov-2010 BPF 1.0 Initial 28-Nov-2010 BPF 1.1 Added issue and reference concerning lack of private attributes 04-Dec-2010 BPF 1.2 Some minor typos 22-Dec-2010 BPF 1.3 Added CPU times, simplified object structure 24-Oct-2011 BPF 1.4 Placeholder for next revision Title changed from original of ‘A Simple PL/SQL Code Timing Object’ 27-Oct-2011 BPF 1.5 to reflect rewrite of contents 15-Jan-2012 BPF 1.6 Get_Timer_Stats code: Small bug fix 25-Sep-2012 BPF 1.7 References into hyperlinks
0919c4cd41449f04124a876bcc11428a.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 Description Oracle Database Oracle Database 10g Express Edition Release 10.2.0.1.0 - Production Perl ActivePerl 5.12.4.1205, by ActiveState Software Inc. Java 1.5.0_06 Oracle JDeveloper 10.1.3.1.0 Diagrammer Microsoft Visio 2003 (11.3216.5606) Operating System Microsoft Windows 7 Home Premium (64 bit) Computer Samsung 900X3A, 4GB memory, Intel I5-2537M @ 1.4GHz x 2
0919c4cd41449f04124a876bcc11428a.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 Element Category Description Global Data Record Timer set level data Start Time Timestamp Time of set construction Start CPU Time (User) Integer User CPU time offset at set construction Start CPU Time (System) Integer System CPU time offset at set construction. Prior Time Timestamp Prior time Prior CPU Time (User) Integer Prior user CPU time offset Prior CPU Time (System) Integer Prior system CPU time offset
0919c4cd41449f04124a876bcc11428a.doc Page 5 of 42 Timer Set Name Character Name of timer set Array List of timers (built dynamically) Timer Name Character Name of timer Timer List Elapsed Time Number Elapsed time associated with timer CPU Time (User) Integer User CPU time associated with timer CPU Time (System) Integer System CPU time associated with timer. Hash Hash used to find timer in the list without searching Timer Hash Timer Name Character Name of timer (hash key) Timer Index Integer Index of timer in timer list (hash value)
0919c4cd41449f04124a876bcc11428a.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 Type Notes Timer Set Name Character 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 doesn’t 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
0919c4cd41449f04124a876bcc11428a.doc Page 7 of 42 hash 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 Type Notes Timer Name Character 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 Type Notes Timer Name Character 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.
0919c4cd41449f04124a876bcc11428a.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 Oracle’s 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_list_type, timer_set_name VARCHAR2(30), start_time TIMESTAMP, prior_time TIMESTAMP, start_time_cpu INTEGER, prior_time_cpu 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: Brendan Furey Date: 27 October 2011 Description: 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 := p_timer_set_name; start_time := SYSTIMESTAMP; prior_time := start_time; start_time_cpu := DBMS_Utility.Get_CPU_Time; prior_time_cpu := start_time_cpu; timer_list := NULL;
RETURN;
END timer_set_type;
0919c4cd41449f04124a876bcc11428a.doc Page 9 of 42 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 = 0 THEN RETURN; ELSE x_ela_secs := Utils.Get_Seconds (timer_list (l_index).ela_interval); x_cpu_secs := 0.01*timer_list (l_index).cpu_interval; x_calls := timer_list (l_index).n_calls; END IF;
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 || ' ' || c_lines || ' ' || c_lines || ' ' || '--' || c_lines || '
0919c4cd41449f04124a876bcc11428a.doc Page 10 of 42 ---' || 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_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) || ' Elapsed CPU Calls 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;
0919c4cd41449f04124a876bcc11428a.doc Page 11 of 42 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: Brendan Furey Date: 27 October 2011 Description: 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 CONSTANT VARCHAR2(30) := 'HH24:MI:SS'; c_datetime_fmt CONSTANT VARCHAR2(30) := 'DD Mon RRRR ' || c_time_fmt;
g_debug_level PLS_INTEGER := 1; g_id VARCHAR2(30); g_line_size PLS_INTEGER := 150;
END Utils; / SHOW ERROR CREATE OR REPLACE PACKAGE BODY Utils AS /**************************************************************************************************
Author: Brendan Furey Date: 27 October 2011 Description: 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 TIMESTAMP;
0919c4cd41449f04124a876bcc11428a.doc Page 12 of 42 g_init_time TIMESTAMP; TYPE hash_type IS TABLE OF PLS_INTEGER INDEX BY VARCHAR2(60); g_timer_hash 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 IS BEGIN
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 VARCHAR2, p_level PLS_INTEGER DEFAULT 0, p_debug_level PLS_INTEGER DEFAULT 0) AS
i PLS_INTEGER; l_len PLS_INTEGER; l_padding VARCHAR2(50); l_text VARCHAR2(4000); BEGIN
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
0919c4cd41449f04124a876bcc11428a.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 Oracle’s ‘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 non- database object-oriented languages, there are some significant limitations in Oracle’s model for objects, one of which is mentioned below (and see also the later section on Oracle’s object orientation).
Instance Hash Arrays It is a serious limitation that hash arrays (associative arrays in Oracle’s 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 Oracle’s built in SQL constructs compares with what can be achieved using an efficient PL/SQL program (normally it’s 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 Oracle’s 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 Oracle’s 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: Usage of timer set instances in multiple packages at once Detection of timing at too high a resolution
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; /
0919c4cd41449f04124a876bcc11428a.doc Page 14 of 42 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 it’s 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. 0919c4cd41449f04124a876bcc11428a.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).
0919c4cd41449f04124a876bcc11428a.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 Oracle’s 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: Brendan Furey Date: 27 October 2011 Description: 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 := p_timer_set_name; l_timer_set.start_time := l_start_time; l_timer_set.prior_time := l_start_time; l_timer_set.start_time_cpu := l_start_time_cpu; l_timer_set.prior_time_cpu := l_start_time_cpu; l_timer_set_h.timer_set := 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;
0919c4cd41449f04124a876bcc11428a.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 INTEGER := DBMS_Utility.Get_CPU_Time; l_systimestamp TIMESTAMP := SYSTIMESTAMP; l_timer_ind PLS_INTEGER := 0; l_timer timer_type;
l_timer_list timer_list_type := g_timer_set_list (p_timer_set_ind).timer_set.timer_list; l_timer_hash hash_type := g_timer_set_list (p_timer_set_ind).timer_hash; l_prior_time TIMESTAMP := g_timer_set_list (p_timer_set_ind).timer_set.prior_time; l_prior_time_cpu PLS_INTEGER := g_timer_set_list (p_timer_set_ind).timer_set.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;
0919c4cd41449f04124a876bcc11428a.doc Page 18 of 42 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 CPU Calls Ela/Call CPU/Call'); END Write_Header;
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 || ' ---' || 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 timer_list_type := g_timer_set_list (p_timer_set_ind).timer_set.timer_list; l_sum_ela NUMBER := 0; l_sum_cpu NUMBER := 0; l_ela_seconds NUMBER; l_head_len PLS_INTEGER; l_self_timer PLS_INTEGER; 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;
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,
0919c4cd41449f04124a876bcc11428a.doc Page 19 of 42 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; i := g_timer_set_list.NEXT (i);
0919c4cd41449f04124a876bcc11428a.doc Page 20 of 42 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 Oracle’s 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 don’t 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
0919c4cd41449f04124a876bcc11428a.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 Elapsed CPU Calls Ela/Call CPU/Call ------Lines 4.99 4.88 1,263 0.00395 0.00386 (Other) 3.48 3.56 1 3.47500 3.56000 ------Total 8.46 8.44 1,264 0.00670 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 Elapsed CPU Calls Ela/Call CPU/Call ------Open 0.00 0.00 1 0.00000 0.00000 First Fetch 0.46 0.47 1 0.46100 0.47000 Array Copying 2.29 2.29 16 0.14338 0.14313 Remaining fetching 0.78 0.77 16 0.04900 0.04813 Recursion for 1000 (12 levels) 8.49 8.47 1 8.49400 8.47000 (Other) 0.00 0.00 1 0.00000 0.00000 ------Total 12.03 12.00 36 0.33425 0.33333 ------
Timer Set Summary ======
Timer Set Elapsed CPU Calls Ela/Call CPU/Call ------Tree 12.03 12.00 1 12.03300 12.00000 File Writer 8.46 8.44 1 8.46400 8.44000
0919c4cd41449f04124a876bcc11428a.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; }
0919c4cd41449f04124a876bcc11428a.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); }
0919c4cd41449f04124a876bcc11428a.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
0919c4cd41449f04124a876bcc11428a.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: Use of the timer set object as an instance variable within another object Dynamic timer partitioning: The listTree method’s last parameter is appended to the timer names within the DirTree object
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;
0919c4cd41449f04124a876bcc11428a.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 Elapsed CPU = User + System Calls Ela/Call CPU/Call ------Contruct C:/ 141.38 116.86 30.56 86.30 1 141.38444 116.86100 Tree by size, all 22.39 22.17 21.61 0.56 1 22.38828 22.16700 Tree by size, 5MB 3.45 3.43 3.42 0.02 1 3.44557 3.43200 (Other) 0.00 0.00 0.00 0.00 1 0.00003 0.00000 ------Totals 167.22 142.46 55.58 86.88 4 41.80458 35.61500 ------
0919c4cd41449f04124a876bcc11428a.doc . 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 Elapsed CPU = User + System Calls Ela/Call CPU/Call ------Contructor 141.38 116.86 30.56 86.30 1 141.38443 116.86100 Pre Tree (All) 2.26 2.23 2.23 0.00 1 2.26308 2.23100 Headings (All) 0.00 0.00 0.00 0.00 1 0.00046 0.00000 Directory Printing (All) 3.00 3.02 2.89 0.12 49,646 0.00006 0.00006 *** 2.30 2.24 2.12 0.12 49,646 0.00005 0.00005 File Sort (All) 11.93 11.85 11.77 0.08 49,646 0.00024 0.00024 File Printing (All) 2.44 2.15 1.82 0.33 49,646 0.00005 0.00004 *** 1.73 1.38 1.05 0.33 49,646 0.00003 0.00003 Directory Sort (All) 1.00 0.95 0.95 0.00 51,831 0.00002 0.00002 *** 0.26 0.14 0.14 0.00 51,831 0.00001 0.00000 Pre Tree (5 MB) 1.79 1.78 1.78 0.00 1 1.79230 1.77800 Headings (5 MB) 0.00 0.00 0.00 0.00 1 0.00040 0.00000 Directory Printing (5 MB) 0.12 0.09 0.09 0.00 1,609 0.00007 0.00006 File Sort (5 MB) 0.54 0.59 0.59 0.00 1,609 0.00033 0.00037 File Printing (5 MB) 0.06 0.02 0.02 0.00 1,609 0.00004 0.00001 Directory Sort (5 MB) 0.41 0.47 0.45 0.02 17,999 0.00002 0.00003 *** 0.16 0.18 0.17 0.02 17,999 0.00001 0.00001 (Other) 2.42 2.60 2.57 0.03 1 2.42352 2.59800 ------Totals 167.36 142.60 55.72 86.88 223,601 0.00075 0.00064 ------
0919c4cd41449f04124a876bcc11428a.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.
0919c4cd41449f04124a876bcc11428a.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
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)) ;
0919c4cd41449f04124a876bcc11428a.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 { String name; long interval[] = {0, 0, 0, 0};
0919c4cd41449f04124a876bcc11428a.doc Page 31 of 42 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: 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
Code package TestWS;
import Foot.proxy.*; import TimerSet.TimerSet;
public class Caller {
0919c4cd41449f04124a876bcc11428a.doc Page 32 of 42 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
0919c4cd41449f04124a876bcc11428a.doc Page 33 of 42 Example Output Iterating through ArrayList elements... 0: Description: Round 1 Goal @ 79 minutes, by Rafael Márquez Goal @ 55 minutes, by Siphiwe Tshabalala . . continues… . 63: Description: Final Goal @ 116 minutes, by Andrés 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 Elapsed CPU = User + System Calls Ela/Call CPU/Call ------infoSoapType 0.48 0.41 0.34 0.06 1 0.47614 0.40560 allGames 6.32 0.51 0.50 0.02 1 6.31736 0.51480 Iterating, outer 0.01 0.00 0.00 0.00 64 0.00019 0.00000 Iteration, inner 0.05 0.03 0.03 0.00 64 0.00081 0.00049 (Other) 0.00 0.00 0.00 0.00 1 0.00004 0.00000 ------Totals 6.86 0.95 0.87 0.08 131 0.05235 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 Elapsed CPU = User + System Calls Ela/Call CPU/Call ------Initialising 6.79 0.92 0.84 0.08 1 6.79091 0.92041 Games 0.06 0.03 0.03 0.00 1 0.06379 0.03120 (Other) 0.05 0.05 0.03 0.02 1 0.05024 0.04680 ------Totals 6.90 1.00 0.90 0.09 3 2.30164 0.33280 ------Process exited with exit code 0.
0919c4cd41449f04124a876bcc11428a.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.
0919c4cd41449f04124a876bcc11428a.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. It’s also possible to have an object-oriented interface to a relational data model using Oracle’s object views (Oracle® Database Application Developer’s 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 Oracle’s 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 (Oracle’s 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 (Oracle’s term for hashes) are PL/SQL only and thus cannot be used for object instance variables
0919c4cd41449f04124a876bcc11428a.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 can’t 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 Oracle’s 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.
0919c4cd41449f04124a876bcc11428a.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 Oracle’s 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.
0919c4cd41449f04124a876bcc11428a.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 Perl Java Three collection types are Core Java includes a 1- available in PL/SQL, being two Perl has 1-dimensional dimensional array type that can variations of standard arrays standard arrays and hash be applied to any native type or and a hash array type. arrays. Complex data structures class. Complex data structures Complex data structures including nested arrays are including nested arrays are including nested arrays are implemented using array implemented using class Collections implemented using nesting of references in the higher level nesting. The (named) attributes types. Types may be both array. Record types with field in a class correspond roughly to native types such as the array names do not exist, arrays Oracle’s record or object types. types, or named user-defined being used instead (hash Variations on the basic array types. Record and object types arrays provide name-value type, including for hash arrays, provide for a set of named pairs). are provided by classes fields of specified types. available in Java libraries. Oracle packages are code modules, that group together related procedures, and provide session-persistent variables. Perl packages are essentially Java packages are directories Packages They are split into header and namespaces, and usually where class files are stored. body sections, and can be used correspond to files. to implement some of the key features of object-orientation as shown here. Object oriented features are provided explicitly through Objects in Perl are simply object type headers and bodies. references to arrays within Java was designed around the However, these have significant packages that have been concept of object orientation, Object limitations in Oracle and key ‘blessed’ into objects of the and all code is organised into Orientation features of object orientation package type. As in Oracle, object classes. However, class can also be provided through object orientation is optional, methods and variables do not packages with either record and can be used only when require object instantiation. types or object type headers useful. without bodies. Subroutines may be passed a single input array, which, as mentioned, always consists of scalar variables, passed by Java methods may take a set of reference but which may named input parameters of any Procedures may have a set of include array or scalar type, passed by value, but named parameters of any type, references. Often the input object types are essentially which can be input, output or array is copied into local references and so any methods Parameters both and can be passed by variables that don’t affect the called act on the underlying value or reference. underlying input references. objects. Functions return a value of A return value may be specified Methods may be declared void, specified type. as a scalar or array of scalars or return a value of specified (again possibly including type. references), otherwise the value of the last expression evaluated is returned. Procedure Oracle procedures may have Perl subroutines may be Java classes may be nested Nesting nested procedures that are only defined within other subroutines within other classes (called callable within their nesting but are not in fact local to that inner classes, and used in our structure, and which can subroutine, but accessible timer set class) and are then access variables defined at throughout the relevant only callable within the outer higher levels. package. This results in rather class. bizarre behaviour in relation to
0919c4cd41449f04124a876bcc11428a.doc Page 39 of 42 the inner subroutine’s access to 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 that’s not always a good idea), or accessing the inner subroutine via reference. Separation of packages into Perl does not have explicitly headers and bodies provides private and public subroutines, for public and private but there is a convention to The private and public Scoping procedures and variables. prefix subroutines not intended keywords define what can be Objects, as noted earlier, to be called directly with an accessed outside a class. cannot have private instance underscore. We have followed attributes. this convention. Increment operators allow succinct application of arithmetic operators incrementally. Oracle Perl has increment operators: Increment unfortunately lacks this feature Java has increment operators $x += $y; Operator that most modern languages that work similarly to Perl. $x++; have, so one must write, for example: x := x + y; x := x + 1; Largest Smallest Middle Standard Object (including the - Lines: 131 - Lines: 153 Code Size hash code): - Statements: 92 - Statements: 95 (Here various - Lines: 259 - Words: 560 - Words: 656 measures - Statements: 192 - Characters: 4,965 - Characters: 6,614 are taken of - Words: 1,139 the timer set - Characters: 12,563 object code, omitting Zombie Object (excluding blank and session summary code): comment - Lines: 201 lines) - Statements: 152 - Words: 940 - Characters: 10,535 ‘Java is: ‘The most important principle of Simple--the number of ‘PL/SQL combines the data- language design is simply that language constructs you need manipulating power of SQL with easy things should be easy, to understand to get your job the processing power of and hard things should be done is minimal. In their own procedural languages.’ possible’ Familiar--Java looks like C and words - Oracle® Database PL/SQL - Programming Perl (a nicely C++ while discarding the User's Guide and Reference written manual with a lot of overwhelming complexities of 10g Release 2 (10.2) good material on programming those languages. ‘ in general) - The Java Language Environment
0919c4cd41449f04124a876bcc11428a.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
0919c4cd41449f04124a876bcc11428a.doc Page 41 of 42 References REF Document Details Oracle® Database Application Developer’s Guide - REF-1 Object-Relational Features 10g Release 2 (10.2) Oracle® Database PL/SQL User's Guide and Reference REF-2 10g Release 2 (10.2) Larry Wall, Tom Christiansen & Randal L. REF-3 Programming Perl Schwartz, 1996 REF-4 A Fresh Look at Efficient Perl Sorting Uri Guttman and Larry Rosler REF-5 The Java Tutorials Oracle A White Paper, by James Gosling & Henry REF-6 The Java Language Environment McGilton, May 1996 Rick Groleau, 28 October 2003.This is a link REF-7 Imagining Other Dimensions from this interesting site: http://www.pbs.org/wgbh/nova/elegant/ REF-8 A Perl Object for Flattened Master-Detail Data in Excel BP Furey, August 2011 Public Web Service functions for Visual DataFlex football REF-9 Web service pool REF-10 Pride and Prejudice and Zombies Book Trailer Youtube video
0919c4cd41449f04124a876bcc11428a.doc Page 42 of 42