30、Erlang代码分析与调试全攻略

Erlang代码分析与调试全攻略

1. 分析与性能分析工具

在开发过程中,我们常常需要深入了解运行中的程序,查找潜在问题代码、调查性能问题以及检查死代码或已弃用函数的使用情况。以下是一些相关的工具和方法。

1.1 覆盖率分析

在测试代码时,我们不仅想知道哪些代码行被频繁执行,还想知道哪些代码行从未被执行。未执行的代码行可能是错误的潜在来源,因此找出这些代码行非常重要。我们可以使用覆盖率分析器来完成这项工作。

以下是一个覆盖率分析的示例:

1> cover:start().
%% start the coverage analyser
{ok,<0.34.0>}
2> cover:compile(shout).
%% compile shout.erl for coverage
{ok,shout}
3> shout:start().
%% run the program
<0.41.0>
Playing:<<"title: track018 performer: .. ">>
4> %% let the program run for a bit
4>
cover:analyse_to_file(shout).
%% analyse the results
{ok,"shout.COVER.out"}
%% this is the results file

覆盖率分析的结果会输出到一个文件中,文件左侧会显示每行代码的执行次数。标记为零的行特别值得关注,因为这些代码从未被执行,我们无法确定程序在这些部分是否正确。设计测试用例使所有覆盖率计数大于零是系统地查找程序中隐藏故障的有效方法。

1.2 性能分析工具

标准的Erlang发行版提供了三种性能分析工具:
| 工具名称 | 功能描述 | 适用场景 | 系统负载 |
| ---- | ---- | ---- | ---- |
| cprof | 统计每个函数的调用次数,轻量级分析器 | 实时系统 | 增加5% - 10%系统负载 |
| fprof | 显示调用和被调用函数的时间,输出到文件 | 实验室或模拟系统中的大型系统分析 | 显著增加系统负载 |
| eprof | 测量Erlang程序的时间使用情况,fprof的前身 | 小规模分析 | - |

以下是运行cprof的示例:

