1
1
s-lang/slsh/lib/profile.sl

754 строки
20 KiB
Plaintext

% Copyright (C) 2012-2017,2018 John E. Davis
%
% This file is part of the S-Lang Library and may be distributed under the
% terms of the GNU General Public License. See the file COPYING for
% more information.
%---------------------------------------------------------------------------
% See the slprof script for an example of using the profiler.
%
_boseos_info=0;
_bofeof_info=0;
%public variable __DEBUG_PROFILE_;
% For lines
private variable Profile_Info_Type = struct
{
name, % (bos/eof)
num_called, % (bos/bof) num times line called
num_s_triggered, % (eof/eof) num child statements
num_f_triggered, % (eof/eof) num functions triggered
num_self_f, % (eos/) num functions directly called
num_self_s, % num statements directly called
self_time, % (eos/eof)
cum_time, % (eos/eof)
};
Profile_Info_Type.num_called = 0;
Profile_Info_Type.num_s_triggered = 0;
Profile_Info_Type.num_f_triggered = 0;
Profile_Info_Type.num_self_f = 0;
Profile_Info_Type.num_self_s = 0;
Profile_Info_Type.self_time = 0.0;
Profile_Info_Type.cum_time = 0.0;
private variable L_Info_Table;
private variable F_Info_Table;
private define convert_profile_info_to_array (p)
{
variable keys = assoc_get_keys (p);
variable i, n = length (p);
% Filter out the bad entries
_for i (0, n-1, 1)
{
if (keys[i] == "") % Dummy
assoc_delete_key (p, keys[i]);
}
n = length (p);
keys = assoc_get_keys (p);
variable s = @Profile_Info_Type;
s.num_called = Int_Type[n];
s.num_s_triggered = Int_Type[n];
s.num_f_triggered = Int_Type[n];
s.num_self_f = Int_Type[n];
s.num_self_s = Int_Type[n];
s.self_time = Double_Type[n];
s.cum_time = Double_Type[n];
s.name = keys;
variable vals = assoc_get_values (p);
_for i (0, n-1, 1)
{
variable v = vals[i];
s.num_called[i] = v.num_called;
s.num_s_triggered[i] = v.num_s_triggered;
s.num_f_triggered[i] = v.num_f_triggered;
s.num_self_f[i] = v.num_self_f;
s.num_self_s[i] = v.num_self_s;
s.self_time[i] = v.self_time;
s.cum_time[i] = v.cum_time;
}
return s;
}
private define new_l_info (name)
{
variable l = @Profile_Info_Type;
L_Info_Table[name] = l;
l.name = name;
return l;
}
private define new_f_info (name)
{
variable finfo = @Profile_Info_Type;
F_Info_Table[name] = finfo;
finfo.name = name;
return finfo;
}
private variable Function_Stack_Type = struct
{
f_info,
s_counter,
f_counter,
self_s_counter,
self_time,
cum_time,
};
private variable Function_Stack;
private variable Line_Stack_Type = struct
{
l_info,
s_counter,
f_counter,
self_f_counter,
self_time,
cum_time,
};
private variable Line_Stack;
private variable L_Info;
private variable L_S_Counter; % bos/eof
private variable L_F_Counter; % bos/eof
private variable L_Self_Time; % bof/eof
private variable L_Cum_Time; % bof/eof
private variable F_Info;
private variable F_S_Counter; % bof
private variable F_F_Counter; % bof
private variable F_Self_S; % bos
private variable F_Self_Time; % bos/eos
private variable F_Cum_Time; % bos/eos
private variable Num_Statement_Counts;
private variable Num_Fun_Calls;
private variable Tstart;
private variable Null_Struct = struct {dummy};
private variable BOS_Depth;
private variable BOS_Stack_Depth;
private define push_line_info ()
{
variable s = @Line_Stack_Type;
s.l_info = L_Info;
s.s_counter = L_S_Counter;
s.f_counter = L_F_Counter;
s.self_time = L_Self_Time;
s.cum_time = L_Cum_Time;
list_append (Line_Stack, s);
BOS_Stack_Depth++;
}
private define pop_line_info ()
{
variable s = list_pop (Line_Stack, -1);
L_Info = s.l_info;
L_Self_Time = s.self_time;
L_Cum_Time = s.cum_time;
L_Info.num_self_f++;
L_Info.num_s_triggered += (Num_Statement_Counts - s.s_counter);
L_Info.num_f_triggered += (Num_Fun_Calls - s.f_counter);
L_F_Counter = Num_Fun_Calls;
L_S_Counter = Num_Statement_Counts;
BOS_Stack_Depth--;
}
% This variable tracks sequential calls to the BOS and EOS handlers
% This can happen when profiled code calls non-profiled, which calls
% profiled.
private variable Last_Was_BOS_EOS = 0;
private variable Dummy_L_Info;
private define bos_handler (file, line)
{
variable dt = toc() - Tstart;
F_Cum_Time += dt;
F_Self_Time += dt;
F_Self_S++;
if (BOS_Depth >= BOS_Stack_Depth)
{
#ifexists __DEBUG_PROFILE_
%vmessage ("# bos pushing (%d,%d)", BOS_Depth,BOS_Stack_Depth);
#endif
L_Cum_Time += dt;
L_Self_Time += dt;
push_line_info ();
}
BOS_Depth = BOS_Stack_Depth;
Num_Statement_Counts++;
file = sprintf ("%s:%d", file, line);
variable l = L_Info_Table[file];
if (l == Null_Struct)
l = new_l_info (file);
l.num_called++;
L_Info = l;
L_S_Counter = Num_Statement_Counts;
L_F_Counter = Num_Fun_Calls;
L_Self_Time = 0.0;
L_Cum_Time = 0.0;
#ifexists __DEBUG_PROFILE_
vmessage ("BOS: (%d, %d) %S", BOS_Depth, BOS_Stack_Depth, file);
#endif
Tstart = toc ();
}
private define eos_handler ()
{
variable dt = toc()-Tstart;
if (BOS_Depth < BOS_Stack_Depth)
{
#ifexists __DEBUG_PROFILE_
%vmessage ("# eos popping (%d,%d)", BOS_Depth,BOS_Stack_Depth);
#endif
pop_line_info ();
}
BOS_Depth--;
L_Self_Time += dt;
L_Cum_Time += dt;
F_Cum_Time += L_Cum_Time;
F_Self_Time += L_Self_Time;
L_Info.self_time += L_Self_Time;
L_Info.cum_time += L_Cum_Time;
#ifexists __DEBUG_PROFILE_
vmessage ("EOS: (%d,%d) %S", BOS_Depth, BOS_Stack_Depth, L_Info.name);
#endif
% This is necessary if a BOF/EOF sequence follows to avoid another
% push/pop of this object since
if (Last_Was_BOS_EOS == -1)
L_Info = Dummy_L_Info;
Last_Was_BOS_EOS = -1;
Tstart = toc();
}
private define bof_handler (fun, file)
{
variable dt = toc () - Tstart;
Num_Fun_Calls++;
#ifexists __DEBUG_PROFILE_
%vmessage ("# bof pushing (%d,%d)", BOS_Depth,BOS_Stack_Depth);
#endif
L_Self_Time += dt;
L_Cum_Time += dt;
push_line_info ();
%BOS_Stack_Depth++;
variable f = @Function_Stack_Type;
f.f_info = F_Info;
f.s_counter = F_S_Counter;
f.f_counter = F_F_Counter;
f.self_time = F_Self_Time;
f.self_s_counter = F_Self_S;
f.cum_time = F_Cum_Time;
list_append (Function_Stack, f);
file = sprintf ("%S:%S", fun, file);
variable f_info = F_Info_Table[file];
if (f_info == Null_Struct)
f_info = new_f_info (file);
f_info.num_called++;
F_Info = f_info;
F_S_Counter = Num_Statement_Counts;
F_F_Counter = Num_Fun_Calls;
F_Cum_Time = 0.0;
F_Self_Time = 0.0;
F_Self_S = 0;
#ifexists __DEBUG_PROFILE_
vmessage ("BOF: (%d,%d) %S", BOS_Depth, BOS_Stack_Depth, file);
#endif
Tstart = toc ();
}
private define eof_handler ()
{
variable dt = toc() - Tstart;
#ifexists __DEBUG_PROFILE_
%vmessage ("# eof popping (%d,%d)", BOS_Depth,BOS_Stack_Depth);
#endif
F_Cum_Time += dt;
F_Self_Time += dt;
F_Info.cum_time += F_Cum_Time;
F_Info.self_time += F_Self_Time;
F_Info.num_s_triggered += (Num_Statement_Counts - F_S_Counter);
F_Info.num_f_triggered += (Num_Fun_Calls - F_F_Counter);
F_Info.num_self_s += F_Self_S;
pop_line_info ();
% Add on time spent in the function to this line's value
L_Cum_Time += F_Cum_Time;
#ifexists __DEBUG_PROFILE_
vmessage ("EOF: (%d,%d) %S", BOS_Depth, BOS_Stack_Depth, F_Info.name);
#endif
variable f = list_pop (Function_Stack, -1);
F_Info = f.f_info;
F_Info.num_self_f++;
F_S_Counter = f.s_counter;
F_F_Counter = f.f_counter;
F_Self_Time = f.self_time;
F_Self_S = f.self_s_counter;
F_Cum_Time = f.cum_time;
%F_Cum_Time += dt;
%F_Self_Time += dt;
Tstart = toc();
}
% In function-only mode, lines are not profiled. The self-time of the
% function is its cumulative time minus the cumulative times of the
% functions that it directly calls. The F_Self_Time variable will
% track those.
private define f_bof_handler (fun, file)
{
variable dt = toc () - Tstart;
Num_Fun_Calls++;
variable f = @Function_Stack_Type;
f.f_info = F_Info;
f.f_counter = F_F_Counter;
f.self_time = F_Self_Time;
f.cum_time = F_Cum_Time + dt;
list_append (Function_Stack, f);
file = sprintf ("%S:%S", fun, file);
variable f_info = F_Info_Table[file];
if (f_info == Null_Struct)
f_info = new_f_info (file);
f_info.num_called++;
F_Info = f_info;
F_F_Counter = Num_Fun_Calls;
F_Cum_Time = 0.0;
F_Self_Time = 0.0;
Tstart = toc ();
}
private define f_eof_handler ()
{
variable dt = toc() - Tstart;
F_Cum_Time += dt;
F_Info.cum_time += F_Cum_Time;
F_Info.self_time += (F_Cum_Time - F_Self_Time);
F_Info.num_f_triggered += (Num_Fun_Calls - F_F_Counter);
variable f = list_pop (Function_Stack, -1);
F_Info = f.f_info;
F_Info.num_self_f++;
F_F_Counter = f.f_counter;
F_Self_Time = f.self_time + F_Cum_Time;
F_Cum_Time += f.cum_time;
Tstart = toc();
}
% Usage: profile_on (do_line_profile)
define profile_on ()
{
_boseos_info = 0;
_bofeof_info = 1;
if (_NARGS)
{
variable arg = ();
if (arg)
_boseos_info = 3;
}
}
define profile_off ()
{
_boseos_info = 0;
_bofeof_info = 0;
}
private define setup_profiler_variables ()
{
L_Info_Table = Assoc_Type[Struct_Type, Null_Struct];
F_Info_Table = Assoc_Type[Struct_Type, Null_Struct];
Line_Stack = {};
Function_Stack = {};
Dummy_L_Info = new_l_info ("");
L_Info = Dummy_L_Info;
L_S_Counter = 0;
L_F_Counter = 0;
L_Self_Time = 0;
L_Cum_Time = 0;
F_Info = new_f_info ("");
F_S_Counter = 0;
F_F_Counter = 0;
F_Self_Time = 0;
F_Self_S = 0;
F_Cum_Time = 0;
Tstart = 0;
Num_Statement_Counts = 0;
Num_Fun_Calls = 0;
BOS_Stack_Depth=0;
BOS_Depth=0;
}
define profile_begin ()
{
variable arg = 0;
if (_NARGS)
arg = ();
if (arg)
{
()=_set_bos_handler (&bos_handler);
()=_set_eos_handler (&eos_handler);
()=_set_bof_handler (&bof_handler);
()=_set_eof_handler (&eof_handler);
}
else
{
()=_set_bos_handler (NULL);
()=_set_eos_handler (NULL);
()=_set_bof_handler (&f_bof_handler);
()=_set_eof_handler (&f_eof_handler);
}
setup_profiler_variables ();
profile_on (arg);
tic ();
}
define profile_end ()
{
()=_set_bos_handler (NULL);
()=_set_eos_handler (NULL);
()=_set_bof_handler (NULL);
()=_set_eof_handler (NULL);
profile_off ();
}
% Calibration Notes:
%
% If the execution of a statememt causes M other statements to
% execute and N function calls, then the amount of profiler overhead
% is given by
%
% N*dF + M*dS
%
% where dF is the amount of overhead per function call for running
% the bof/eof-handlers, and dS is the overhead per statement in
% running the bos/eos-handlers.
%
% The timers used to measure the amount of time spent in a function
% or statement are stopped when a handler is called, and started
% again when the handler returns. This introduces systematic error
% in the measurements. Call dt_S the systematic error introduced by
% bos/eos handlers and dt_F systematic error from the bof/eof handlers.
% Then the observed time as given by the interval times is related to
% the "true" time t via
%
% t_obs = t + N*dt_F + M*dt_S + dt_S
%
% The self-time of the statement t_self is determined through an
% interval timer that stops when a function is called, and starts
% again after the function returns. If the statement makes N_self
% direct function calls, then t_self is related to its observed
% self-time t_self_obs by
%
% t_self_obs = t_self + N_self*dt_F + dt_S
%
% The self-time of a function is the sum of the self-times of the
% individual statements that were executed directly by the function.
% Suppose the function executes M_self statements that in turn
% directly call a total of N_self functions. Then, for functions it
% follows that:
%
% t_self_obs_f = t_self_f + N_self*dt_F + M_self*dt_S + dt_F
%
% where the last term accounts for the overhead of the bof/eof
% handler for the function itself.
%
% ----------------------------------------------------------------
%
% The values dF, dS, dt_S, and dt_F can be determined as follows:
%
% With no handlers in place, t can be accurately measured and can be
% considered t be a known quantity. Suppose the statement has no
% function calls and let that statement be executed a very large
% number of times B with handlers in place. Then the elapsed time
% t_elapsed as given by the wallclock will be given by
%
% t_elapsed = B*(t+dS) ===> dS = (t_elapsed - B*t)/B
%
% The total measured or observed time t_obs, as given by the
% profiler's starting and stopping of the clock upon each execution
% will be given by
%
% t_obs = B*(t+dt_S) ===> dt_S = (t_obs - B*t)/B
%
% If the statement is just a function call to a function that does
% nothing, then
%
% t_elapsed = B*(t + dS + dF)
%
% Using the prior determination of dS, we obtain
%
% dF = t_elapsed/B - t - dS
%
% For just a single function call with no arguments, the self-time is
% defined to be 0. So:
%
% t_self_obs = 0 + 1*dt_F + dt_S ==> dt_F = t_self_obs-dt_S
%
private variable Overhead_Per_Statement = 0.0; % dS
private variable Error_Per_Statement = 0.0; % dt_S
private variable Overhead_Per_Function = 0.0; % dF
private variable Error_Per_Function = 0.0; % dt_F
profile_off ();
private define cal_nop_0 ();
private define cal_f_0 (n)
{
loop (n)
{
cal_nop_0(); cal_nop_0(); cal_nop_0(); cal_nop_0(); cal_nop_0();
cal_nop_0(); cal_nop_0(); cal_nop_0(); cal_nop_0(); cal_nop_0();
cal_nop_0(); cal_nop_0(); cal_nop_0(); cal_nop_0(); cal_nop_0();
cal_nop_0(); cal_nop_0(); cal_nop_0(); cal_nop_0(); cal_nop_0();
}
}
private define cal_s_0 (n)
{
loop (n)
{
() = 1; () = 1; () = 1; () = 1; () = 1;
() = 1; () = 1; () = 1; () = 1; () = 1;
() = 1; () = 1; () = 1; () = 1; () = 1;
() = 1; () = 1; () = 1; () = 1; () = 1;
}
}
profile_on (1);
private define cal_nop_1 ();
private define cal_f_1 (n)
{
loop (n)
{
cal_nop_1(); cal_nop_1(); cal_nop_1(); cal_nop_1(); cal_nop_1();
cal_nop_1(); cal_nop_1(); cal_nop_1(); cal_nop_1(); cal_nop_1();
cal_nop_1(); cal_nop_1(); cal_nop_1(); cal_nop_1(); cal_nop_1();
cal_nop_1(); cal_nop_1(); cal_nop_1(); cal_nop_1(); cal_nop_1();
}
}
private define cal_s_1 (n)
{
loop (n)
{
() = 1; () = 1; () = 1; () = 1; () = 1;
() = 1; () = 1; () = 1; () = 1; () = 1;
() = 1; () = 1; () = 1; () = 1; () = 1;
() = 1; () = 1; () = 1; () = 1; () = 1;
}
}
profile_off ();
#ifnexists sum
private define sum (x)
{
variable s = 0.0;
foreach (x) s += ();
return s;
}
#endif
define profile_calibrate ()
{
#ifexists __DEBUG_PROFILE_
return;
#endif
if (_NARGS == 0) 1000;
variable n1 = ();
variable n0 = 100*n1;
tic();
cal_s_0 (n0);
variable t0 = toc;
variable t_expected = ((t0*n1)/n0); % when done n1 times
profile_begin (1);
tic ();
cal_s_1 (n1);
variable t_elapsed = toc ();
profile_end ();
variable s = convert_profile_info_to_array (L_Info_Table);
variable num = sum (s.num_called);
variable t_obs = sum(s.cum_time);
Error_Per_Statement = (t_obs - t_expected)/num;
Overhead_Per_Statement = (t_elapsed-t_expected)/num;
% Now calibrate the function call overhead
tic();
cal_f_0(n0);
t0 = toc;
t_expected = ((t0*n1)/n0); % when done n1 times
profile_begin (1);
tic ();
cal_f_1 (n1);
t_elapsed = toc ();
profile_end ();
%variable f = convert_func_info_to_array ();
s = convert_profile_info_to_array (L_Info_Table);
num = sum (s.num_called);
Overhead_Per_Function = (t_elapsed - t_expected)/num - Overhead_Per_Statement;
t_obs = sum(s.self_time);
Error_Per_Function = t_obs/num - Error_Per_Statement;
if (Error_Per_Function < 0)
Error_Per_Function = 0.0;
}
private define profile_report_lines (fp, s)
{
s.cum_time -= (Error_Per_Statement * (s.num_called + s.num_s_triggered)
+ s.num_f_triggered * Error_Per_Function);
s.self_time -= (Error_Per_Statement * s.num_called
+ s.num_self_f * Error_Per_Function);
variable rates = s.cum_time/s.num_called;
variable i = array_sort (s.self_time);
%variable i = array_sort (s.cum_time);
array_reverse (i);
rates = rates[i];
variable num_called = s.num_called[i];
variable self_time = s.self_time[i];
variable cum_time = s.cum_time[i];
variable num_self_f = s.num_self_f[i];
variable num_s_triggered = s.num_s_triggered[i];
variable num_f_triggered = s.num_f_triggered[i];
variable name = s.name[i];
#iffalse
variable total_counts = sum(num_called);
variable total_calls = sum(num_self_f);
() = fprintf (fp, "#Number of profiled statements executed: %g\n", total_counts);
() = fprintf (fp, "#Number of profiled function calls: %g\n", total_calls);
() = fprintf (fp, "#Profiler Overhead per statement: %g ms\n", Overhead_Per_Statement*1e3);
() = fprintf (fp, "#Profiler Overhead per function : %g ms\n", Overhead_Per_Function*1e3);
() = fprintf (fp, "#Profiler Error per statement : %g ms\n", Error_Per_Statement*1e3);
() = fprintf (fp, "#Profiler Error per function : %g ms\n", Error_Per_Function*1e3);
() = fprintf (fp, "#Total Profiler Overhead: %g secs\n",
total_counts * Overhead_Per_Statement + total_calls*Overhead_Per_Function);
() = fprintf (fp, "\n\n");
#endif
() = fprintf (fp, "#ncalls ms/call totalselfms totalsecs Fcalls Scalls File:line\n");
% 1234567 1234567890AB 1234567890AB 1234567890AB 1234567 1234567
_for i (0, length(rates)-1, 1)
{
() = fprintf (fp, "%7d %12.5f %12.5f %12.5f %7d %7d %s\n",
num_called[i], rates[i]*1e3, self_time[i]*1e3,
cum_time[i], num_f_triggered[i], num_s_triggered[i], name[i]);
}
}
private define profile_report_funcs (fp, s)
{
#ifnfalse
s.cum_time -= (Error_Per_Statement * s.num_s_triggered
+ s.num_f_triggered * Error_Per_Function);
s.self_time -= (Error_Per_Statement * s.num_self_s
+ s.num_self_f * Error_Per_Function);
#endif
variable rates = s.cum_time/s.num_called;
variable i = array_sort (s.self_time);
%variable i = array_sort (s.cum_time);
array_reverse (i);
rates = rates[i];
variable num_called = s.num_called[i];
variable self_time = s.self_time[i];
variable cum_time = s.cum_time[i];
variable num_self_f = s.num_self_f[i];
variable num_self_s = s.num_self_s[i];
variable num_s_triggered = s.num_s_triggered[i];
variable num_f_triggered = s.num_f_triggered[i];
variable name = s.name[i];
() = fprintf (fp, "#function ncalls ms/call totalselfms totalsecs Function File\n");
% 123456789012345678901234 1234567 1234567890AB 1234567890AB 1234567890AB
_for i (0, length(rates)-1, 1)
{
variable func_file = name[i];
variable j = is_substr (func_file, ":");
variable func = substr (func_file, 1, j-1);
variable file = substr (func_file, j+1, -1);
() = fprintf (fp, "%-24s %7d %12.4f %12.4f %12.4f %7d %7d %s\n",
func,
num_called[i], rates[i]*1e3, self_time[i]*1e3,
cum_time[i], num_self_f[i], num_self_s[i],
file
);
}
}
private define output_title (fp, title)
{
variable s = "----------------------------------------------------------------";
variable spaces = "";
loop ((strlen(s) - strlen(title))/2)
spaces = strcat (spaces, " ");
() = fprintf (fp, "\n#%s\n#%s%s\n#%s\n\n", s, spaces, title, s);;
}
define profile_report (file)
{
if (0 == __is_initialized (&L_Info_Table))
return;
variable fp = file;
if (typeof (file) == String_Type)
fp = fopen (fp, "w");
variable f = convert_profile_info_to_array (F_Info_Table);
if (length (f.cum_time))
{
output_title (fp, "Function Call Profile Report");
profile_report_funcs (fp, f);
}
variable s = convert_profile_info_to_array (L_Info_Table);
if (length (s.cum_time))
{
output_title (fp, "Line by Line Profile Report");
profile_report_lines (fp, s);
}
ifnot (_eqs (fp,file))
() = fclose (fp);
}
provide ("profile");