注:本文例子使用的是luajit2.0.5版本,原生lua版本可能有差异,但差异不大。

写在前面:

lua性能分析PepperfishProfiler挺好用的,主要灵活,分析的数据也清晰,有嵌套调用的耗时信息。

其他lua分析工具也可见。

本脚本的基于PepperfishProfiler做修改的。

为什么需要修改:

复杂的lua逻辑导致PepperfishProfiler分析结果不准确,

原因:lua的debug.sethook(hook_call, "cr" )的call和return没对应导致。而PepperfishProfiler没对这些情况做处理。

具体如图

注意FunD函数返回一个函数,导致debug信息中line:797函数名与代码中不一致,且return时只有一个FunD:797,其实是return了FunC,丢失的return FunD。

正文:

本工具的所有修改都是为了解决这个问题0.0。

本工具只适用于Call模式的分析。

对原工具的输出数据做了优化。

先上代码

--[[Example usage:profiler = newProfiler(false)profiler:start()< call some functions that take time >profiler:stop()local outfile = io.open( "profile.txt", "w+" )profiler:report( outfile )outfile:close()--]]--
-- All profiler related stuff is stored in the top level table '_profiler'
--
_profiler = {}--
-- newProfiler() creates a new profiler object for managing
-- the profiler and storing state.  Note that only one profiler
-- object can be executing at one time.
--
function newProfiler(bRecordCFun)if _profiler.running thenprint("Profiler already running.")returnendlocal newprof = {}for k,v in pairs(_profiler) donewprof[k] = vendnewprof.variant = "call"newprof.bRecordCFun = bRecordCFun==nil and true or false; --记录C方法return newprof
end--
-- This function starts the profiler.  It will do nothing
-- if this (or any other) profiler is already running.
--
function _profiler.start(self)if _profiler.running thenreturnend-- Start the profiler. This begins by setting up internal profiler state_profiler.running = selfself.rawstats = {}self.callstack = {}self.currPos_ar = nil; --定位当前执行函数self.cache_lineInfo = {};self.cache_startTime = os.clock();self.lineProfiler_cost_time = 0;debug.sethook( _profiler_hook_wrapper_by_call, "crl" )
end--
-- This function stops the profiler.  It will do nothing
-- if a profiler is not running, and nothing if it isn't
-- the currently running profiler.
--
function _profiler.stop(self)if _profiler.running ~= self thenreturnend-- Stop the profiler.debug.sethook( nil )_profiler.running = nilCppLog("[all profiler time]"..os.clock()-self.cache_startTime.."ms")-- CppLog("[lineProfiler_cost_time]"..self.lineProfiler_cost_time.."ms")
end--
-- Simple wrapper to handle the hook.  You should not
-- be calling this directly. Duplicated to reduce overhead.
--
function _profiler_hook_wrapper_by_call(action)if _profiler.running == nil thendebug.sethook( nil )end_profiler.running:_internal_profile_by_call(action)
end--
-- This is the main by-function-call function of the profiler and should not
-- be called except by the hook wrapper
--
function _profiler._internal_profile_by_call(self,action)-- Since we can obtain the 'function' for the item we've had call us, we-- can use that...local caller_info = debug.getinfo( 3 )if caller_info == nil thenprint "No caller_info"returnendif self.bRecordCFun == false and caller_info.what == "C" thenreturn;endself.profile_start_Time = os.clock();--[[caller_info:table: 0x00472a38 = {["linedefined"] = 797,["currentline"] = 797,["func"] = function: 0x0013dd50,["isvararg"] = false,["namewhat"] = "global"["lastlinedefined"] = 799,["source"] = "@GamePlay\ServerEntry.lua"["nups"] = 0,["what"] = "Lua"["nparams"] = 0,["name"] = "zxjFunD"["short_src"] = "GamePlay\ServerEntry.lua"}call return 不一致测试:function zxjFunA()for i = 1, 100000, 1 dolocal a = {}endendfunction zxjFunB()for i = 1, 100000, 1 dolocal a = {}endzxjFunA()endfunction zxjFunC()zxjFunB()endfunction zxjFunD()for i = 1, 100000, 1 dolocal a = {}endreturn zxjFunC() --直接return fun 会导致return 不一致endfunction zxjFunE()zxjFunD();endcall: zxjFunE();结果:[CppLog] Call callTimesss:1 func:function: 0x00b6a598 linedefined:806 funName:zxjFunE[CppLog][CppLog] Call callTimesss:2 func:function: 0x00b6a580 linedefined:800 funName:zxjFunD[CppLog][CppLog] Call callTimesss:3 func:function: 0x00b6a568 linedefined:797 funName:zxjFunD --此处call 的funName其实应该是zxjFunC[CppLog][CppLog] Call callTimesss:4 func:function: 0x00b6a550 linedefined:791 funName:zxjFunB[CppLog][CppLog] Call callTimesss:5 func:function: 0x00b6a538 linedefined:786 funName:zxjFunA[CppLog][CppLog] Return callTimesss:5 func:function: 0x00b6a538 linedefined:786 funName:zxjFunA[CppLog][CppLog] Return callTimesss:4 func:function: 0x00b6a550 linedefined:791 funName:zxjFunB[CppLog][CppLog] Return callTimesss:3 func:function: 0x00b6a568 linedefined:797 funName:zxjFunD --缺少一次return 本次return其实是zxjFunC[CppLog][CppLog] Return callTimesss:2 func:function: 0x00b6a598 linedefined:806 funName:zxjFunE]]if action == "line" thenself.cache_lineInfo.source = caller_info.source;  --记录最近一次逐行调用信息self.cache_lineInfo.line = caller_info.currentline;self.lineProfiler_cost_time = self.lineProfiler_cost_time + os.clock() - self.profile_start_Time;return;endCppLog("")-- CppLog(action.." name:"..caller_info.source.." "..caller_info.name..caller_info.linedefined.." currentline:"..caller_info.currentline)CppLog(action.." name:"..caller_info.name.." :"..caller_info.linedefined)if action == "call" then-- CppLog("[cache_lineInfo] source:"..self.cache_lineInfo.source.. " line:"..self.cache_lineInfo.line);-- add line profiler cost time to currPos_arif nil ~= self.currPos_ar thenself.currPos_ar.line_cost_time = self.currPos_ar.line_cost_time + self.lineProfiler_cost_time;self.lineProfiler_cost_time = 0;end-- TO: get realy parent function for this call-- 判定call的位置是否当前定位函数的内部--[[当前记录的执行函数不一定时此次call的父函数的原因: call和return的不一致eg:fun A()  B(); return 0; endfun B() return C()  endfun C() return 0;  endA();call A,call B,call C,return C,return A,加入D函数:fun A()  B(); D();return 0; endfun B() return C()  endfun C() return 0;  endfun D() return 0;  endA();call A,call B,call C,return C,no return B,call D,没执行该处理,则 D 的父函数视为B,但实际情况D的父函数可以为A]]while nil ~= self.currPos_ar and(self.cache_lineInfo.source ~= self.currPos_ar.source or(self.cache_lineInfo.line < self.currPos_ar.linedefined orself.cache_lineInfo.line > self.currPos_ar.lastlinedefined)) doself:_one_fun_return(self.currPos_ar.caller_info);endlocal latest_ar = self.currPos_ar-- Are we allowed to profile this function?local should_not_profile = 0for k,v in pairs(self.prevented_functions) doif k == caller_info.func thenshould_not_profile = vendend-- Also check the top activation record...if latest_ar thenif latest_ar.should_not_profile == 2 thenshould_not_profile = 2endend-- if latest_ar then--   CppLog("[parent]name:"..latest_ar.fun_name..latest_ar.linedefined);-- end-- Making a call...local this_ar = {}this_ar.should_not_profile = should_not_profilethis_ar.parent_ar = latest_arthis_ar.anon_child = 0this_ar.name_child = 0this_ar.children = {}this_ar.children_time = {}this_ar.clock_start = self.profile_start_Timethis_ar.func = caller_info.func;this_ar.source = caller_info.source;this_ar.linedefined = caller_info.linedefined;this_ar.lastlinedefined = caller_info.lastlinedefined;this_ar.fun_name = caller_info.name or tostring(caller_info.func);this_ar.caller_info = caller_info;this_ar.line_cost_time = 0; --行分析消耗时间-- Last thing to do on a call is to insert this onto the ar stack...table.insert( self.callstack, this_ar )-- CppLog("len:"..#self.callstack)--fun call update currposself.currPos_ar = this_ar;-- CppLog("[setCurPos2] name:"..self.currPos_ar.fun_name..self.currPos_ar.linedefined.."["..self.currPos_ar.linedefined..","..self.currPos_ar.lastlinedefined.."] source:"..self.currPos_ar.source)this_ar.proflier_time = os.clock() - self.profile_start_Time; --call 的分析消耗时间-- CppLog(this_ar.fun_name..this_ar.linedefined.." proflier_time:"..this_ar.proflier_time);elseif action == "return" then-- add line profiler cost time to currPos_arif nil ~= self.currPos_ar thenself.currPos_ar.line_cost_time = self.currPos_ar.line_cost_time + self.lineProfiler_cost_time;self.lineProfiler_cost_time = 0;end-- 因为call return 不一致-- 需要将跳过的return做计算for i = #self.callstack, 1, -1 dolocal this_ar = self.callstack[i];self:_one_fun_return(this_ar.caller_info);if this_ar.func == caller_info.func or this_ar.linedefined == caller_info.linedefined thenbreak;endendend
endfunction _profiler._one_fun_return(self,caller_info)if #self.callstack <= 0 thenreturn;endlocal profile_return_start_Time = self.profile_start_Time;local this_ar = table.remove(self.callstack,#self.callstack);this_ar.clock_end = self.profile_start_Timethis_ar.this_time = this_ar.clock_end - this_ar.clock_start- this_ar.proflier_time   --减去自身call 和子函数call、return 的分析消耗时间- this_ar.line_cost_time  --减去自身函数内部的逐行分析消耗时间-- CppLog("[one_fun_return]name:"..this_ar.fun_name..this_ar.linedefined)-- Now, if we have a parent, update its call info...if this_ar.parent_ar then-- CppLog("[parent]name:"..this_ar.parent_ar.fun_name..this_ar.parent_ar.linedefined)local funcStrKey = this_ar.fun_name.."_"..this_ar.linedefined;this_ar.parent_ar.children[funcStrKey] =(this_ar.parent_ar.children[funcStrKey] or 0) + 1this_ar.parent_ar.children_time[funcStrKey] =(this_ar.parent_ar.children_time[funcStrKey] or 0 ) +this_ar.this_timeif this_ar.fun_name == nil thenthis_ar.parent_ar.anon_child =this_ar.parent_ar.anon_child + this_ar.this_timeelsethis_ar.parent_ar.name_child =this_ar.parent_ar.name_child + this_ar.this_timeendend-- Now if we're meant to record information about ourselves, do so...if this_ar.should_not_profile == 0 thenlocal inforec = self:_get_func_rec(this_ar.fun_name,this_ar.linedefined,this_ar.func)inforec.count = inforec.count + 1inforec.time = inforec.time + this_ar.this_timeinforec.anon_child_time = inforec.anon_child_time + this_ar.anon_childinforec.name_child_time = inforec.name_child_time + this_ar.name_childinforec.func_info = this_ar.caller_infoinforec.parent_info = ""if this_ar.parent_ar theninforec.parent_info = this_ar.parent_ar.source.." ["..this_ar.parent_ar.fun_name.."]:"..this_ar.parent_ar.linedefinedendfor k,v in pairs(this_ar.children) doinforec.children[k] = (inforec.children[k] or 0) + vinforec.children_time[k] =(inforec.children_time[k] or 0) + this_ar.children_time[k]endend-- CppLog(this_ar.fun_name..this_ar.linedefined.." this_time:"..this_ar.this_time.." proflier_time:"..this_ar.proflier_time);if this_ar.parent_ar thenlocal nowTime = os.clock();this_ar.parent_ar.proflier_time = this_ar.parent_ar.proflier_time+ this_ar.proflier_time --子函数调用的分析消耗+ (nowTime - profile_return_start_Time) --这次 return 的分析耗时this_ar.parent_ar.line_cost_time = this_ar.parent_ar.line_cost_time + this_ar.line_cost_time --传递子函数的逐行分析耗时self.profile_start_Time = nowTime;  --避免重复计算消耗-- CppLog("[parent]"..this_ar.parent_ar.fun_name..this_ar.parent_ar.linedefined.." proflier_time:"..this_ar.parent_ar.proflier_time);end--fun return update currposself.currPos_ar = self.callstack[#self.callstack];-- if self.currPos_ar then--   CppLog("[setCurPos1] name:"..self.currPos_ar.fun_name..self.currPos_ar.linedefined.."["..self.currPos_ar.linedefined..","..self.currPos_ar.lastlinedefined.."] source:"..self.currPos_ar.source)-- end
endfunction _profiler._get_func_rec(self,funName,linedefined,func)-- Find the function ref for 'func' (if force and not present, create one)local ret = self.rawstats[funName.."_"..linedefined]if ret == nil then-- Build a new function statistics tableret = {}ret.func = funcret.count = 0ret.time = 0ret.anon_child_time = 0ret.name_child_time = 0ret.children = {}ret.children_time = {}self.rawstats[funName.."_"..linedefined] = retendreturn ret
end--
-- This writes a profile report to the output file object.  If
-- sort_by_total_time is nil or false the output is sorted by
-- the function time minus the time in it's children.
--
function _profiler.report( self, outfile, sort_by_total_time )-- table_print(self.rawstats)outfile:write[[Lua Profile output created by profiler.lua. Copyright Pepperfish 2002+]]-- This is pretty awful.local terms = {}if self.variant == "time" thenterms.capitalized = "Sample"terms.single = "sample"terms.pastverb = "sampled"elseif self.variant == "call" thenterms.capitalized = "Call"terms.single = "call"terms.pastverb = "called"elseassert(false)endlocal total_time = 0local ordering = {}for func,record in pairs(self.rawstats) dotable.insert(ordering, func)endif sort_by_total_time thentable.sort( ordering, function(a,b) return self.rawstats[a].time > self.rawstats[b].time end)elsetable.sort( ordering, function(a,b)local arec = self.rawstats[a] local brec = self.rawstats[b]local atime = arec.time - (arec.anon_child_time + arec.name_child_time)local btime = brec.time - (brec.anon_child_time + brec.name_child_time)return atime > btimeend)endfor i=1,table.getn(ordering) dolocal func = ordering[i]local record = self.rawstats[func]local thisfuncname = " " .. self:_pretty_name(func,true) .. " "-- if string.len( thisfuncname ) < 42 then--   thisfuncname =--     string.rep( "-", (42 - string.len(thisfuncname))/2 ) .. thisfuncname--   thisfuncname =--     thisfuncname .. string.rep( "-", 42 - string.len(thisfuncname) )-- endtotal_time = total_time + ( record.time - ( record.anon_child_time +record.name_child_time ) )outfile:write( "No."..i..string.rep( "-", 19 ) .. thisfuncname .. " [parent]" .. record.parent_info ..string.rep( "-", 19 ) .. "\n" )outfile:write( terms.capitalized.." count:         " ..string.format( "%4d", record.count ) .. "\n" )outfile:write( "Time spend total:       " ..string.format( "%4.3f", record.time ) .. "ms\n" )outfile:write( "Time spent in children: " ..string.format("%4.3f",record.anon_child_time+record.name_child_time) .."ms\n" )local timeinself =record.time - (record.anon_child_time + record.name_child_time)outfile:write( "Time spent in self:     " ..string.format("%4.3f", timeinself) .. "ms\n" )outfile:write( "Time spent per " .. terms.single .. ":  " ..string.format("%4.5f", record.time/record.count) .."ms/" .. terms.single .. "\n" )outfile:write( "Time spent in self per "..terms.single..": " ..string.format( "%4.5f", timeinself/record.count ) .. "ms/" ..terms.single.."\n" )-- Report on each child in the form-- Child  <funcname> called n times and took a.bslocal added_blank = 0local childrenlist = {} for k,v in pairs(record.children) doif self.prevented_functions[k] == nil orself.prevented_functions[k] == 0then-- if added_blank == 0 then--   outfile:write( "\n" ) -- extra separation line--   added_blank = 1-- endif record.children_time[k] ~= 0 thenlocal childrenInfo = {}childrenInfo.str =  "Child " .. self:_pretty_name(k) ..string.rep( " ", 41-string.len(self:_pretty_name(k)) ) .. " " ..terms.pastverb.." " .. string.format("%6d", v) .. " times. Took " ..string.format("%4.2f", record.children_time[k] ) .. "ms\n"childrenInfo.time = record.children_time[k];table.insert(childrenlist,childrenInfo);endendendtable.sort(childrenlist,function (a,b)return a.time > b.time;end)outfile:write( "\n" )for k, v in pairs(childrenlist) dooutfile:write(v.str);endoutfile:write( "\n" ) -- extra separation lineoutfile:flush()endoutfile:write( "\n\n" )outfile:write( "Total time spent in profiled functions: " ..string.format("%5.3g",total_time) .. "s\n" )outfile:write( [[END
]] )outfile:flush()
end--
-- This writes the profile to the output file object as
-- loadable Lua source.
--
function _profiler.lua_report(self,outfile)-- Purpose: Write out the entire raw state in a cross-referenceable form.local ordering = {}local functonum = {}for func,record in pairs(self.rawstats) dotable.insert(ordering, func)functonum[func] = table.getn(ordering)endoutfile:write("-- Profile generated by profiler.lua Copyright Pepperfish 2002+\n\n" )outfile:write( "-- Function names\nfuncnames = {}\n" )for i=1,table.getn(ordering) dolocal thisfunc = ordering[i]outfile:write( "funcnames[" .. i .. "] = " ..string.format("%q", self:_pretty_name(thisfunc)) .. "\n" )endoutfile:write( "\n" )outfile:write( "-- Function times\nfunctimes = {}\n" )for i=1,table.getn(ordering) dolocal thisfunc = ordering[i]local record = self.rawstats[thisfunc]outfile:write( "functimes[" .. i .. "] = { " )outfile:write( "tot=" .. record.time .. ", " )outfile:write( "achild=" .. record.anon_child_time .. ", " )outfile:write( "nchild=" .. record.name_child_time .. ", " )outfile:write( "count=" .. record.count .. " }\n" )endoutfile:write( "\n" )outfile:write( "-- Child links\nchildren = {}\n" )for i=1,table.getn(ordering) dolocal thisfunc = ordering[i]local record = self.rawstats[thisfunc]outfile:write( "children[" .. i .. "] = { " )for k,v in pairs(record.children) doif functonum[k] then -- non-recorded functions will be ignored nowoutfile:write( functonum[k] .. ", " )endendoutfile:write( "}\n" )endoutfile:write( "\n" )outfile:write( "-- Child call counts\nchildcounts = {}\n" )for i=1,table.getn(ordering) dolocal thisfunc = ordering[i]local record = self.rawstats[thisfunc]outfile:write( "children[" .. i .. "] = { " )for k,v in record.children doif functonum[k] then -- non-recorded functions will be ignored nowoutfile:write( v .. ", " )endendoutfile:write( "}\n" )endoutfile:write( "\n" )outfile:write( "-- Child call time\nchildtimes = {}\n" )for i=1,table.getn(ordering) dolocal thisfunc = ordering[i]local record = self.rawstats[thisfunc];outfile:write( "children[" .. i .. "] = { " )for k,v in pairs(record.children) doif functonum[k] then -- non-recorded functions will be ignored nowoutfile:write( record.children_time[k] .. ", " )endendoutfile:write( "}\n" )endoutfile:write( "\n\n-- That is all.\n\n" )outfile:flush()
end-- Internal function to calculate a pretty name for the profile output
function _profiler._pretty_name(self,func,bTitle)-- Only the data collected during the actual-- run seems to be correct.... why?local info = self.rawstats[ func ].func_info-- local info = debug.getinfo( func )local name = ""if info.what == "Lua" thenname = "L:"endif info.what == "C" thenname = "C:"endif info.what == "main" thenname = " :"endif info.name == nil thenname = name .. "<"..tostring(func) .. ">"elsename = name .. info.nameendif info.source thenname = name .. "@" .. info.sourceelseif info.what == "C" thenname = name .. "@?"elsename = name .. "@<string>"endendname = name .. ":"if info.what == "C" thenname = name .. "?"elsename = name .. info.linedefinedendif bTitle thenname = name .. " Root"endif info.name thenname = name .."  Fun:"..info.name;endreturn name
end--
-- This allows you to specify functions which you do
-- not want profiled.  Setting level to 1 keeps the
-- function from being profiled.  Setting level to 2
-- keeps both the function and its children from
-- being profiled.
--
-- BUG: 2 will probably act exactly like 1 in "time" mode.
-- If anyone cares, let me (zorba) know and it can be fixed.
--
function _profiler.prevent(self, func, level)self.prevented_functions[func] = (level or 1)
end_profiler.prevented_functions = {[_profiler.start] = 2,[_profiler.stop] = 2,[_profiler._internal_profile_by_call] = 2,[_profiler_hook_wrapper_by_call] = 2,[_profiler.prevent] = 2,[_profiler._get_func_rec] = 2,[_profiler.report] = 2,[_profiler.lua_report] = 2,[_profiler._pretty_name] = 2,
}local function _list_table(tb, table_list, level)local ret = ""local indent = string.rep(" ", level*4)for k, v in pairs(tb) dolocal quo = type(k) == "string" and "\"" or ""ret = ret .. indent .. "[" .. quo .. tostring(k) .. quo .. "] = "if type(v) == "table" thenlocal t_name = table_list[v]if t_name thenret = ret .. tostring(v) .. " -- > [\"" .. t_name .. "\"]\n"elsetable_list[v] = tostring(k)ret = ret .. "{\n"ret = ret .. _list_table(v, table_list, level+1)ret = ret .. indent .. "}\n"endelseif type(v) == "string" thenret = ret .. "\"" .. tostring(v) .. "\"\n"elseret = ret .. tostring(v) .. ",\n"endendlocal mt = getmetatable(tb)if mt then ret = ret .. "\n"local t_name = table_list[mt]ret = ret .. indent .. "<metatable> = "if t_name thenret = ret .. tostring(mt) .. " -- > [\"" .. t_name .. "\"]\n"elseret = ret .. "{\n"ret = ret .. _list_table(mt, table_list, level+1)ret = ret .. indent .. "}\n"endendreturn ret
end-------------------------------------------------------------------
-- Public functions
-------------------------------------------------------------------function table_tostring(tb)--print("table_tostring")if type(tb) ~= "table" thenprint("Sorry, it's not table, it is " .. type(tb) .. ".","table_Print")endlocal ret = " = {\n"local table_list = {}table_list[tb] = "root table"ret = ret .. _list_table(tb, table_list, 1)ret = ret .. "}"return ret
endfunction table_print(tb)if tb == nil thenprint("table_Print {}","table_Print")returnendif CppLog thenCppLog(tostring(tb) .. table_tostring(tb))end-- print(tostring(tb) .. table_tostring(tb))
end