1> cprof:start().
%% start the profiler
4501
2> shout:start().
%% run the application
<0.35.0>
3> cprof:pause().
%% pause the profiler
4844
4> cprof:analyse(shout). %% analyse function calls
{shout,232,
[{{shout,split,2},73},
{{shout,write_data,4},33},
{{shout,the_header,1},33},
{{shout,send_file,6},33},
{{shout,bump,1},32},
{{shout,make_header1,1},5},
{{shout,'-got_request_from_client/3-fun-0-',1},4},
{{shout,songs_loop,1},2},
{{shout,par_connect,2},2},
{{shout,unpack_song_descriptor,1},1},
...
5> cprof:stop().
%% stop the profiler
4865

此外, cprof:analyse() 可以分析所有已收集统计信息的模块。更多关于cprof的详细信息,请参考 http://www.erlang.org/doc/man/cprof.html

1.3 xref交叉引用

可以使用xref模块生成交叉引用。xref仅在代码使用 debug_info 标志编译时有效。在开发程序时,偶尔对代码进行交叉引用检查是个好主意。

以下是对一个示例项目进行交叉引用检查的步骤:
1. 进入项目目录:

$ cd /home/joe/2007/vsg-1.6
  1. 删除所有 .beam 文件:
$ rm *.beam
  1. 使用 debug_info 标志编译所有 .erl 文件:
$ erlc +debug_info *.erl
  1. 启动Erlang shell并运行交叉引用检查:
1> xref:d('.')
[{deprecated,[]},
{undefined,[{{new,win1,0},{wish_manager,on_destroy,2}},
{{vsg,alpha_tag,0},{wish_manager,new_index,0}},
{{vsg,call,1},{wish,cmd,1}},
{{vsg,cast,1},{wish,cast,1}},
{{vsg,mkWindow,7},{wish,start,0}},
{{vsg,new_tag,0},{wish_manager,new_index,0}},
{{vsg,new_win_name,0},{wish_manager,new_index,0}},
{{vsg,on_click,2},{wish_manager,bind_event,2}},
{{vsg,on_move,2},{wish_manager,bind_event,2}},
{{vsg,on_move,2},{wish_manager,bind_tag,2}},
{{vsg,on_move,2},{wish_manager,new_index,0}}]},
{unused,[{vsg,new_tag,0},
{vsg_indicator_box,theValue,1},
{vsg_indicator_box,theValue,1}]}]

xref:d('.') 会对当前目录中使用调试标志编译的所有代码进行交叉引用分析,并生成已弃用、未定义和未使用函数的列表。

2. 调试

调试Erlang程序相对容易,这得益于其单赋值变量的特性。由于Erlang没有指针和可变状态(除了ETS表和进程字典),找出问题所在通常不是难事。

2.1 编译器诊断

当编译程序时,如果源代码存在语法错误,编译器会提供有用的错误消息。以下是一些常见的编译器错误:
- 头部不匹配 :如果函数定义的子句没有相同的名称和参数数量,会出现此错误。

%% bad.erl
Line 1
foo(1,2) ->
-
a;
-
foo(2,3,a) ->
-
b.
1> c(bad).
./bad.erl:3: head mismatch
  • 未绑定变量 :代码中使用了未赋值的变量会导致此错误。
%% bad.erl
Line 1
foo(A, B) ->
-
bar(A, dothis(X), B),
-
baz(Y, X).
1> c(bad).
./bad.erl:2: variable 'X' is unbound
./bad.erl:3: variable 'Y' is unbound
  • 未终止的字符串 :如果在字符串或原子中忘记了引号,会得到此错误消息。
  • 不安全变量 :程序可能会导致变量未定义的情况,编译器会发出“不安全变量”错误消息。
%% bad.erl
Line 1
foo() ->
-
case bar() of
-
1 ->
-
X = 1,
5
Y = 2;
-
2 ->
-
X = 3
-
end,
-
b(X, Y).
> c(bad).
./bad.erl:9: variable 'Y' unsafe in 'case' (line 2)
{ok,bad}
  • 变量遮蔽 :在函数内部使用与参数同名的变量可能会导致混淆,编译器会发出警告。
%% bad.erl
Line 1
foo(X, L) ->
-
lists:map(fun(X) -> 2*X end, L).
1> c(bad).
./bad.erl:1: Warning: variable 'X' is unused
./bad.erl:2: Warning: variable 'X' shadowed in 'fun'
{ok,bad}

可以通过重命名变量来解决此问题:

foo(X, L) ->
lists:map(fun(Z) -> 2*Z end, L).
2.2 运行时诊断

如果Erlang进程崩溃,可能会得到错误消息。要查看错误消息,需要有其他进程监控崩溃的进程,并在被监控进程死亡时打印错误消息。为了看到所有错误消息,最好始终使用 spawn_link

当与shell链接的进程崩溃时,会打印堆栈跟踪信息。以下是一个故意制造错误并查看堆栈跟踪的示例:

%% lib_misc.erl
deliberate_error(A) ->
bad_function(A, 12),
lists:reverse(A).
bad_function(A, _) ->
{ok, Bin} = file:open({abc,123}, A),
binary_to_list(Bin).
1> lib_misc:deliberate_error("file.erl").
** exited: {{badmatch,{error,einval}},
[{lib_misc,bad_function,2},
{lib_misc,deliberate_error,1},
{erl_eval,do_apply,5},
{shell,exprs,6},
{shell,eval_loop,3}]} **

堆栈跟踪信息从顶部开始,显示错误的类型和具体值,随后是函数调用的列表。需要注意的是,由于Erlang的尾调用优化,某些调用可能不会出现在堆栈跟踪中。

2.3 调试技术

Erlang程序员使用多种调试技术,以下是一些常见的方法:
- io:format调试 :在程序中添加 io:format(...) 语句来打印感兴趣变量的值。在调试并行程序时,在发送和接收消息前后打印消息是个好主意。

loop(...) ->
receive
Any ->
io:format("*** warning unexpected message:~p~n",[Any])
loop(...)
end

还可以使用宏来标记未实现的功能:

%% lib_misc.erl
-define(NYI(X),(begin
io:format("*** NYI ~p ~p ~p~n",[?MODULE, ?LINE, X]),
exit(nyi)
end)).
glurk(X, Y) ->
?NYI({glurk, X, Y}).
  • 数据结构转储到文件 :如果感兴趣的数据结构很大,可以将其写入文件。
%% lib_misc.erl
dump(File, Term) ->
Out = File ++ ".tmp",
io:format("** dumping to ~s~n",[Out]),
{ok, S} = file:open(Out, [write]),
io:format(S, "~p.~n",[Term]),
file:close(S).
  • 使用错误日志记录器 :可以创建一个配置文件,将错误消息记录到文本文件中。
%% elog5.config
%% text erorr log
[ {kernel,
[{error_logger,
{file, "/home/joe/error_logs/debug.log"}}]}].

启动Erlang时使用配置文件:

erl -config elog5.config
  • 调试器 :标准的Erlang发行版包含一个调试器。启动调试器的步骤如下:
1> %% recompile lib_misc so we can debug it
1>
c(lib_misc, [debug_info]).
{ok, lib_misc}
2> im().
%% A window will pop up. Ignore it for now
<0.42.0>
3> ii(lib_misc).
{module,lib_misc}
4> iaa([init]).
true.
5> lib_misc:

启动调试器后,可以检查变量、单步执行代码、设置断点等。由于经常需要调试多个进程,调试器还可以生成自身的副本,为每个调试的进程提供一个调试窗口。

Erlang代码分析与调试全攻略(续)

2. 调试(续)
2.4 调试方法总结

为了更清晰地展示不同调试方法的特点和适用场景,我们可以用表格进行总结:
| 调试方法 | 特点 | 适用场景 |
| ---- | ---- | ---- |
| io:format调试 | 简单直接,通过打印变量值来跟踪程序执行 | 小型程序或快速定位问题 |
| 数据结构转储到文件 | 适用于处理大型数据结构,方便后续分析 | 数据量较大的程序 |
| 使用错误日志记录器 | 集中记录错误信息,便于长期监控和分析 | 复杂系统或需要持续运行的程序 |
| 调试器 | 功能强大,可进行单步执行、设置断点等操作 | 深入调试复杂问题 |

下面是一个简单的mermaid流程图,展示了选择调试方法的一般流程:

graph TD;
    A[发现问题] --> B{问题类型};
    B -->|小型问题或快速定位| C[io:format调试];
    B -->|大型数据结构| D[数据结构转储到文件];
    B -->|复杂系统或长期监控| E[使用错误日志记录器];
    B -->|深入调试复杂问题| F[调试器];
3. 综合应用示例

假设我们正在开发一个简单的音乐播放程序 shout ,在开发过程中遇到了性能和逻辑问题,我们可以综合运用上述分析和调试工具来解决问题。

3.1 性能分析

首先,我们使用 cprof 来分析程序的性能,找出调用频繁的函数:

1> cprof:start().
%% start the profiler
4501
2> shout:start().
%% run the application
<0.35.0>
3> cprof:pause().
%% pause the profiler
4844
4> cprof:analyse(shout). %% analyse function calls
{shout,232,
[{{shout,split,2},73},
{{shout,write_data,4},33},
{{shout,the_header,1},33},
{{shout,send_file,6},33},
{{shout,bump,1},32},
{{shout,make_header1,1},5},
{{shout,'-got_request_from_client/3-fun-0-',1},4},
{{shout,songs_loop,1},2},
{{shout,par_connect,2},2},
{{shout,unpack_song_descriptor,1},1},
...
5> cprof:stop().
%% stop the profiler
4865

cprof 的输出中,我们可以看到 shout:split/2 函数的调用次数较多,可能是性能瓶颈所在,我们可以对该函数进行优化。

3.2 覆盖率分析

接着,我们使用覆盖率分析来找出未执行的代码:

1> cover:start().
%% start the coverage analyser
{ok,<0.34.0>}
2> cover:compile(shout).
%% compile shout.erl for coverage
{ok,shout}
3> shout:start().
%% run the program
<0.41.0>
Playing:<<"title: track018 performer: .. ">>
4> %% let the program run for a bit
4>
cover:analyse_to_file(shout).
%% analyse the results
{ok,"shout.COVER.out"}
%% this is the results file

查看 shout.COVER.out 文件,标记为零的行表示未执行的代码,我们可以设计测试用例来覆盖这些代码,确保程序的正确性。

3.3 交叉引用检查

在开发过程中,我们还可以使用 xref 进行交叉引用检查,确保代码中没有未定义或已弃用的函数:

$ cd /path/to/shout_project
$ rm *.beam
$ erlc +debug_info *.erl
$ erl
1> xref:d('.')

根据 xref 的输出,我们可以及时发现并修复代码中的问题。

3.4 调试逻辑问题

如果程序出现逻辑错误,我们可以使用 io:format 调试或调试器来定位问题。例如,在 shout 程序的接收循环中添加 io:format 语句:

loop(...) ->
receive
Any ->
io:format("*** warning unexpected message:~p~n",[Any])
loop(...)
end

如果问题比较复杂,我们可以使用调试器进行深入调试:

1> %% recompile shout so we can debug it
1>
c(shout, [debug_info]).
{ok, shout}
2> im().
%% A window will pop up. Ignore it for now
<0.42.0>
3> ii(shout).
{module,shout}
4> iaa([init]).
true.
5> shout:
4. 总结

通过本文的介绍,我们了解了Erlang中各种分析和调试工具的使用方法,包括覆盖率分析、性能分析、交叉引用检查和调试技术。在实际开发中,我们可以根据具体问题选择合适的工具和方法,综合运用这些工具可以帮助我们更高效地开发和维护高质量的Erlang程序。

为了方便大家记忆和参考,我们将主要工具和方法总结如下:
| 工具/方法 | 功能 | 使用步骤 |
| ---- | ---- | ---- |
| 覆盖率分析 | 找出未执行的代码 | 1. cover:start() ; 2. cover:compile(Module) ; 3. 运行程序; 4. cover:analyse_to_file(Module) |
| 性能分析(cprof) | 统计函数调用次数 | 1. cprof:start() ; 2. 运行程序; 3. cprof:pause() ; 4. cprof:analyse(Module) ; 5. cprof:stop() |
| 交叉引用检查(xref) | 检查未定义或已弃用的函数 | 1. 编译代码时使用 +debug_info 标志; 2. xref:d('.') |
| io:format调试 | 打印变量值 | 在关键位置添加 io:format(...) 语句 |
| 数据结构转储到文件 | 处理大型数据结构 | 调用 dump(File, Term) 函数 |
| 使用错误日志记录器 | 记录错误信息 | 1. 创建配置文件; 2. erl -config config_file |
| 调试器 | 深入调试复杂问题 | 1. 重新编译代码并添加 debug_info 标志; 2. 执行一系列启动命令 |

希望这些工具和方法能帮助你在Erlang开发中更加得心应手,快速解决遇到的问题。

基于径向基函数神经网络RBFNN的自适应滑模控制学习(Matlab代码实现)内容概要:本文介绍了基于径向基函数神经网络(RBFNN)的自适应滑模控制方法,并提供了相应的Matlab代码实现。该方法结合了RBF神经网络的非线性逼近能力和滑模控制的强鲁棒性,用于解决复杂系统的控制问题,尤其适用于存在不确定性和外部干扰的动态系统。文中详细阐述了控制算法的设计思路、RBFNN的结构权重更新机制、滑模面的构建以及自适应律的推导过程,并通过Matlab仿真验证了所提方法的有效性和稳定性。此外,文档还列举了大量相关的科研方向和技术应用,涵盖智能优化算法、机器学习、电力系统、路径规划等多个领域,展示了该技术的广泛应用前景。; 适合人群:具备一定自动控制理论基础和Matlab编程能力的研究生、科研人员及工程技术人员,特别是从事智能控制、非线性系统控制及相关领域的研究人员; 使用场景及目标:①学习和掌握RBF神经网络滑模控制相结合的自适应控制策略设计方法;②应用于电机控制、机器人轨迹跟踪、电力电子系统等存在模型不确定性或外界扰动的实际控制系统中,提升控制精度鲁棒性; 阅读建议:建议读者结合提供的Matlab代码进行仿真实践,深入理解算法实现细节,同时可参考文中提及的相关技术方向拓展研究思路,注重理论分析仿真验证相结合。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值