ChinaUnix.net 首页 | 博客 | Linux | 论坛 | 人才 | 培训 | 知识库 | 资料 | 读书 | 手册 | 精华 | 下载 | 沙龙 | 搜索
Linux首页 | Linux论坛 | 论坛精华 | 开源新闻 | 技术文章 | 专题专栏 | 新手指南 | 迁移方案 | 产品方案 | 开源项目 | 开源图书 | 软件下载 | 人才招聘 | Linux博客
  搜索

  产品与方案
·中科红旗全面打造现代化邮政体系
·红旗助力“网上审批服务” 推动电子政务
·红旗正版化开创呼和浩特网吧建设新起点
·红旗Linux助信息产业部邮件服务器“快跑”
·中标普华Linux 为电子政务信息化保驾护航
·中标普华Linux助力基金产业
·中标普华Office率先支持UOF标准
·中标普华邮件系统助力西藏政府信息化建设
·红旗Linux助力国库集中支付系统改革
·红旗助中信卫星 掀起GIS通信应用风暴
·红旗软件助力烟草总局 全面建设“数字烟草”
·红旗助力“信访阳光工程”打造畅通信访渠道
·红帽联合FIS发布下一代实时核心银行平台
·红旗助力金盾 打造全无忧出入境信息系统
·红旗Linux全力打造中国邮政总局名址信息库
·爱尔兰证交所从Unix迁移到红帽企业Linux
·一流的意大利银行选择使用红帽企业Linux
·PLUS Finanzservice选择使用红帽企业Linux
·红帽助力TransACT Communications 公司
·法国零售业巨头Lapeyre采用Redhat Linux
·旅游预订网站选择使用红帽企业Linux
·马哈拉施特拉邦政府的红帽解决之道
·美国联邦政府案例
·红帽为慕尼黑展览会提供现代化集群系统
·Yuba郡用开源软件和红帽产品提高了效率
·红帽企业Linux助印度理工建立高性能计算中心
·采用红帽Linux 将系统维护时间缩短了65%
·从UNIX迁移到Linux使Peñoles公司获益非浅
·Hikal公司用红帽企业Linux开展任务关键的ERP项目
·KDE3.5.4新版本发布
·芝加哥商业交易所从Unix向Linux迁移
·南方基金管理有限公司成功案例 Red Hat Linux
·广东北电通讯设备有限公司成功案例
·挪威国家石油公司从UNIX迁移到红帽Linux,成本减半
·中央电视台CCTV动画部案例 Red Hat Linux

  图书

鸟哥的Linux私房菜基础学..


Linux程序设计.第3版


Linux设备驱动开发详解


  下载
·Endian Firewall
·linux kernel(Linux 内核)
·CentOS
·Fedora Core 6
·Scientific Linux
·Slackware 11.0
·Gentoo Linux
·ubuntu-6.10-i386服务器版本
·ubuntu-6.10-amd64服务器版
·ubuntu-6.10-i386桌面版
·ubuntu-6.10-amd64桌面版
·Engarde Linux
您的位置: Linux时代 > 技术文档 > 程序开发 >

一个 Shell 程序的性能优化

日期:2006-10-31 作者:许四化 来自:IBM DW中国


编写 Linux Shell 脚本程序不要仅限于完成基本的程序功能,认真的分析 Shell 脚本并找出优化的方法对个人能力的提高以及对脚本程序的质量改善都有重要的意义,希望读者能从本文中获得许多实用的 Shell 程序方法。

本文 Shell 程序运行环境:

  • 程序运行环境 Redhat Linux As3
  • GNU bash, version 2.05b.0(1)-release (i386-redhat-linux-gnu)
  • 代码清单:shellcode.txt

问题描述:有一个普通的通话话单文件(包括"计费号码","主叫号码","被叫号码","开始时间","结束时间","时长","费用"等其它字段),要求根据另外一个号段配置文件(由"号段下限"和"号段上限"两个字段组成)将此话单文件进行分拣过虑。

分拣规则:如果通话话单文件中的"计费号码"位于号段文件的某个号段内,则将此条记录计入结果文件 1,否则计入结果文件 2。

通话话单文件样例:


9013320003|9013320003|9918128025|20060814163420|20060814163450|30|20|00|01|005
9926645208|9926645208|9918188065|20060814163415|20060814163545|90|30|00|01|005
9934877207|9934877207|9936972003|20060814163620|20060814163930|190|50|00|01|005
......
......

号段配置文件样例:


9013305000,9013327999
9013767000,9013768999
9923670000,9923679999
9928998000,9928999999
9932310000,9932319999
9932333400,9932333599
9936034000,9936036999
9936084000,9936084999
9998537000,9998537999
9998620000,9998629999
9998690000,9998699999

例如:

对于通话话单文件的第一条记录中的"计费号码"为 9013320000,此号码正好属于号段配置文件的第一个号段 9013305000,9013327999中,即:条件 9013305000<= 9013320000 <=9013327999 成立,所以应该将通话话单文件的第一条记录计入结果文件 1 中;对于通话话单文件中的第二条记录的"计费号码"为 9926645208 它不属于号段文件中的任何一个段,所以应该将通话话单的第二条记录计入结果文件 2 中。