主要对_internal_profile_by_call做了修改,

增加逐行勾子(测试逐行监听只会增加小量的时间消耗),

增加了定位当前执行函数 currPos_ar ,为了准确定位函数调用的嵌套关系,

增加了call、return、line分析的耗时记录,减少因分析带来的时间消耗影响分析结果,但无法做到全部消耗时间剔除,数据结果的时间值只适用于耗时比例分析,无法做为时间的运行时间。

修改_get_func_rec的匹配条件

注:毫秒单位其实有点大,建议重写os.clock 返回微妙,提供准确性

注:CppLog 是自己封装的log,其实就是print

注释已经写了一些调试信息和思路,这里做一些补充:

1.为什么要增加逐行勾子:

为了记录call的最后一次行数调用的信息cache_lineInfo,可以确定ache_lineInfo一定是本次call的父函数,

根据这个信息和调用堆栈callstack,可以找到本次call的父函数,并且能确定该父函数前的其他堆栈函数已经执行结束,但是没有返回return勾子,所以将这些行数做为return处理。

2.call 、return 都有_one_fun_return操作

记录的执行函数 currPos_ar 每次call是前移到当前call上,同时调用堆栈callstack Add,

return时后退,同时调用堆栈callstack Remove。

在return丢失,我们当前记录的执行函数 currPos_ar和调用堆栈callstack其实不准确(少后退和少移除),

