龙空技术网

C/C++/Perl/汇编/Java效率比较

梦幻辰风 496

前言:

此时兄弟们对“java运行效率比c低多少”大致比较注意,兄弟们都需要剖析一些“java运行效率比c低多少”的相关知识。那么小编也在网上搜集了一些有关“java运行效率比c低多少””的相关文章,希望咱们能喜欢,同学们一起来了解一下吧!

(转)

  我无聊的翻着散落案头的书籍,这些都是五花八门的关于编程和系统管理的著作。干了这么多年程序员,大大小小的软件和项目也做了无数。每每有新入行的朋友问我这个所谓的"老前辈":哪种语言最好之类的问题,我总会作出一副知识渊博的样子,复述着从更老的老前辈那里听来的或者某些名著上看来的"知识"。就好比我们从学习编程的第一天起,就被计算机老师告知,COBOL语言是擅长处理商务事务、FOTRAN语言是用于科学计算一样。类似的知识还有"汇编语言比C语言快得多"以及"JAVA是一种效率很低的语言环境"在一代又一代的程序员中口耳相传,几乎成为了毋庸置疑的真理。

  我产生了一个想法,能不能对于同一个应用用几种编程语言分别实现,来比较一下看看到底哪种语言效率最高?

  老实说我自己都觉得这个想法很无聊,想想谁会反复用不同的语言写同一个程序呢?下雨天打孩子,闲着也是闲着。再说,对于某种语言的弱点和优势有一个量化的分析,对于我们今后在做项目的时候面临工具选择也少许有一点指导意义。另外,觉得好玩才是我做这件事情的真正原因。

  选题

  选择一个什么样的程序问题进行这样的测试呢?这是一个很关键的问题,也最容易影响测试的公平性。另外的,对于每种语言,各自的优势都是不同的。程序员的偏爱也是各不相同的。在网上和现实中,对于什么语言更好一些的争论从来就没有停止过。甚至的,各门各派的程序员所构成的各种阵营,把某种语言奉若神明的也不在少数。不信,你在CSDN的JAVA论坛说一句"JAVA执行效率太低了云云"试试?立刻会被铺天盖地的板砖掀翻在地。类似的,还有管理员对于操作系统的偏好和争论:在Linux论坛你要是表扬Windows,其惨烈程度简直是难以言状。因此,从这个意义上来说,程序员们对于编程语言的偏好,类似于战士之喜爱枪械,赛手之喜爱赛车,已经上升为一种精神层面的东西了。蔡学镛先生说得好:有人逢微软必反,有人逢微软必捧。这是一种纯粹的精神上的爱,但它可能会影响正常的、科学的思考。

  可以预料的,我这篇文章一定会遭到各路豪杰的迎头痛击。

  好了,让我们言归正转吧。首先的,我们的选题中要使用的各种程序语言的最常用的要素。什么是最常用的要素呢?当然了,大家都有的就是赋值、数组操作、循环、判断等。另外,对IO的操作也是编程语言重要的内容。其次的,操作时间一定要长,否则,对于解释性的语言来说是极不公平的:解释器还没调入内存呢,人家编译派的已经运行完了。最后,就是程序不能太复杂。除了我没有那么大的毅力用各种语言完成一个复杂算法的决心外,程序过于复杂,算法在测试中起的作用就越来越大,影响运行效率的原因也就增加了。算法过于复杂,开发工具的扩展部分用得也就越多。于是就成了语言附加库之间的竞赛了,这是我不愿意看到的。

  考虑上述因素,我设计了一个简单的选题:从指定文本文件中搜索指定字符串,计算个数。并且打印出搜索到的个数作为结果输出。作为程序员的你粗粗过一下脑子,马上会想到这个算法里面包含了条件判断、循环、数组操作等基本的程序语言因素。这满足了上面第一个条件。另外的,为了满足第二个条件,我准备了一个多达2G的文本文件,总共有文本1500万行多。这保怔了足够的运行时间(但应该不会太长),而决不会一眨眼就执行完了。最后的,我们都知道,在文本串里面搜索子串的算法是数据结构课本中的一个典型的例子(考试也经常被考到的),也满足算法简单的要求。同时,为了让每个程序的环境都一样,我得每测试一次就重新启动一次机器,避免CACHE的影响。

  准备

  比赛嘛,就需要公平。首先的,硬件平台要统一。我找了一台看起来还不错的机器(服务器):两颗PIII800,1G内存。操作系统嘛,原来的机器上有新装的Windows2000Server版本。几乎没装什么别的应用。我偷懒了一下,没有重新安装OS,就这样用吧。

  第一个选手:PERL

  如果别人交给我这个题目,我会马上决定用PERL语言来做这件事。这个题目是完全的文本处理问题,还有比用PERL来做更合适的吗?因为PERL是专门为了文本处理而编制的语言。事实上也是这样,我用了2分钟,写了几行代码,就轻松实现了这个问题。这也说明了,选择适用的编程语言工具,比选择喜爱的工具更重要。

