背景
Asterisk作为优秀的开源VOIP软件,其功能核心Dialplan辅助Asterisk核心完成一些复杂的业务功能。但其中一个常见的应用Macro在Dialplan的扩展中展现的相当灵活,方便用户快速扩展适合自己的应用。
Macro的灵活性也有它的弊端,即会导致较低的性能。
Macro原理
通过阅读app_macro代码发现Macro执行过程是递归调用。如:
[macro-1]
exten = s,1,Noop
exten = s,n,Macro(2)
[macro-2]
exten = s,1,Noop
exten = s,n,Macro(3)
[macro-3]
exten = s,1,Noop
exten = s,n,Macro(4)
[macro-4]
exten = s,1,Noop
exten = s,n,Noop()
在上述的dialplan中,macro-1将会递归调用macro-2,macro-3,直到macro-3返回后则此次调用结束,递归结束后需要按原路径回归,这样在回归阶段将会消耗一定的性能,从而造成实际性能的损失。
Macro核心框架
while (ast_exists_extension(chan, ast_channel_context(chan),
ast_channel_exten(chan), ast_channel_priority(chan),
S_COR(ast_channel_caller(chan)->id.number.valid,
ast_channel_caller(chan)->id.number.str, NULL))) {
/*!
* 在此处开始调用macro-1 context下包含的extension,这样就会持续递归下去,
* 除非遇到MacroExit时此次Macro才结束调用。
*/
res = ast_spawn_extension(chan, ast_channel_context(chan),
ast_channel_exten(chan), ast_channel_priority(chan),
S_COR(ast_channel_caller(chan)->id.number.valid,
ast_channel_caller(chan)->id.number.str, NULL),
&foundx, 1);
/*!
* 此处extension执行完后检查chan是否已经挂断,挂断则进行栈回归阶段。
*/
/* don't stop executing extensions when we're in "h" */
if (ast_check_hangup(chan) && !inhangup) {
ast_debug(1, "Extension %s, macroexten %s, priority %d returned normally even though call was hung up\n",
ast_channel_exten(chan),
ast_channel_macroexten(chan),
ast_channel_priority(chan));
goto out;
}
}
Macro的替代品GoSub
Gosub摒弃了Macro的递归调用,而是构造了一个类似Linux栈的数据结构,这样后续的数据都会存放在这个栈上,当GoSub要结束时需要调用Return,然后回收资源。
GoSub实例
[from-internal]
exten => s,1,Noop()
exten => s,n,GoSub(from-internal-1,s,1(此处为传递给from-internal-1context的参数))
[from-internal-1]
exten => s,1,Noop()
exten => s,n,Return()
递归
递归的实现是函数通过直接或间接地调用自己,而函数调用时每次调用都要做地址保存,参数传递等,这是通过一个递归工作栈实现的。而调用结束时需要按原路返回(即回归阶段)以释放资源,这期间势必有大量重复的计算。
递归实现优缺点
- 优点
- 代码简洁清晰,容易验证正确性
- 缺点
- 耗费栈内存较多(有可能会出现递归栈溢出)(如:参数传递需要压栈等操作,会对执行效率有一定的影响)
- 大量重复计算
递归和循环执行效率对比
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/time.h>
static int count = 0;
static int total = 1000000;
static int function()
{
if (count >= total) {
return 0;
}
count++;
function();
}
static int function2()
{
for (int i = 0; i < total; i++) {
count++;
}
}
int main(int argc, const char *argv[])
{
struct timeval start_time = {0};
struct timeval ended_time = {0};
gettimeofday(&start_time, NULL);
function();
gettimeofday(&ended_time, NULL);
long diff = ended_time.tv_sec * 1000 * 1000 + ended_time.tv_usec - start_time.tv_sec * 1000 * 1000 - start_time.tv_usec;
printf("recursive function take time: %ld\n", diff);
gettimeofday(&start_time, NULL);
function2();
gettimeofday(&ended_time, NULL);
diff = ended_time.tv_sec * 1000 * 1000 + ended_time.tv_usec - start_time.tv_sec * 1000 * 1000 - start_time.tv_usec;
printf("linear function take time: %ld\n", diff);
return 0;
}
运行结果
recursive function take time: 20203
linear function take time: 1991