return丢失后的下一次操作可以是call和return,所以都要做执行函数 currPos_ar 校准操作,将少后退的数据做return处理。

(1、2条其实做同一件事)

结束:

脚本经过我的反复调试和修改,不那么美观,也懒得整理,希望帮助到你

结果展示:

找到你report的文件位置,打开会看到如图:

还是不错的

---------------------------------------2021.5.7----------------------

使用一段时间后,对脚本做了修改优化

--[[Example usage:profiler = newProfiler(false)profiler:start()< call some functions that take time >profiler:stop()local outfile = io.open( "profile.txt", "w+" )profiler:report( outfile )outfile:close()--]]--
-- All profiler related stuff is stored in the top level table '_profiler'
--
_profiler = {}local bZLog = false; --zLog 是否打印
local bCppLogWrite = false; --CppLog 打印是否写文件
local cppLogWriteOutPath = "PepperfishProfiler_CppLog.txt"---tool fun
local table_print,CppLogInit,zLog,CppLogEnd,_CppLog;--
-- newProfiler() creates a new profiler object for managing
-- the profiler and storing state.  Note that only one profiler
-- object can be executing at one time.
--
function newProfiler(bRecordCFun)if _profiler.running thenprint("Profiler already running.")returnendlocal newprof = {}for k,v in pairs(_profiler) donewprof[k] = vendnewprof.variant = "call"newprof.bRecordCFun = bRecordCFun==nil and true or false; --记录C方法return newprof
end--
-- This function starts the profiler.  It will do nothing
-- if this (or any other) profiler is already running.
--
function _profiler.start(self)if _profiler.running thenreturnend-- Start the profiler. This begins by setting up internal profiler state_profiler.running = selfself.rawstats = {}self.callstack = {}self.currPos_ar = nil; --定位当前执行函数self.cache_lineInfo = {};self.cache_startTime = os.clock();self.lineProfiler_cost_time = 0;self.all_profile_cost_Time = 0;debug.sethook( _profiler_hook_wrapper_by_call, "crl" )if bCppLogWrite thenCppLogInit();end
end--
-- This function stops the profiler.  It will do nothing
-- if a profiler is not running, and nothing if it isn't
-- the currently running profiler.
--
function _profiler.stop(self)if _profiler.running ~= self thenreturnend-- Stop the profiler.debug.sethook( nil )_profiler.running = nilCppLog("[all profiler time]"..os.clock()-self.cache_startTime.."ms")CppLog("[all_profile_cost_Time] "..self.all_profile_cost_Time);-- CppLog("[lineProfiler_cost_time]"..self.lineProfiler_cost_time.."ms")
end--
-- Simple wrapper to handle the hook.  You should not
-- be calling this directly. Duplicated to reduce overhead.
--
function _profiler_hook_wrapper_by_call(action)if _profiler.running == nil thendebug.sethook( nil )end_profiler.running.profile_start_Time = os.clock();local t = os.clock();_profiler.running:_internal_profile_by_call(action)_profiler.running.all_profile_cost_Time = _profiler.running.all_profile_cost_Time + os.clock() - t;
end--
-- This is the main by-function-call function of the profiler and should not
-- be called except by the hook wrapper
--
function _profiler._internal_profile_by_call(self,action)-- Since we can obtain the 'function' for the item we've had call us, we-- can use that...local caller_info = debug.getinfo( 3 )if caller_info == nil thenprint "No caller_info"self.lineProfiler_cost_time = self.lineProfiler_cost_time + os.clock() - self.profile_start_Time;returnendif self.bRecordCFun == false and caller_info.what == "C" thenself.lineProfiler_cost_time = self.lineProfiler_cost_time + os.clock() - self.profile_start_Time;return;endif caller_info.name == nil then-- 这种情况太复杂,暂时忽略self.lineProfiler_cost_time = self.lineProfiler_cost_time + os.clock() - self.profile_start_Time;return;end--[[caller_info:table: 0x00472a38 = {["linedefined"] = 797,["currentline"] = 797,["func"] = function: 0x0013dd50,["isvararg"] = false,["namewhat"] = "global"["lastlinedefined"] = 799,["source"] = "@GamePlay\ServerEntry.lua"["nups"] = 0,["what"] = "Lua"["nparams"] = 0,["name"] = "zxjFunD"["short_src"] = "GamePlay\ServerEntry.lua"}call return 不一致测试:function zxjFunA()for i = 1, 100000, 1 dolocal a = {}endendfunction zxjFunB()for i = 1, 100000, 1 dolocal a = {}endzxjFunA()endfunction zxjFunC()zxjFunB()endfunction zxjFunD()for i = 1, 100000, 1 dolocal a = {}endreturn zxjFunC() --直接return fun 会导致return 不一致endfunction zxjFunE()zxjFunD();endcall: zxjFunE();结果:[CppLog] Call callTimesss:1 func:function: 0x00b6a598 linedefined:806 funName:zxjFunE[CppLog][CppLog] Call callTimesss:2 func:function: 0x00b6a580 linedefined:800 funName:zxjFunD[CppLog][CppLog] Call callTimesss:3 func:function: 0x00b6a568 linedefined:797 funName:zxjFunD --此处call 的funName其实应该是zxjFunC[CppLog][CppLog] Call callTimesss:4 func:function: 0x00b6a550 linedefined:791 funName:zxjFunB[CppLog][CppLog] Call callTimesss:5 func:function: 0x00b6a538 linedefined:786 funName:zxjFunA[CppLog][CppLog] Return callTimesss:5 func:function: 0x00b6a538 linedefined:786 funName:zxjFunA[CppLog][CppLog] Return callTimesss:4 func:function: 0x00b6a550 linedefined:791 funName:zxjFunB[CppLog][CppLog] Return callTimesss:3 func:function: 0x00b6a568 linedefined:797 funName:zxjFunD --缺少一次return 本次return其实是zxjFunC[CppLog][CppLog] Return callTimesss:2 func:function: 0x00b6a598 linedefined:806 funName:zxjFunE]]if action == "line" thenself.cache_lineInfo.source = caller_info.source;  --记录最近一次逐行调用信息self.cache_lineInfo.line = caller_info.currentline;self.lineProfiler_cost_time = self.lineProfiler_cost_time + os.clock() - self.profile_start_Time;return;endzLog("")zLog(action.." name:"..caller_info.source.." "..caller_info.name..caller_info.linedefined.." currentline:"..caller_info.currentline)if action == "call" thenzLog("[cache_lineInfo] source:"..self.cache_lineInfo.source.. " line:"..self.cache_lineInfo.line);-- add line profiler cost time to currPos_arif nil ~= self.currPos_ar thenself.currPos_ar.line_cost_time = self.currPos_ar.line_cost_time + self.lineProfiler_cost_time;self.lineProfiler_cost_time = 0;end-- TO: get realy parent function for this call-- 判定call的位置是否当前定位函数的内部--[[当前记录的执行函数不一定时此次call的父函数的原因: call和return的不一致eg:fun A()  B(); return 0; endfun B() return C()  endfun C() return 0;  endA();call A,call B,call C,return C,return A,加入D函数:fun A()  B(); D();return 0; endfun B() return C()  endfun C() return 0;  endfun D() return 0;  endA();call A,call B,call C,return C,no return B,call D,没执行该处理,则 D 的父函数视为B,但实际情况D的父函数可以为A]]while nil ~= self.currPos_ar and(self.cache_lineInfo.source ~= self.currPos_ar.source or(self.cache_lineInfo.line < self.currPos_ar.linedefined orself.cache_lineInfo.line > self.currPos_ar.lastlinedefined)) dozLog("[cache_lineInfo Data] source:"..self.cache_lineInfo.source.." line:"..self.cache_lineInfo.line.."\n\t".."[currPos_ar Data:] source:"..self.currPos_ar.source.." linedefined:"..self.currPos_ar.linedefined.." lastlinedefined:"..self.currPos_ar.lastlinedefined)self:_one_fun_return(self.currPos_ar.caller_info);endlocal latest_ar = self.currPos_ar-- Are we allowed to profile this function?local should_not_profile = 0for k,v in pairs(self.prevented_functions) doif k == caller_info.func thenshould_not_profile = vendend-- Also check the top activation record...if latest_ar thenif latest_ar.should_not_profile == 2 thenshould_not_profile = 2endendif latest_ar thenzLog("[parent]name:"..latest_ar.fun_name..latest_ar.linedefined);end-- Making a call...local this_ar = {}this_ar.should_not_profile = should_not_profilethis_ar.parent_ar = latest_arthis_ar.anon_child = 0this_ar.name_child = 0this_ar.children = {}this_ar.children_time = {}this_ar.clock_start = self.profile_start_Timethis_ar.func = caller_info.func;this_ar.source = caller_info.source;this_ar.linedefined = caller_info.linedefined;this_ar.lastlinedefined = caller_info.lastlinedefined;this_ar.fun_name = caller_info.name or tostring(caller_info.func);this_ar.caller_info = caller_info;this_ar.line_cost_time = 0; --行分析消耗时间-- Last thing to do on a call is to insert this onto the ar stack...table.insert( self.callstack, this_ar )--fun call update currposself.currPos_ar = this_ar;this_ar.proflier_time = os.clock() - self.profile_start_Time; --call 的分析消耗时间elseif action == "return" then-- add line profiler cost time to currPos_arif nil ~= self.currPos_ar thenself.currPos_ar.line_cost_time = self.currPos_ar.line_cost_time + self.lineProfiler_cost_time;self.lineProfiler_cost_time = 0;end-- 因为call return 不一致-- 需要将跳过的return做计算for i = #self.callstack, 1, -1 dolocal this_ar = self.callstack[i];self:_one_fun_return(this_ar.caller_info);if this_ar.func == caller_info.func or this_ar.linedefined == caller_info.linedefined thenbreak;endendend
endfunction _profiler._one_fun_return(self,caller_info)if #self.callstack <= 0 thenreturn;endlocal profile_return_start_Time = self.profile_start_Time;local this_ar = table.remove(self.callstack,#self.callstack);this_ar.clock_end = self.profile_start_Timethis_ar.this_time = this_ar.clock_end - this_ar.clock_start- this_ar.proflier_time   --减去自身call 和子函数call、return 的分析消耗时间- this_ar.line_cost_time  --减去自身函数内部的逐行分析消耗时间zLog("[one_fun_return]name:"..this_ar.fun_name..this_ar.linedefined)-- Now, if we have a parent, update its call info...if this_ar.parent_ar thenzLog("[one_fun_return parent]name:"..this_ar.parent_ar.fun_name..this_ar.parent_ar.linedefined)local funcStrKey = this_ar.fun_name.."_"..this_ar.linedefined.."_"..this_ar.source;this_ar.parent_ar.children[funcStrKey] =(this_ar.parent_ar.children[funcStrKey] or 0) + 1this_ar.parent_ar.children_time[funcStrKey] =(this_ar.parent_ar.children_time[funcStrKey] or 0 ) +this_ar.this_timeif this_ar.fun_name == nil thenthis_ar.parent_ar.anon_child =this_ar.parent_ar.anon_child + this_ar.this_timeelsethis_ar.parent_ar.name_child =this_ar.parent_ar.name_child + this_ar.this_timeendend-- Now if we're meant to record information about ourselves, do so...if this_ar.should_not_profile == 0 thenlocal inforec = self:_get_func_rec(this_ar.fun_name,this_ar.linedefined,this_ar.func,this_ar.source)inforec.count = inforec.count + 1inforec.time = inforec.time + this_ar.this_timeinforec.anon_child_time = inforec.anon_child_time + this_ar.anon_childinforec.name_child_time = inforec.name_child_time + this_ar.name_childinforec.func_info = this_ar.caller_infoinforec.parent_info = ""if this_ar.parent_ar theninforec.parent_info = this_ar.parent_ar.source.." ["..this_ar.parent_ar.fun_name.."]:"..this_ar.parent_ar.linedefinedendfor k,v in pairs(this_ar.children) doinforec.children[k] = (inforec.children[k] or 0) + vinforec.children_time[k] =(inforec.children_time[k] or 0) + this_ar.children_time[k]endendif this_ar.parent_ar thenlocal nowTime = os.clock();this_ar.parent_ar.proflier_time = this_ar.parent_ar.proflier_time+ this_ar.proflier_time --子函数调用的分析消耗+ (nowTime - profile_return_start_Time) --这次 return 的分析耗时this_ar.parent_ar.line_cost_time = this_ar.parent_ar.line_cost_time + this_ar.line_cost_time --传递子函数的逐行分析耗时self.profile_start_Time = nowTime;  --避免重复计算消耗end--fun return update currposself.currPos_ar = self.callstack[#self.callstack];
endfunction _profiler._get_func_rec(self,funName,linedefined,func,source)-- Find the function ref for 'func' (if force and not present, create one)local ret = self.rawstats[funName.."_"..linedefined.."_"..source]if ret == nil then-- Build a new function statistics tableret = {}ret.func = funcret.count = 0ret.time = 0ret.anon_child_time = 0ret.name_child_time = 0ret.children = {}ret.children_time = {}self.rawstats[funName.."_"..linedefined.."_"..source] = retendreturn ret
end--
-- This writes a profile report to the output file object.  If
-- sort_by_total_time is nil or false the output is sorted by
-- the function time minus the time in it's children.
--
function _profiler.report( self, outfile, sort_by_total_time )-- table_print(self.rawstats)outfile:write[[Lua Profile output created by profiler.lua. Copyright Pepperfish 2002+]]-- This is pretty awful.local terms = {}if self.variant == "time" thenterms.capitalized = "Sample"terms.single = "sample"terms.pastverb = "sampled"elseif self.variant == "call" thenterms.capitalized = "Call"terms.single = "call"terms.pastverb = "called"elseassert(false)endlocal total_time = 0local ordering = {}for func,record in pairs(self.rawstats) dotable.insert(ordering, func)endif sort_by_total_time thentable.sort( ordering, function(a,b) return self.rawstats[a].time > self.rawstats[b].time end)elsetable.sort( ordering, function(a,b)local arec = self.rawstats[a] local brec = self.rawstats[b]local atime = arec.time - (arec.anon_child_time + arec.name_child_time)local btime = brec.time - (brec.anon_child_time + brec.name_child_time)return atime > btimeend)endfor i=1,table.getn(ordering) dolocal func = ordering[i]local record = self.rawstats[func]local thisfuncname = " " .. self:_pretty_name(func,true) .. " "-- if string.len( thisfuncname ) < 42 then--   thisfuncname =--     string.rep( "-", (42 - string.len(thisfuncname))/2 ) .. thisfuncname--   thisfuncname =--     thisfuncname .. string.rep( "-", 42 - string.len(thisfuncname) )-- endtotal_time = total_time + ( record.time - ( record.anon_child_time +record.name_child_time ) )outfile:write( "No."..i..string.rep( "-", 19 ) .. thisfuncname .. " [parent]" .. record.parent_info ..string.rep( "-", 19 ) .. "\n" )outfile:write( terms.capitalized.." count:         " ..string.format( "%4d", record.count ) .. "\n" )outfile:write( "Time spend total:       " ..string.format( "%4.3f", record.time ) .. "ms\n" )outfile:write( "Time spent in children: " ..string.format("%4.3f",record.anon_child_time+record.name_child_time) .."ms\n" )local timeinself =record.time - (record.anon_child_time + record.name_child_time)outfile:write( "Time spent in self:     " ..string.format("%4.3f", timeinself) .. "ms\n" )outfile:write( "Time spent per " .. terms.single .. ":  " ..string.format("%4.5f", record.time/record.count) .."ms/" .. terms.single .. "\n" )outfile:write( "Time spent in self per "..terms.single..": " ..string.format( "%4.5f", timeinself/record.count ) .. "ms/" ..terms.single.."\n" )-- Report on each child in the form-- Child  <funcname> called n times and took a.bslocal added_blank = 0local childrenlist = {} for k,v in pairs(record.children) doif self.prevented_functions[k] == nil orself.prevented_functions[k] == 0then-- if added_blank == 0 then--   outfile:write( "\n" ) -- extra separation line--   added_blank = 1-- end-- if record.children_time[k] ~= 0 thenlocal childrenInfo = {}childrenInfo.str =  "Child " .. self:_pretty_name(k) ..string.rep( " ", 41-string.len(self:_pretty_name(k)) ) .. " " ..terms.pastverb.." " .. string.format("%6d", v) .. " times. Took " ..string.format("%4.2f", record.children_time[k] ) .. "ms\n"childrenInfo.time = record.children_time[k];table.insert(childrenlist,childrenInfo);-- endendendtable.sort(childrenlist,function (a,b)return a.time > b.time;end)outfile:write( "\n" )for k, v in pairs(childrenlist) dooutfile:write(v.str);endoutfile:write( "\n" ) -- extra separation lineoutfile:flush()endoutfile:write( "\n\n" )outfile:write( "Total time spent in profiled functions: " ..string.format("%5.3g",total_time) .. "s\n" )outfile:write( [[END
]] )outfile:flush()if bCppLogWrite thenCppLogEnd();end
end--
-- This writes the profile to the output file object as
-- loadable Lua source.
--
function _profiler.lua_report(self,outfile)-- Purpose: Write out the entire raw state in a cross-referenceable form.local ordering = {}local functonum = {}for func,record in pairs(self.rawstats) dotable.insert(ordering, func)functonum[func] = table.getn(ordering)endoutfile:write("-- Profile generated by profiler.lua Copyright Pepperfish 2002+\n\n" )outfile:write( "-- Function names\nfuncnames = {}\n" )for i=1,table.getn(ordering) dolocal thisfunc = ordering[i]outfile:write( "funcnames[" .. i .. "] = " ..string.format("%q", self:_pretty_name(thisfunc)) .. "\n" )endoutfile:write( "\n" )outfile:write( "-- Function times\nfunctimes = {}\n" )for i=1,table.getn(ordering) dolocal thisfunc = ordering[i]local record = self.rawstats[thisfunc]outfile:write( "functimes[" .. i .. "] = { " )outfile:write( "tot=" .. record.time .. ", " )outfile:write( "achild=" .. record.anon_child_time .. ", " )outfile:write( "nchild=" .. record.name_child_time .. ", " )outfile:write( "count=" .. record.count .. " }\n" )endoutfile:write( "\n" )outfile:write( "-- Child links\nchildren = {}\n" )for i=1,table.getn(ordering) dolocal thisfunc = ordering[i]local record = self.rawstats[thisfunc]outfile:write( "children[" .. i .. "] = { " )for k,v in pairs(record.children) doif functonum[k] then -- non-recorded functions will be ignored nowoutfile:write( functonum[k] .. ", " )endendoutfile:write( "}\n" )endoutfile:write( "\n" )outfile:write( "-- Child call counts\nchildcounts = {}\n" )for i=1,table.getn(ordering) dolocal thisfunc = ordering[i]local record = self.rawstats[thisfunc]outfile:write( "children[" .. i .. "] = { " )for k,v in record.children doif functonum[k] then -- non-recorded functions will be ignored nowoutfile:write( v .. ", " )endendoutfile:write( "}\n" )endoutfile:write( "\n" )outfile:write( "-- Child call time\nchildtimes = {}\n" )for i=1,table.getn(ordering) dolocal thisfunc = ordering[i]local record = self.rawstats[thisfunc];outfile:write( "children[" .. i .. "] = { " )for k,v in pairs(record.children) doif functonum[k] then -- non-recorded functions will be ignored nowoutfile:write( record.children_time[k] .. ", " )endendoutfile:write( "}\n" )endoutfile:write( "\n\n-- That is all.\n\n" )outfile:flush()
end-- Internal function to calculate a pretty name for the profile output
function _profiler._pretty_name(self,func,bTitle)-- Only the data collected during the actual-- run seems to be correct.... why?local info = self.rawstats[ func ].func_info-- local info = debug.getinfo( func )local name = ""if info.what == "Lua" thenname = "L:"endif info.what == "C" thenname = "C:"endif info.what == "main" thenname = " :"endif info.name == nil thenname = name .. "<"..tostring(func) .. ">"elsename = name .. info.nameendif info.source thenname = name .. "@" .. info.sourceelseif info.what == "C" thenname = name .. "@?"elsename = name .. "@<string>"endendname = name .. ":"if info.what == "C" thenname = name .. "?"elsename = name .. info.linedefinedendif bTitle thenname = name .. " Root"endif info.name thenname = name .."  Fun:"..info.name;endreturn name
end--
-- This allows you to specify functions which you do
-- not want profiled.  Setting level to 1 keeps the
-- function from being profiled.  Setting level to 2
-- keeps both the function and its children from
-- being profiled.
--
-- BUG: 2 will probably act exactly like 1 in "time" mode.
-- If anyone cares, let me (zorba) know and it can be fixed.
--
function _profiler.prevent(self, func, level)self.prevented_functions[func] = (level or 1)
end_profiler.prevented_functions = {[_profiler.start] = 2,[_profiler.stop] = 2,[_profiler._internal_profile_by_call] = 2,[_profiler_hook_wrapper_by_call] = 2,[_profiler.prevent] = 2,[_profiler._get_func_rec] = 2,[_profiler.report] = 2,[_profiler.lua_report] = 2,[_profiler._pretty_name] = 2,
}-------------------------------------------------------------------
-- Tool functions
-------------------------------------------------------------------local function _list_table(tb, table_list, level)local ret = ""local indent = string.rep(" ", level*4)for k, v in pairs(tb) dolocal quo = type(k) == "string" and "\"" or ""ret = ret .. indent .. "[" .. quo .. tostring(k) .. quo .. "] = "if type(v) == "table" thenlocal t_name = table_list[v]if t_name thenret = ret .. tostring(v) .. " -- > [\"" .. t_name .. "\"]\n"elsetable_list[v] = tostring(k)ret = ret .. "{\n"ret = ret .. _list_table(v, table_list, level+1)ret = ret .. indent .. "}\n"endelseif type(v) == "string" thenret = ret .. "\"" .. tostring(v) .. "\"\n"elseret = ret .. tostring(v) .. ",\n"endendlocal mt = getmetatable(tb)if mt then ret = ret .. "\n"local t_name = table_list[mt]ret = ret .. indent .. "<metatable> = "if t_name thenret = ret .. tostring(mt) .. " -- > [\"" .. t_name .. "\"]\n"elseret = ret .. "{\n"ret = ret .. _list_table(mt, table_list, level+1)ret = ret .. indent .. "}\n"endendreturn ret
endlocal function table_tostring(tb)--print("table_tostring")if type(tb) ~= "table" thenprint("Sorry, it's not table, it is " .. type(tb) .. ".","table_Print")endlocal ret = " = {\n"local table_list = {}table_list[tb] = "root table"ret = ret .. _list_table(tb, table_list, 1)ret = ret .. "}"return ret
endtable_print = function (tb)if tb == nil thenprint("table_Print {}","table_Print")returnendif zLog thenzLog(tostring(tb) .. table_tostring(tb))end-- print(tostring(tb) .. table_tostring(tb))
endlocal cppLogStr = ""CppLogInit = function()cppLogStr = ""
end_CppLog = function(str)cppLogStr = cppLogStr .."\n".. str;
endzLog = function(str)
endCppLogEnd = function()local outfile = io.open(cppLogWriteOutPath,"w+");outfile:write(cppLogStr)outfile:flush()
endif bCppLogWrite thenCppLog = _CppLog;_profiler.prevented_functions[CppLog] = 2;_profiler.prevented_functions[CppLogInit] = 2;_profiler.prevented_functions[CppLogEnd] = 2;
endif bZLog thenzLog = CppLog
end