#!/usr/bin/perl

$filename="d:/access.log_";

$count = 0;

open(FILE , "<$filename");

while(<FILE>)

{

@match_list = ($_ =~ /HIT/g);

$count=$count+@match_list;

}

close(FILE);

print "Count = $count ";

exit

  PERL是一位语言学家Larry Wall发明的,事实上,早期这种语言是专门用于在UNIX平台处理文字文件的(Perl=Practical Extraction Report Language:实用报表析取语言)。后来人们发现有大量文本构成的HTML页面用PERL来做CGI程序生成动态页面再合适不过了。因为互联网的兴起,PERL跟着发大了起来。这种语言的语法和C语言基本类似,因此比较好掌握,并且的,其关于"正则表达式"处理的强大功能目前基本上无人能够望其项背。事实上,类似于"过滤出含有TOM或者ABC的、并且后者的第一个和第三个字母大写,前者最少出现2次,后者出现5次、而且中间间隔8个或4个字母或空格的文本行"。我猜你正在反复的揣摩这句话,事实上,这就是所谓正则表达式,这样的问题,在PERL只需要一行语句就可以完成。在C语言中需要多少语句才能实现呢。

  我略略解释一下上面的程序,让没有用过PERL语言的程序员也有个感性认识。

  第一行是在UNIX中才用得到,因为PERL是一种基于解释的脚本语言。

  第四行是打开文件

  下面的循环是一行一行的读文件的内容。循环中间的第一句话是把凡是文本行中含有的HIT全部放到一个数组中;循环中中的第二句话是统计一下刚才的数组中有几个HIT,然后累加起来。循环完成了,我们的任务也就完成了。怎么样,很简单吧?"/HIT/g"就是最简单的正则表达式。

  现在的PERL语言早已经不是原来的脚本语言形象了,现代PERL几乎具备了其特语言的所有特性,并且的在模块的功能帮助下,可以实现很大的应用。而且还增加了一些面向对象的特点。尽管大多数人仍然在用它处理大量的文本,但也有使用PERL完成大型应用的,尤其是在WEB方面。值得一提的是PERL也是一个跨平台语言。

  我的这个程序在测试平台上,使用PERL5.8解释器,用了8分18秒08完成了1500万行文本的扫描,并得出了正确的结果。

  第二个选手:纯C

  也许年龄大了,但是我真的很喜欢C语言。而且我最喜欢的就是使用指针和强制类型转换来任意操作数据。我甚至会在程序里通过指针手工拼凑一个长整性的数据。说句可能引起争议的话,我觉得JAVA语言抛弃可爱的指针的做法基本上就是逃避。因为掌握不好就不用,到头来就是牺牲了效率。

  本文这个题目,用C语言来实现应该还是比较不错的选择。下面的代码就是在VC下面实现的纯C代码的字符串搜索程序(为了避免图形界面的干扰,一律做成控制台程序)。编译的时候使用速度优先编译选项。

#include <stdio.h>