对于这样一个简单的问题首先想到的解决方法为:

解决方法1:

写一个双重循环,外层循环为逐条读取"通话话单文件"并获取每条记录的第一个字段的值"计费号码",内层循环:根据外层循环获得的"计费号码"在"号段文件"中循环比较,判断此号码是否属于相应号段。

程序代码如下(省略了文件存在性判断等语句):


while read f
do
org="$(expr substr ${f} 1 10)"   #取得"计费号码"存入变量org中
while read numseg
do
nglow="$(expr substr ${numseg} 1 10 )"   #将号段下限存入变量nglow
ngtop="$(expr substr ${numseg} 12 10 )"  #将号段上限存入变量ngtop
if [ "$org" \> "$nglow"  -a "$org" \< $ngtop ]
#判断"计费号码"是否在此号段内
then
echo "${f}" >> ./resultfile1.cdr #如果在此号段内,将此记录计入结果文件1中
else 
echo "${f}" >> ./resultfile2.cdr #如果不在此号段内,将此记录计入结果文件2中
fi
done < ./numseg.txt
done < ./rttest.txt

解决方法1 对于号段文件和通话话单的记录数都比较少的情况下基本可以完成工作,但是当两个文件的记录数较多(例如号段文件>50条,话单文件> 10000条)的时候,这种方法就会花费几个小时甚至几天的时间才能得出处理结果。此脚本程序执行慢的原因是对第二个循环内的比较运算只用了最简单的顺序比较方法,所以当号段文件的记录增多的时候,脚本的执行速度会急剧下降。

解决方法2:

将内层循环的逐个比较的方法改为二分查找法进行判断,程序代码如下:


#!/bin/bash
#Author Xsh  date:08-15-2006
#程序中使用了二分查找法进行号码的范围判断
#为突出重点,省略了文件存在性判断和异常捕获以及帮助提示等程序语句
#程序的工作目录为当前目录

echo "Time:$(date)==>Strat to processing........." #显示程序开始运行时间
while read numseg
do
tmplow="${tmplow} $(expr substr ${numseg} 1 10 & >/dev/null ) "
tmptop="${tmptop} $(expr substr ${numseg} 12 10 & >/dev/null ) "
done < ./numseg.txt
#读取号段文件,下限号段存入变量tmplow,上限号段存入变量tmptop
arr_lownug=(${tmplow}) #将下限号段存入数组arr_lownug
arr_topnug=(${tmptop})	#将上限号段存入数组arr_topnug