Lua Profiler 工具(基于PepperfishProfiler 修改)相关推荐

  1. 【放置江湖】LUA手游 基于HOOK 解密修改流程

    1.下载拿到<放置江湖> apk 后我们第一步检查游戏框架,直接查看 \lib 目录里面的 so就可以了.   很明显这也是一款基于cocos2dlua 开发的游戏.接着我们查看,他的lu ...

  2. Lua Profiler——快速定位Lua性能问题

    导读 随着Lua在项目中的大量使用,它所带来的性能问题也逐步成为了项目运行时的重大性能瓶颈之一.特别是内存相关的性能问题,无论是内存分配过大还是内存泄露无法回收,目前都已经在不少研发项目中集中爆发. ...

  3. SQL Server Profiler工具

    SQL Server Profiler工具 原文:SQL Server Profiler工具 一.SQL Profiler工具简介 SQL Profiler是一个图形界面和一组系统存储过程,其作用如下 ...

  4. 文件批量重命名工具,批量修改文件名的实现思路

    在工作中可能会遇到文件数据成果已经制作完成后,遇到文件命名规则变更,需要对大量文件重命名,甚至修改目录结构的.本文介绍利用FME实现文件批量重命名的解决方法. 因为工作实际情况各不相同,文件重命名规则 ...

  5. 快手视频伪原创工具 视频怎么修改m5d

             快手视频伪原创工具 视频怎么修改m5d          保证视频时长                      在短视频操作中,视频修改的作用显得尤为重要,这里介绍几款常见的工具 ...

  6. android profiler 简书,使用AndroidStudio提供的Android Profiler工具和mat进行内存泄漏分析...

    废话不多说直接说流程 给项目中集成LeakCanary工具进行内存泄漏检测.发现有内存泄漏后该工具会进行提示 有内存泄露后我们需要使用as的profiler工具进行分析并获取到.hprof文件,步骤如 ...

  7. 【Android CPU 优化】Android CPU 调优 ( Trace 文件分析 | Android Profiler 工具 | CPU Profiler 工具 )

    文章目录 一.Android CPU 优化 二.CPU Profiler 工具 三.相关资源 一.Android CPU 优化 在 Android 中 , 出现 动画掉帧 , 页面切换白屏 , 卡顿 ...

  8. 【Android 内存优化】Android Profiler 工具常用功能 ( 监测内存 | 内存快照 )

    文章目录 一. 内存泄漏排查 ( Android Profiler 工具 ) 二. Android Profiler 内存监测相关功能 三.内存快照分析 内存泄漏原理 : 长生命周期对象 , 持有短生 ...

  9. PROFILER 技术总结(二): 利用Monte Carlo Profiler 工具

    之前提到的利用微秒级精度定时器的方法测试程序的运行的效率的方法,它主要比较适于测试程序的顺序运行(如音频.视频解码效率,函数调用等),对于经常发生中断的程序和另外开辟线程连续性测试效率不是很好.因而对 ...