#include <string.h>

void main()

{

int len=2048;

char filename[20];//文件名

char buff[10000];//文件缓冲区

char hit[5];

FILE *fd;

int i,j,flag=0,over=0;

int max,readed;

int count=0;//最后的结果

strcpy(&filename[0] , "d:/access.log_");

strcpy(&hit[0] , "HIT");

buff[0]=0x0;

buff[1]=0x0;

//打开文件:

if((fd = fopen(&filename[0] , "rb"))==NULL)

{

printf("Error : Can not open file %s ",&filename[0]);

}

//读取文件内容

while(over != 1)

{

readed = fread(&buff[2] , 1 , len , fd);

if(readed < len)

{

over=1;

max=readed;

}

else

{

max=len;

}

for(i=0;i<max;i++)

{

for(j=0;j<3;j++)

{

if(hit[j] != buff[i+j])

{

flag=0;//一旦有一个不相同就退出并且标志为0

break;

}

else

{

flag=1;//一个相同为1,如果连续都相同最后结果定是1

}

}

if(flag==1)

{

count++;

i+=j-1;

}

else

{

if(j==0)

{

i+=(j);

}

else

{

i+=(j-1);

}

}

}

//把最后两个字符转移到前面两个字节以防止切断搜索串.

buff[0]=buff[max];

buff[1]=buff[max+1];

}

fclose(fd);

printf("count:%d ",count);

}

  程序很好懂,用的也是教科书上面的标准字符串搜索算法,但是比前面的PERL程序长多了吧?那是因为人家PERL已经帮你完成了大部分工作。但是看到上面这段程序的运行结果你可能会高兴起来,它最快一次只用了2分10秒52,最慢也只用了2分20秒59就完成了1500万行文本的搜索任务。平均2分15秒多。为什么每次时间不一样呢?我不清楚具体原因,但学过操作系统的朋友会明白,只有在单道单任务的系统中,代码才能有执行上的可再现性。

  有经验的朋友可能会说,你的缓冲区只用了2048字节,加大它速度还会增加呢。是的,而且我相信还有高手能作出更快的程序来,但这不重要,重要的是我们要考察的是不同语言完成同一件工作的效率。而且你能够明白,在程序中,改进什么能够提高效率,这就足够了。因为C语言程序中,这些都是自由可控的。

  第三个选手:C++

  C++和前面的C是亲戚。我简单的把前面的C代码移植过来,然后把文件输入部分改成了流类对象。至于算法部分嘛。跟前面的C是一模一样的。最后在编译的时候,除了使用速度最佳编译选项外,当然还用了C++的编译参数,因此执行文件的长度比前面的C要长一些,这说明我加的流类代码比标准C库要复杂。是的,C++应该说是目前流行的计算机编程语言中复杂度排名靠前的。其复杂的类和继承关系,以及各种初始化的次序和构造函数执行顺序等都需要考虑。还有多态以及动态联编技术等。C++也是我非常喜欢的语言,提供了面向对象的代码重用特性和足够的安全型,但是在效率上的确比纯C略逊一筹。你知道吗,大部分的操作系统核心几乎都是用纯C写成的,尽管很复杂,但很少有使用面向对象技术的。为什么,不是面向对象技术不好,也不是操作系统核心不够复杂(那什么复杂?),主要的考虑就是效率问题。

#include <stdio.h>

#include <string.h>

#include <fstream.h>

void main()

