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
Utility Package...........................................................................................12 Notes on Oracle (Standard Object Model) Implementation..........................14 Test Program (Employee Hierarchy)...........................................................14 Example Output.........................................................................................15
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
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 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.
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
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
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
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
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);
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
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
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;
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 -----------------------------------------------------------------------------------
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
***************************************************************************************************/ 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;
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())); } }
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
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
110516815.doc
Page 36 of 42
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.
Collections
Packages
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
Code Size (Here various measures are taken of the timer set object code, omitting blank and comment lines)
Middle -
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