最新文章

  1. 1.10 卷积神经网络示例-深度学习第四课《卷积神经网络》-Stanford吴恩达教授
  2. 关于SmartForm和ScriptForm的输出格式设置说明(转载)
  3. numa对MySQL多实例性能影响
  4. DTCC 2020 | 阿里云叶正盛:数据库2025
  5. html5音频文件生成波形图代码,HTML5/D3.js 可视音频波形柱状图
  6. cdockpane限制调整大小_影视后期制作小伙伴必看:使用AU对声音质量进行调整的三大技巧...
  7. 深度linux登录后界面卡死,Deepin Linux 15(.1)启动即卡死的问题
  8. android浏览器插件开发,【转】Chrome扩展开发自己的浏览器插件
  9. Mac安装Lingo
  10. 1.list倒叙输出
  11. JDK1.8u162以及JDK1.8所有历史版本官网下载地址
  12. springboot基于微信小程序的在线办公系统+java+uinapp+Mysql
  13. Mac下mysql安装,MySQLclient
  14. 无盘服务器磁盘缓存,网众无盘教程 教你挂盘设置缓存
  15. ps还原上一步快捷键,ps还原上一步快捷键_photoshop恢复上一步操作的快捷键是什么...
  16. python 任务管理系统_python bottle框架开发任务管理系统 V_1.0版
  17. get请求获取不到参数
  18. 网络技术交流QQ群:46855032
  19. 1069. The Black Hole of Numbers (20)
  20. 递增递减运算符 详解(++、--)

热门文章

  1. 股票成本价买入价计算器 V1.2
  2. [20180224]理解exp direct导出操作.txt
  3. 怎样在家远程访问单位的电脑呢 有这两种方法
  4. windows10更新体验windows11
  5. 西门子200高数计数器读取增量式编码器数值
  6. python开发程序知道微信好友是否已读信息吗_微商成功神器,python程序员教你,一键分析微信好友近期所有信息...
  7. 协程的原理和应用,C++现实协程
  8. 这7位图灵奖得主,竟然今日才入选ACM Fellow,他们可是程序员“祖师爷”
  9. 基于PHP的网上租房售房系统设计与实现
  10. 安装postgre遇到安装失败,或者端口未监听错误排查