{

int len=2048;

char filename[20];//文件名

char buff[10000];//文件缓冲区

char hit[5];

int i,j,flag=0;

int max;

int count=0;//最后的结果

strcpy(&filename[0] , "d:/access.log_");

strcpy(&hit[0] , "HIT");

buff[0]=0x0;

buff[1]=0x0;

//用输入流打开文件:

ifstream input(&filename[0]);

//读取文件内容

while(input)

{

input.getline(&buff[2] , len);

max = strlen(&buff[2]);

for(i=0;i<max;i++)

{

for(j=0;j<3;j++)

{

if(hit[j] != buff[i+j])

{

flag=0;//一旦有一个不相同就退出并且标志为0

break;

}

else

{

flag=1;//一个相同为1,如果连续都相同最后结果定是1

}

}

if(flag==1)

{

count++;

i+=j-1;

}

else

{

if(j==0)

{

i+=(j);

}

else

{

i+=(j-1);

}

}

}

}

printf("count:%d ",count);

}

  这段C++程序在测试平台上用了最快4分25秒95 到最慢5分40秒68的时间完成1500万行的文本检索,并在2G的文件中检索出10951968个"HIT"字符串。这结果是正确的。

  第四个选手:汇编

  本以为汇编程序能够达到前所未有的高速,把前面的选手远远抛在身后而笑傲江湖。这一想法支撑我完成了艰涩的代码。可事实上测试的结果缺让我大失所望,完全用机器指令书写的程序,去掉缓冲区才几百字节,算法和前面的C程序一模一样,扫描1500万行文本竟然最快也要2分14秒56!这甚至还比不过C语言的最快纪录。而平均下来,汇编程序的速度竟然和前面的C程序在伯仲之间。恐怕这样的结果也出乎大部分人的意外。因为我们从入行的那一天起,就被告知汇编是你所能够掌握的最快的语言!尽管代码坚涩难懂,但性能的代价是值得的。而从这里的测试看,你觉得向下面这样的代码,实现和C语言一样的速度和功能值得吗?

;堆栈段

STSG SEGMENT STACK 'S'

DW 64 DUP(?)

STSG ENDS

;数据段

DATA SEGMENT

rlength EQU 2048

fname DB 'access.log_',0

hit DB 'HIT$'

fd DW ? ;文件句柄

resault DB 'count : $' ;结果提示

count DD 0 ;存放结果

disflag DB 0 ;显示标志

buff DB 5000 dup(0) ;缓冲区

DATA ENDS

;代码段

CODE SEGMENT

MAIN PROC FAR

ASSUME CS:CODE,DS:DATA,SS:STSG,ES:NOTHING

MOV AX,DATA

MOV DS,AX

;我的代码开始:

mov ah,3dh ;打开文件

lea dx,fname

mov al,00h ;文件打开方式

int 21h ;开始操作

;这里就不作错误处理了,偷懒喽!

;CF=0表示正确,CF=1表示错误,AX是文件句柄或者是错误代码

mov fd,ax ;保存文件句柄

READ: mov ah,3fh ;读文件

mov bx,fd ;文件句柄

mov cx,rlength ;要读length字节

lea dx,buff ;给出读缓冲区指针

add dx,2 ;缓冲区指针向后错两个(目的是解决边界问题:有一个HIT正好横跨rlength界限)

int 21h ;开始读

;AX里面是实际读出的字节数

;读完了以后,扫描缓冲区

push ax ;保存AX字节数

cmp ax,0

jz ALLEND ;文件读完了就退出

sub dx,2 ;指针向前错2个,

mov si,dx

add dx,2 ;把指针回到原来的位置

add dx,ax ;计算结尾

LOD3: cmp si,dx ;到头了就重新读一次文件

jz OVR

lods buff

lea bx,HIT

cmp al,[bx]

jnz LOD3 ;读第一个字节不相等就重新读一个

cmp si,dx

jz OVR

lods buff

cmp al,[bx+1]

jnz LOD3 ;如果第一个字节相等,就读第2个字节,不行等就从第一个字节再重比较。

cmp si,dx ;如果第二个字节也相等的话,就比较第三个字节。

jz OVR

lods buff

cmp al,[bx+2]

jnz LOD3 ;第三个字节不相等再从头开始

;有一个HIT匹配

push bx

lea bx,count

add WORD ptr [bx],1 ;计数器增加一个

adc WORD ptr [bx+2],0 ;进位

pop bx

jmp LOD3

OVR: mov ah,[si-1]

mov BYTE ptr buff+1 , ah

mov ah,[si-2]