#定义函数checknum(),输入参数为需要检查的"计费号码",输出参数为0或者1
#若checknum()输出为0 表示"计费号码" 不在号段文件的所有号段范围内
#若checknum()输出为1 表示"计费号码" 在号段文件的所有号段范围内
# checknum()函数中用二分搜索法进行号码的判断
checknum(){
thisnum=$1
ckresult=0
lowflag=0
topflag=$(expr ${#arr_lownug[*]} - 1 )  #标注1
MaxIndex=$(expr ${#arr_topnug[*]} - 1 ) #标注2
midflag=0
midflag=$(expr ${topflag} / 2 )		#标注3
if [ "${thisnum}" \< "${arr_lownug[0]}" -o "${thisnum}" \>
"${arr_topnug[${MaxIndex}]}"  ]
then
return 0
else
while [ "$lowflag" != "$midflag" ]
do
if[ "$thisnum" \> "${arr_lownug[${midflag}]}" -o "$thisnum" == \
"${arr_lownug[${midflag}]}" ]
then
lowflag=${midflag}
midflag=$(expr `expr ${topflag} + ${lowflag}` / 2 ) #标注4
elif["$thisnum"\<"${arr_lownug[${midflag}]}" -o "$thisnum" == \
"${arr_lownug[${midflag}]}" ] 
then
topflag=${midflag}
midflag=$(expr `expr ${topflag} + ${lowflag}` / 2 ) #标注5
else
echo "Error!"   
fi
done 
if [ "$thisnum" \< "${arr_topnug[${lowflag}]}" -o "$thisnum" == \
"${arr_topnug[${lowflag}]}" ]
then
return 1
else 
return 0 
fi
fi
}#函数定义完毕

while read f
do
org="$(expr substr ${f} 1 10)" #标注6
checknum ${org}
returnval=$?
if [ "$returnval" == "1"  ]
then 
echo "${f}" >> ./Match_result.cdr  #将匹配的记录存入结果文件1
else
echo "${f}" >> ./NoMatch_result.cdr #将不匹配的记录存入结果文件2
fi
done < ./rttest.txt 
echo "Time:$(date) ==> Proccess is end! "
exit 0;

将以上程序投入运行,号段文件有 71行记录,需要分拣的通话话单文件17 万条左右通过观察发现此方法的执行效率确实比解决方法1 提高了很多,但是仍需要数小时的时间才能执行完毕。我另外写了一个同样功能的C语言程序,执行同样的测试数据得出结果仅需要10~15秒的时间!shell 程序确实要比同样功能的C程序慢一些,但是目前的情况是用C程序处理只需要几秒钟时间, 而Shell程序确需要数小时!在用此Shell程序处理的时候我用Top命令看了一些系统资源的消耗发现CPU、内存以及IO的占用都极小,说明系统资源很充足,不是系统的问题造成了程序处理速度慢。问题出在哪了呢?

我用命名ps -ef 观察系统进程的运行情况,发现每过几秒种就会有一个expr进程产生,这个进程运行很短的时间就消失了(不仔细观察可能都看不到有expr这个进程产生)。这时候我觉得我好像发现程序运行慢的原因了:程序的几个循环里面都有用expr进行计算的语句,expr属于Shell外部命令,所以每次运算都要产生一个 expr进程,而程序的运行种最消耗时间的除了IO操作外就数产生新进程操作了,于是我决定把用expr进行计算的地方都尽量修改为用shell的内部命令进行计算。程序变成了下面的样子(标注1~标注6为修改过的地方):

程序代码如下:


―――――――――――――――――――――――――――――――――――――――
#!/bin/bash
#Author Xsh  date:08-15-2006
echo "Time:$(date)==>Strat to processing........." #显示程序开始运行时间
while read numseg
  	do
  	 tmplow="${tmplow} $(expr substr ${numseg} 1 10 & >/dev/null ) " 
  	 tmptop="${tmptop} $(expr substr ${numseg} 12 10 & >/dev/null ) "
done < ./numseg.txt	#循环读取号段文件下限号段存入变量tmplow,上限号段存入变量tmptop

arr_lownug=(${tmplow}) #将下限号段存入数组arr_lownug
arr_topnug=(${tmptop})	#将上限号段存入数组arr_topnug

#定义函数checknum(),输入参数为需要检查的"计费号码",输出参数为0或者1
#函数checknum()输出为0 表示"计费号码" 不在号段文件的所有号段范围内
#函数checknum()输出为1 表示"计费号码" 在号段文件的所有号段范围内
#函数checknum()中用二分搜索法进行号码的判断
checknum(){ #函数定义开始
   thisnum=$1
   ckresult=0
   lowflag=0
   topflag=$((${#arr_lownug[*]} - 1 ))   #标注1
   MaxIndex=$((${#arr_topnug[*]} - 1 ))  #标注2
   midflag=0
   midflag=$((${topflag} / 2 ))	      #标注3
if [ "${thisnum}" \< "${arr_lownug[0]}" -o "${thisnum}" \> "${arr_topnug[${MaxIndex}]}" ]
then
	return 0
else

while [ "$lowflag" != "$midflag" ]
do
 if ["$thisnum"\> "${arr_lownug[${midflag}]}" -o "$thisnum" == \
  "${arr_lownug[${midflag}]}"]
   then
   lowflag=${midflag}
   midflag=$(( $((${topflag} + ${lowflag})) / 2 )) #标注4
elif["$thisnum"\<"${arr_lownug[${midflag}]}" -o "$thisnum" == \
"${arr_lownug[${midflag}]}"] 
then    
     topflag=${midflag}
     midflag=$(($((${topflag} + ${lowflag})) / 2 )) #标注5
else
  echo "Error!"   
fi
done 
if ["$thisnum" \< "${arr_topnug[${lowflag}]}" -o "$thisnum" == \
"${arr_topnug[${lowflag}]}"]
then
 return 1
else 
 return 0 
fi
fi
}#函数定义结束

while read f
do
org="${f:0:10}"  #标注6
checknum ${org}
returnval=$?
if [ "$returnval" == "1"  ]
then 
echo "${f}" >> ./Match_result.cdr  #将匹配的记录存入结果文件1
else
echo "${f}" >> ./NoMatch_result.cdr #将不匹配的记录存入结果文件2
fi
done < ./rttest.txt 
echo "Time:$(date) ==> Proccess is end! "
exit 0;

将修改过的程序进行运行,很快就得出了结果,总的运行时间没有超过8分钟。此时这个程序的运行效率已经基本可以让人接受了。同样的一个问题由于改进了程序算法和充分利用了LinuxShell的内置运算符,将程序的运行时间大大的缩短。当然此程序还有可以改进的地方,对此文感兴趣的读者可以对此程序做进一步优化。

原文链接:http://www-128.ibm.com/developerworks/cn/linux/l-shprg/index.html

本文被浏览



 相关新闻

Bourne again shell (bash) 基本编程2006-10-25 14:48:17
Linux编程:将PHP作为Shell脚本使用2006-10-18 13:48:14
Linux 操作系统编程之Shell 问答录2006-10-18 13:45:38
Linux下的shell编程入门2006-10-17 13:06:22
Linux主要shell命令详解2005-02-03 15:43:30
shell入门基础必备2005-02-03 15:32:57


 相关评论
关于我们 | 联系方式 | 广告合作 | 诚聘英才 | 网站地图 | 免费注册

Copyright © 2001-2006 ChinaUnix.net All Rights Reserved

感谢所有关心和支持过ChinaUnix的朋友们

京ICP证041476号