mov BYTE ptr buff , ah

pop ax ;恢复这次总共读出的字节数

cmp ax,rlength ;看看是不是最后一次(剩余的零头)

jz READ

;如果是最后一次读文件,

ALLEND: mov ah,3eh ;关闭文件

mov bx,fd ;文件句柄

int 21h ;关闭文件

mov ah,9 ;显示结果字符串

lea dx,resault

int 21h

;转换2进制结果到10进制ACSII形式

mov bx, WORD ptr count

call TERN

mov ax,4c00h ;返回DOS

int 21h

;结束代码,最大的数字已经排到了最前面

MAIN ENDP

TERN PROC ;这个子程序是转换并显示2进制数字的

mov cx,10000

call DEC_DIV

mov cx,1000

call DEC_DIV

mov cx,100

call DEC_DIV

mov cx,10

call DEC_DIV

mov cx,1

call DEC_DIV

ret

TERN ENDP

DEC_DIV PROC

mov ax,bx

mov dx,0

div cx

mov bx,dx

mov dl,al

add dl,30H

mov ah,disflag ;read flag

cmp ah,0

jnz DISP ;已经显示过有效数字了

cmp dl,30H

jz NODISP

mov disflag,1 ;作用是第一个有效数字出现前不显示0

DISP: mov ah,2

int 21H

NODISP: ret

DEC_DIV ENDP

CODE ENDS

END MAIN

  上面这段代码我猜你也懒得仔细阅读。其实他不能"显示结果"。因为最后这段负责把最终结果转换成可显示ASCII码的程序实际上只能转换二进制十六位的数据,而最终的结果高达1000万挂零,显示会出错。由于这最终结果的显示已经和程序的运行没有大关系了,因此,我也就懒得去写一个32位的ASCII转换程序了。就这样吧。

  第五个选手:JAVA

  JAVA是一个不能不参加比赛的选手。有如此多的人热爱他,他们中的一半人是因为JAVA的面向对象特性以及良好的跨平台特性。而另一半人纯粹就是因为JAVA不姓"微(软)",这就是意识形态在程序员头脑中对某种语言的注释。单纯从语言元素上来说,我还是比较喜欢JAVA的。因为他的语法干净、简洁。环境也好。虽然用虚拟机系统(JVM)的做法来实现跨平台特性并非什么了不得的创意(像不像30年前的BASIC解释器?别跟我说什么中间代码?几乎所有的解释器都是把语言因素翻译成中间代码的,JVM不过是分成2步来实现罢了,但从运行机制上应该是差不多的。),但JVM仍然将JAVA的跨平台特性做到了前所未有的地步。而且JVM是一个很干净的系统,让人用起来赏心悦目。说到这里我忍不住想提一下J2EE企业应用框架了。不知道有多少人能够看懂SUN出的J2EE的"理论著作"?满纸充斥着各种生造的概念,洋溢着溢美之词。JAVA的企业应用框架实在是比较复杂的东西,虽然赶不上后来的.NET框架,但足以让大多数初学者望而却步。一句话,东西太多了。事实上JAVA的企业级应用并没有想象的成功,iPlanet就随着电子商务概念的全面垮台而渐渐淡出。现在换了个名叫“SUNONE”――SUN公司员工原话。

  我们回到JAVA的语言元素上来说,实际上JAVA可以被理解为被纯化的C++。JAVA去除了C++为了兼容C而增加的一些"非面向对象特质",用其他的一些变通办法实现C++直接实现的功能,比如:多继承。在实现机制上,JAVA的程序会先编译成.CLASS文件,然后这种跨平台的中间代码就可以"一次编译,到处运行"了。当然必须运行在有JVM虚拟机的环境中,连图形什么的都可以照搬。换句话说,你用JAVA程序在PC屏幕上画一个圆,在JAVA-PDA上它还是圆的。

  我在本次测试中,写了下面的代码,用JAVA做了同样的测试,测试中实际上用到了:JAVA的文件流类,运行了循环、条件判断、数组操作等基本的语言因素。环境是J2SE1.3.1-06。JAVA程序做1500万行的文本扫描用了8分21秒18。应该说是几种语言中最慢的,基本上和纯解释的PERL是在同一水准。J2EE的JVM环境还是经过优化的所谓HOTSPOT。

import java.io.*;

public class langtest

{

public static void main(String[] args)

{

String filename = "d:/access.log_";

try

{

count(filename);

}

catch (IOException e)

{

System.err.println(e.getMessage());

};

}

public static void count(String filename) throws IOException

{

long count=0;

long len;

String strline = "";

char hit[] = {'H','I','T'};//要搜索的字符串

char buff[] = new char[2100];

Reader in = new FileReader(filename);//用FileReader类构造产生一个Reader类对象

LineNumberReader line = null;//生成一个空指针

try

{

line = new LineNumberReader(in);//建立LineNumberReader类对象

while((strline = line.readLine()) != null)

{

//到这里已经读出一行了,用下面的代码分析这行有几个HIT

int i=0,j=0,max=0,flag=0;

buff = strline.toCharArray();//转换成字符数组

max = strline.length();

for(i=0;i<max;i++)

{

for(j=0;j<3;j++)

{

if(hit[j] != buff[i+j])

{

flag=0;//一旦有一个不相同就退出并且标志为0

break;

}

else

{

flag=1;//一个相同为1,如果连续都相同最后结果定是1

}

}

if(flag==1)

{

count++;

i+=j-1;

}

else

{

if(j==0)

{

i+=(j);

}

else

{

i+=(j-1);

}

}

}

}

System.out.println("Count : "+count);

}

catch (IOException e)

{

System.err.println(e.getMessage());

}

finally

{

try

{

if(in != null) in.close();

}

catch (IOException e)

{

}

}

}

}

  候捷先生翻译的宏篇巨著《JAVA编程思想》一书中第67页说到:"使用最原始的JAVA解释器,JAVA大概比C慢上20到50倍"之说法我在阅读的时候就心存疑虑,心想要是这样,JAVA完全没有存或与世间的必要了。在亲自动手试验过后,我觉得说JAVA在J2EE环境下,比C慢上2-3倍还是比较可靠的说法的。况且,目前越来越多的硬件JVM的诞生,也给JAVA越来越多的机会。不过我担心的正是这点,JVM的多厂家多样化很可能会造成某些兼容性方面的问题。例如我见过一篇文章就是讨论某种JAVA程序在IBM-JVM可用而在SUN-JVM上不可用之事例。但愿的,JAVA能健康成长。

  总结

  事实上,本文有两个基本的意义传递给初做程序员的读者:

  一、 抛开你的意识形态好恶,选择最合适的编程语言来完成你的工作。每种流行的语言都有自己存在的意义。

  二、 在编程中,有想法就自己做一做,你会得出自己的结论。

  至此,你应该明白,前面的所有测试结果其实并不重要,重要的是你了解了这些语言的特质,也许在今后的编程生涯中会因此增加一点点"经验"呢。

  后记

  本来笔者还打算继续测试一下另外的一种颇为流行的解释语言Python和新贵C#以及在Linux平台完成这些测试,但终究还是被懒惰瓦解了斗志。好在的,Python和Perl比较相似,而C#和JAVA有异曲同工之妙。也可以略略做一点参考。

  事实上,本文测试中有一个大大的不公平之处,相信仔细的读者已经发现了:其中C和ASM都是使用缓冲区直读的办法,不管三七二十一就进行判断(最后用指针检查缓冲区边界)。而C++等其他的语言虽然用了非常方便的流按行读出,但是多做了很多事情:每一个字符都要判断其是不是回车换行符,而按行读近来,每次缓冲的也要少很多。因此其他几种语言就大大的吃亏了。不过这并不影响结论性的东西,因为测试本身就说明越方便就效率越低。事情总是要有人做,不是吗?

转自:

标签: #java运行效率比c低多少